# Linear
Elixir client for the [Linear](https://linear.app) GraphQL API.
Provides a clean, functional interface for querying and mutating Linear data -- issues, comments, projects, workflow states, teams, and more. Includes cursor-based auto-pagination and supports both API key and OAuth authentication.
## Installation
```elixir
def deps do
[
{:linear_client, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
client = Linear.client(api_key: "lin_api_...")
# Fetch the authenticated user
{:ok, viewer} = Linear.viewer(client)
IO.puts(viewer["name"])
# List issues for a team
{:ok, issues} = Linear.list_issues(client, team_id: "TEAM-UUID")
Enum.each(issues, &IO.puts(&1["title"]))
# Create an issue
{:ok, issue} = Linear.create_issue(client, %{
"teamId" => "TEAM-UUID",
"title" => "Fix login bug",
"description" => "Users can't log in with SSO"
})
# Transition an issue by state name
{:ok, _} = Linear.transition_issue(client, "ABC-123", "In Progress")
```
## Authentication
Two modes are supported:
```elixir
# Personal API key (Authorization: <key>)
client = Linear.client(api_key: "lin_api_...")
# OAuth bearer token (Authorization: Bearer <token>)
client = Linear.client(access_token: "oauth_access_token")
```
Create API keys at [Linear Settings > Security & Access](https://linear.app/settings/account/security).
## API Reference
### Queries
```elixir
# Current user
{:ok, viewer} = Linear.viewer(client)
# Teams
{:ok, teams} = Linear.list_teams(client)
# Issues (auto-paginated)
{:ok, issues} = Linear.list_issues(client,
team_id: "...",
state_names: ["In Progress", "Todo"],
assignee_id: "...",
project_id: "...",
first: 50,
include_archived: false
)
# Single issue (UUID or shorthand like "ABC-123")
{:ok, issue} = Linear.get_issue(client, "ABC-123")
# Workflow states
{:ok, states} = Linear.list_workflow_states(client, team_id: "...")
# Projects (auto-paginated)
{:ok, projects} = Linear.list_projects(client)
```
### Mutations
```elixir
# Create issue
{:ok, issue} = Linear.create_issue(client, %{
"teamId" => "...",
"title" => "New feature",
"description" => "Markdown description",
"assigneeId" => "...",
"priority" => 2,
"labelIds" => ["label-uuid"]
})
# Update issue
{:ok, issue} = Linear.update_issue(client, "ABC-123", %{
"title" => "Updated title",
"priority" => 1
})
# Create comment
{:ok, comment} = Linear.create_comment(client, "issue-uuid", "This is fixed in PR #42")
# Transition issue state (resolves state name to ID automatically)
{:ok, issue} = Linear.transition_issue(client, "ABC-123", "Done")
```
### Raw GraphQL
For queries not covered by the built-in helpers:
```elixir
{:ok, data} = Linear.query(client,
"query($id: String!) { issue(id: $id) { title state { name } } }",
variables: %{"id" => "ABC-123"}
)
{:ok, data} = Linear.mutate(client,
"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",
variables: %{"id" => "ABC-123", "input" => %{"title" => "New title"}}
)
```
### Auto-Pagination
`list_issues/2` and `list_projects/2` automatically follow cursors. You can also use pagination directly:
```elixir
{:ok, all_nodes} = Linear.paginate(fn cursor ->
Linear.query(client, my_query, variables: %{"after" => cursor, "first" => 50})
|> case do
{:ok, %{"myConnection" => connection}} -> {:ok, connection}
error -> error
end
end)
```
## Error Handling
All functions return `{:ok, result}` or `{:error, reason}`:
```elixir
case Linear.get_issue(client, "ABC-999") do
{:ok, issue} ->
IO.puts(issue["title"])
{:error, {:graphql_errors, errors, _data}} ->
IO.puts("GraphQL error: #{hd(errors)["message"]}")
{:error, {:http_status, 401}} ->
IO.puts("Authentication failed")
{:error, {:request_failed, reason}} ->
IO.puts("Network error: #{inspect(reason)}")
{:error, {:state_not_found, name}} ->
IO.puts("No workflow state named #{name}")
end
```
## Configuration Options
| Option | Default | Description |
|--------|---------|-------------|
| `:api_key` | `nil` | Linear personal API key |
| `:access_token` | `nil` | OAuth2 bearer token |
| `:endpoint` | `"https://api.linear.app/graphql"` | GraphQL API endpoint |
| `:timeout` | `30_000` | HTTP timeout in milliseconds |
## Architecture
```
Linear (public API facade)
├── Linear.Client — HTTP transport, auth, request/response handling
├── Linear.Queries — Pre-built read queries (viewer, teams, issues, etc.)
├── Linear.Mutations — Pre-built write mutations (create, update, transition)
└── Linear.Pagination — Cursor-based auto-pagination for connections
```
## Development
```bash
mix deps.get
mix test
mix credo --strict
```
## License
MIT — see [LICENSE](LICENSE).