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

# Linear template

> A real Linear GraphQL API backend — issues, projects, teams, cycles, and webhooks.

The Linear template (`apps/linear-clone`, name `linear`) is an API-only clone of
the [Linear GraphQL API](https://linear.app/developers/graphql). Unlike the other
templates, Linear is **not REST** — its public API is a single GraphQL endpoint,
and the clone mirrors that exactly: one `POST /graphql` that accepts
`{ query, variables, operationName }`, returns the GraphQL `{ data, errors }`
transport envelope, dispatches Linear's literal camelCase operation names
(`issueCreate`, `issues`, `teamCreate`, …), and paginates with **Relay
connections**. It's faithful enough at the **shape level** that an agent built for
Linear can operate it.

```bash theme={null}
asymmetric spin linear
```

## What's inside

| Piece                  | Notes                                                                                                                                                                                                                                                                             |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `backend/`             | NestJS backend on Postgres via `pg`. Data plane is `POST /graphql`; a small REST control plane lives under `/api`. JWT (access + refresh) + Linear-style `lin_api_…` personal API keys, Socket.io realtime. Health at `/api/health`, Swagger (control plane only) at `/api/docs`. |
| `mcp-server/`          | Model Context Protocol server for AI interactions.                                                                                                                                                                                                                                |
| `supabase/migrations/` | `001_initial_schema.sql` — 15 tables + triggers, run on spin and replayed on reset.                                                                                                                                                                                               |
| `seeds/`               | `acme.sql` — the bundled deterministic, idempotent demo fixture.                                                                                                                                                                                                                  |

This template is **API-only** — there is no frontend.

## Modes

| Mode            | Services  | Use it for                                         |
| --------------- | --------- | -------------------------------------------------- |
| `api` (default) | `backend` | Agents that talk to the GraphQL API or MCP server. |

## Seeding

```bash theme={null}
asymmetric spin linear --seed acme           # deterministic fixture on create
asymmetric seed linear-a1b2 --ai             # realistic data via the API
```

Seeded users share the password `password123` (e.g. `dana@acme.test`). AI seeding
generates `users`, `teams`, `issues`, and `projects` (the template's declared
entities). See [Seeding](/concepts/seeding).

## Tokens and auth

Two credential types both resolve to the same principal:

* a **JWT** access token from `POST /api/auth/signup` / `login` / `refresh` (the
  web/control-plane path), and
* a Linear-shaped **personal API key** (`lin_api_…`) minted at
  `POST /api/api-keys`. Only `sha256(key)` is stored; the plaintext is returned in
  the `token` field **once** at creation.

Against the GraphQL endpoint, send the personal API key **raw** in the
`Authorization` header (no `Bearer` prefix) — this is exactly how Linear
authenticates a personal API key. An OAuth-style `Bearer` token is also accepted.

```bash theme={null}
B=http://127.0.0.1:3002
# Control plane (REST): sign up, create an org (token is refreshed to carry the
# org claim), then mint a personal API key.
TOK=$(curl -s $B/api/auth/signup -H 'content-type: application/json' \
  -d '{"email":"you@acme.test","password":"password123","name":"You"}' | jq -r .accessToken)
TOK=$(curl -s $B/api/organization -H "authorization: Bearer $TOK" -H 'content-type: application/json' \
  -d '{"name":"Acme","urlKey":"acme"}' | jq -r .accessToken)
KEY=$(curl -s $B/api/api-keys -H "authorization: Bearer $TOK" -H 'content-type: application/json' \
  -d '{"label":"agent"}' | jq -r .token)
```

<Note>
  A `lin_api_` key authenticates but its **scopes are not enforced** — any valid key
  acts with its principal's full access. OAuth app installs and scope enforcement
  are out of scope for this slice. Clones bind to localhost by default, so a key is
  not reachable off your machine unless you `--expose`.
</Note>

## API tour — it's GraphQL

Every data-plane operation is a `POST /graphql`. Mutations return Linear's
`{ success, <entity>, lastSyncId }` payload; queries that return lists are Relay
connections (`nodes`, `edges { node, cursor }`, `pageInfo { hasNextPage, endCursor }`)
with `first` / `after` args.

```bash theme={null}
B=http://127.0.0.1:3002
H="authorization: $KEY"   # raw lin_api_… key, Linear-style

# Create a team (auto-seeds 6 default workflow states)
TEAM=$(curl -s $B/graphql -H "$H" -H 'content-type: application/json' -d '{
  "query":"mutation($input: TeamCreateInput!){ teamCreate(input:$input){ success team{ id key name } } }",
  "variables":{"input":{"key":"ENG","name":"Engineering"}}
}' | jq -r .data.teamCreate.team.id)

# File an issue → human identifier ENG-1 (team key + per-team counter)
curl -s $B/graphql -H "$H" -H 'content-type: application/json' -d "{
  \"query\":\"mutation(\$input: IssueCreateInput!){ issueCreate(input:\$input){ success issue{ identifier title } } }\",
  \"variables\":{\"input\":{\"teamId\":\"$TEAM\",\"title\":\"First bug\",\"priority\":2}}
}" | jq '.data.issueCreate.issue'

# Read issues back through a Relay connection
curl -s $B/graphql -H "$H" -H 'content-type: application/json' -d '{
  "query":"{ issues(first: 10){ nodes{ identifier title } pageInfo{ hasNextPage endCursor } } }"
}' | jq '.data.issues'
```

Issues are numbered **per team** and carry a human identifier like `ENG-42`
(`team.key` + a monotonic counter). Entity ids are UUIDs, matching Linear.

### Realtime

Connect a Socket.io client with `auth: { token }` (JWT or API key). You're joined
to an `org:<id>` room and receive every domain event (`issue.created`,
`issue.updated`, `comment.created`, `project.*`, `cycle.*`).

### Webhooks

Register a target with `POST /api/webhooks` (optionally scoped to a team and to
specific resource types). Deliveries are HTTP POSTs signed with a
`Linear-Signature` header = hex `HMAC-SHA256(rawBody, secret)`, carrying Linear's
webhook envelope:

```json theme={null}
{ "action": "create", "type": "Issue", "actor": {…}, "createdAt": "…",
  "data": {…}, "url": "…", "updatedFrom": {…}, "webhookTimestamp": 0,
  "webhookId": "…", "organizationId": "…" }
```

`updatedFrom` is present only on `update` actions, and `url` is derived per entity
type — matching Linear.

### Rate limiting

The GraphQL endpoint enforces Linear's published budgets **per principal**: 5,000
requests/hour, 3,000,000 complexity points/hour, and a 10,000-point cap on a
single query (complexity is estimated from the selection set). When a budget is
exhausted the response is **HTTP 400** with
`errors[].extensions.code = "RATELIMITED"`, and every response carries
`X-RateLimit-Requests-*` and `X-RateLimit-Complexity-*` headers
(`Limit` / `Remaining` / `Reset`).

## API shape — read this before pointing an agent at it

Linear's real API is **GraphQL**, and the clone reproduces that paradigm exactly:
a single `POST /graphql`, the `{ data, errors }` transport envelope, Linear's
literal operation names, `{ success, <entity>, lastSyncId }` mutation payloads,
and Relay-style connection pagination. So this template matches Linear at the
**shape level**, not by URL paths.

The template ships an `API_PARITY.md` that scores it against Linear's live GraphQL
docs. On the representative core set of 30 Linear operations (issues, comments,
projects, teams, labels, workflow states, cycles, org, users/viewer, webhooks):

| Status                                                     | Count |
| ---------------------------------------------------------- | ----- |
| ✅ exact (identifier + GraphQL convention + envelope align) | 30    |
| ⚠️ partial                                                 | 0     |
| ❌ missing                                                  | 0     |

That's **100% capability and 100% exact-shape coverage of the targeted core
surface**. Linear's full schema is hundreds of operations; the long tail
(attachments, documents, project updates, reactions, favorites, roadmaps,
notifications, team-membership mutations, …) is unimplemented by design — this is
a core slice, not the whole schema. See `apps/linear-clone/API_PARITY.md` for the
per-operation breakdown.

A small **REST control plane** stays under the `/api` prefix for bootstrap that
sits outside Linear's GraphQL surface: `auth/{signup,login,refresh,logout}`,
`api-keys`, and `health`.

<Info>
  The [`verify`](/cli/login#verify) command will fold live fidelity scoring into the
  CLI. Until then, `API_PARITY.md` and the `/verify-api linear` workflow are how
  fidelity is tracked.
</Info>

## MCP server

`mcp-server/` exposes the clone to MCP clients with a personal API key. It ships a
core subset of Linear's MCP tools: `list_teams`, `list_issues`, `get_issue`,
`create_issue`, `update_issue`, `create_comment`, and `list_projects`.

```bash theme={null}
cd mcp-server && bun install
LINEAR_API_URL=http://127.0.0.1:3002/api LINEAR_API_KEY=lin_api_… bun run dev
```

## Inspect a running clone

```bash theme={null}
asymmetric query linear-a1b2 "select identifier, title from issues"
asymmetric db linear-a1b2          # prints a psql shell command
asymmetric logs linear-a1b2 -f     # stream the backend
```

The schema (organizations, users, teams, issues, comments, projects, cycles,
workflow\_states, labels, webhooks, …) comes straight from
`001_initial_schema.sql` — read it to know what's queryable.
