> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Linear

> GraphQL API, OAuth, and webhooks.

Stateful Linear GraphQL API emulation on Linear's published schema, with organizations, users, teams, workflow states, issues, comments, labels, projects, cycles, OAuth apps, tokens, webhooks, and agent sessions. `@linear/sdk` works against it unchanged with `apiUrl` pointed at the emulator's `/graphql`. Reads and writes use in-memory state.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/linear` |
| Port | `4011` in the default set, `4000` when started alone |
| Config key | `linear` |
| HTTP operations | 7, browsable at `http://localhost:3999/docs/linear` while it runs ([API reference](/api-reference.md)) |
| GraphQL root fields | 42 |

```bash CLI theme={null}
npx @deploybaseline/cli start --service linear
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const linear = await createEmulator({ service: "linear" });
// linear.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "linear": {
    "organization": {
      "name": "Acme",
      "url_key": "acme"
    },
    "users": [
      {
        "email": "admin@example.com",
        "name": "Admin User",
        "admin": true
      },
      {
        "email": "dev@example.com",
        "name": "Developer"
      }
    ],
    "teams": [
      {
        "key": "ENG",
        "name": "Engineering",
        "states": [
          {
            "name": "Backlog",
            "type": "backlog"
          },
          {
            "name": "Todo",
            "type": "unstarted"
          },
          {
            "name": "In Progress",
            "type": "started"
          },
          {
            "name": "Done",
            "type": "completed"
          }
        ]
      }
    ],
    "labels": [
      {
        "name": "Bug",
        "color": "#d92d20",
        "team": "ENG"
      },
      {
        "name": "Feature",
        "color": "#2563eb",
        "team": "ENG"
      }
    ],
    "issues": [
      {
        "team": "ENG",
        "title": "Fix local checkout test",
        "description": "Reproduce and fix the checkout failure.",
        "state": "Todo",
        "assignee": "dev@example.com",
        "labels": ["Bug"]
      }
    ],
    "oauth_apps": [
      {
        "client_id": "lin_example_client_id",
        "client_secret": "example_client_secret",
        "name": "My Linear App",
        "redirect_uris": ["http://localhost:3000/api/auth/callback/linear"],
        "scopes": ["read", "write", "issues:create", "comments:create"],
        "actor": "user"
      }
    ],
    "tokens": [
      {
        "token": "lin_test_admin",
        "user": "admin@example.com",
        "scopes": ["read", "write", "issues:create", "comments:create", "admin"]
      }
    ],
    "strict_scopes": false
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service linear
```

Linear is a default provider and listens on `http://localhost:4011` when all services start; started alone, it uses `http://localhost:4000`. Without a seed config there is a `Baseline` organization, an admin (`admin@linear.local`) and a developer (`dev@linear.local`), an `ENG` team with `Bug` and `Feature` labels, a project and a cycle, an OAuth app (`lin_example_client_id`), and the token `lin_test_admin`.

Point `@linear/sdk` at the emulator's `/graphql` with `apiUrl`:

```ts theme={null}
import { LinearClient } from "@linear/sdk";

const linear = new LinearClient({
  apiKey: "lin_test_admin",
  apiUrl: `${process.env.LINEAR_EMULATOR_URL}/graphql`,
});
const issues = await linear.issues();
```

## URL mapping

| Real Linear URL | Emulator URL |
| - | - |
| `https://api.linear.app/graphql` | `$LINEAR_EMULATOR_URL/graphql` |
| `https://linear.app/oauth/authorize` | `$LINEAR_EMULATOR_URL/oauth/authorize` |
| `https://api.linear.app/oauth/token` | `$LINEAR_EMULATOR_URL/oauth/token` |
| `https://api.linear.app/oauth/revoke` | `$LINEAR_EMULATOR_URL/oauth/revoke` |

## Auth

GraphQL accepts `Authorization: Bearer <token>` or a bare personal API key value. The default seeded token is `lin_test_admin`. Seeded tokens map to users or app actors.

Scope checks are relaxed by default. Set `linear.strict_scopes: true` to require supported operation scopes such as `read`, `write`, `issues:create`, `comments:create`, and `admin`.

### OAuth

* `GET /oauth/authorize` — Authorize: local user picker
* `POST /oauth/authorize/callback` — User picker callback: local callback used by the user picker
* `POST /oauth/token` — Exchange token: authorization code, refresh token, and client credentials grants
* `POST /oauth/revoke` — Revoke token: access or refresh tokens

OAuth supports the authorization code grant with PKCE, refresh tokens, revoke, client credentials, and `actor=app` tokens for local app-actor tests. OAuth apps can use `actor: user` or `actor: app`. The configured actor is authoritative. User actor apps use authorization code flows. App actor apps use the app install flow and can request client credentials tokens.

## GraphQL

* `POST /graphql` — Run a GraphQL query or mutation
* `GET /graphql` — Run a GraphQL query from the query string: for tooling. Queries only; a mutation over GET gets HTTP 405

The endpoint serves Linear's published GraphQL schema, vendored from `@linear/sdk`, so queries are validated as Linear validates them: a field, argument, or input key Linear does not have fails with HTTP 400. Any GraphQL error answers HTTP 400. The endpoint runs on the shared [GraphQL kit](/graphql.md).

Supported queries:

* `viewer`
* `organization`
* `users`, `user`
* `teams`, `team`, `teamMembership`
* `workflowStates`, `workflowState`
* `issues`, `issue`
* `comments`, `comment`
* `issueLabels`, `issueLabel`
* `projects`, `project`
* `cycles`, `cycle`
* `webhooks`, `webhook`
* `agentSessions`, `agentSession`

Supported mutations:

* `issueCreate`, `issueUpdate`, `issueDelete`, `issueArchive`, `issueUnarchive`
* `commentCreate`, `commentUpdate`, `commentDelete`
* `issueLabelCreate`, `issueLabelUpdate`, `issueLabelDelete` (label names are unique, ignoring case, within a team and against workspace labels: `Duplicate label name`)
* `issueAddLabel`, `issueRemoveLabel`
* `webhookCreate`, `webhookDelete`
* `agentSessionCreateOnIssue`, `agentSessionCreateOnComment`, `agentSessionUpdate`
* `agentActivityCreate`

Any other root field Linear has (for example `initiatives` or `documents`) fails with `<Type>.<field> is not emulated by baseline`.

Connections use Relay-style cursors with `nodes`, `edges`, and `pageInfo`.

### Fields the emulator does not model

`@linear/sdk` selects every scalar field of a model, so the emulator answers the whole schema. A field it does not model reads as an empty workspace would have it: `null` when nullable, otherwise `false`, `0`, an empty string, list, or JSON object, or an empty connection. A non-null field that has no empty value (an object, enum, or date) fails with `<Type>.<field> is not emulated by baseline` rather than inventing one.

Issue selections expose both numeric `priority` and Linear's derived `priorityLabel` values: `No priority`, `Urgent`, `High`, `Medium`, and `Low`. Issues and comments created with `createAsUser` return a `botActor` with that name and `displayIconUrl`.

Teams expose `viewerMembership`, `viewerCanJoin`, and `viewerCanLeave`. Team membership is not modeled, so the viewer counts as a member of every team. Initiatives are not modeled either, so `ledInitiativeCount` is always `0` and `initiativesEnabled` is `false`.

### Filters and ordering

Connections take Linear's filter inputs (`IssueFilter`, `TeamFilter`, `UserFilter`, and so on) with their comparators: `eq`, `neq`, `in`, `nin`, `null`, `lt`, `lte`, `gt`, `gte`, the string comparators (`contains`, `startsWith`, `containsIgnoreCase`, and the rest), and `and` / `or`. Relations filter through the related entity (`team: { key: { eq: "ENG" } }`), and collections take `some`, `every`, and `length` (`labels: { some: { name: { eq: "Bug" } } }`). Date comparators take ISO dates or durations relative to now, such as `-P2W`. An issue `id` comparator matches the UUID or the identifier (`ENG-12`).

A filter key the schema has but the emulator does not model fails with `IssueFilter.<key> is not emulated by baseline` rather than matching everything. `orderBy` takes `createdAt` (the default) or `updatedAt`.

Issue lists leave out archived and trashed issues unless the query passes `includeArchived: true`, and user lists leave out deactivated users unless it passes `includeDisabled: true`.

### Mutations

* `issueUpdate(id:, input:)` accepts `labelIds`, or `addedLabelIds` and `removedLabelIds` to change labels in place.
* `issueDelete` moves the issue to the trash (archived, with `trashed: true`), as Linear does. `issueDelete(id:, permanentlyDelete: true)` removes it with its comments and agent sessions, and needs an admin.
* `webhookCreate` requires `resourceTypes`.
* A single lookup by an unknown ID fails with `Entity not found: <Type>`.
* An input key Linear accepts but the emulator does not model (for example `IssueCreateInput.estimate`) fails with `<Input>.<key> is not emulated by baseline` instead of being dropped.

### Agent sessions

Agent sessions follow [Linear's agent API](https://linear.app/developers/agents). A session belongs to an app user: the caller when it authenticates as an app (`actor: app`), otherwise the first seeded app user, otherwise the caller. Sessions start `pending`. Activities move them:

| Activity `content.type` | Session `status` |
| - | - |
| `thought`, `action` | `active` |
| `elicitation` | `awaitingInput` |
| `response` | `complete` |
| `error` | `error` |
| `prompt` | `pending` |

`agentActivityCreate` takes `agentSessionId` and `content`, and validates content per type (an `action` needs `action` and `parameter`; the others need `body`). Only `thought` and `action` activities may be `ephemeral`. `agentSessionUpdate` sets `plan`, `summary` (one line, 1 to 255 characters), `externalLink`, and `externalUrls`, `addedExternalUrls`, or `removedExternalUrls`.

Creating a session, delegating an issue to an app user, or mentioning an app user in a comment sends an `AgentSessionEvent` webhook with `action: created`. A user's `prompt` activity sends `action: prompted` with the activity.

### Errors

Every error carries Linear's `extensions.type`, which `@linear/sdk` maps to its error classes: `invalid input` (`InvalidInputLinearError`) for bad input and unknown IDs, `forbidden` (`ForbiddenLinearError`) for a missing scope, `graphql error` (`GraphqlLinearError`) for a query the schema rejects, and `feature not accessible` (`FeatureNotAccessibleLinearError`) for a field the emulator does not model, which also keeps `extensions.code: NOT_EMULATED`.

## Webhooks

Create local webhook subscriptions through `webhookCreate` or seed config. Supported writes dispatch Linear-shaped payloads with these headers:

* `Linear-Delivery`
* `Linear-Event`
* `Linear-Signature`

## Seed config

```json theme={null}
{
  "linear": {
    "organization": {
      "name": "Acme",
      "url_key": "acme"
    },
    "users": [
      {
        "email": "admin@example.com",
        "name": "Admin User",
        "admin": true
      },
      {
        "email": "dev@example.com",
        "name": "Developer"
      }
    ],
    "teams": [
      {
        "key": "ENG",
        "name": "Engineering",
        "states": [
          {
            "name": "Backlog",
            "type": "backlog"
          },
          {
            "name": "Todo",
            "type": "unstarted"
          },
          {
            "name": "In Progress",
            "type": "started"
          },
          {
            "name": "Done",
            "type": "completed"
          }
        ]
      }
    ],
    "labels": [
      {
        "name": "Bug",
        "color": "#d92d20",
        "team": "ENG"
      },
      {
        "name": "Feature",
        "color": "#2563eb",
        "team": "ENG"
      }
    ],
    "issues": [
      {
        "team": "ENG",
        "title": "Fix local checkout test",
        "description": "Reproduce and fix the checkout failure.",
        "state": "Todo",
        "assignee": "dev@example.com",
        "labels": ["Bug"]
      }
    ],
    "oauth_apps": [
      {
        "client_id": "lin_example_client_id",
        "client_secret": "example_client_secret",
        "name": "My Linear App",
        "redirect_uris": ["http://localhost:3000/api/auth/callback/linear"],
        "scopes": ["read", "write", "issues:create", "comments:create"],
        "actor": "user"
      }
    ],
    "tokens": [
      {
        "token": "lin_test_admin",
        "user": "admin@example.com",
        "scopes": ["read", "write", "issues:create", "comments:create", "admin"]
      }
    ],
    "strict_scopes": false
  }
}
```

| Key | What it seeds |
| - | - |
| `organization` | The workspace `name` and `url_key` |
| `users` | Users by `email`, with `name` and optional `admin` |
| `teams` | Teams by `key`, with a `name` and workflow `states` (`name`, `type`) |
| `labels` | Issue labels (`name`, `color`, `team`) |
| `issues` | Issues (`team`, `title`, `description`, `state`, `assignee`, `labels`) |
| `oauth_apps` | OAuth apps (`client_id`, `client_secret`, `name`, `redirect_uris`, `scopes`, `actor` `user` or `app`) |
| `tokens` | API tokens (`token`, `user`, `scopes`) |
| `strict_scopes` | `true` requires operation scopes (see [Auth](#auth)); `false` by default |

## Inspector

* `GET /` — Inspector: tabbed view of issues, teams, users, projects, agent sessions, OAuth apps, tokens, webhook subscriptions, and webhook deliveries

## Limits

Resolvers for the root fields not listed above, exact production rate limiting, notification inbox behavior, rich document APIs, customer APIs, initiative APIs, exact search relevance, and full production agent behavior are not implemented.

## Conformance

| | |
| - | - |
| Routes | 7 supported or partial, 0 planned |
| GraphQL root fields | 42 supported or partial, 0 planned |
| Route gate | fail |
| Spec gate | no public spec |
| SDKs tested | `@linear/sdk@98.0.0` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.