Linear

Elixir client for the Linear 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

def deps do
  [
    {:linear_client, "~> 0.1.0"}
  ]
end

Quick Start

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:

# 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.

API Reference

Queries

# 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

# 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:

{: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:

{: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}:

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_keynil Linear personal API key
:access_tokennil OAuth2 bearer token
:endpoint"https://api.linear.app/graphql" GraphQL API endpoint
:timeout30_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

mix deps.get
mix test
mix credo --strict

License

MIT — see LICENSE.