Founding keys are opena year free at 6,000 req/hour, for the first 1,000 developersClaim yours
Documentation 29

Linear quickstart · 3 min read

Keep @linear/sdk. Change one line.

The same simulated data set as every other host — as a Linear workspace. GraphQL-only, like the real thing: cycles, projects, workflow histories, and issue branchNames that name real branches on the live git hosts.

01Point apiUrl at linear.
02Query GraphQL as before
03Pin a snapshot for CI

01 / Base URL

One endpoint, the real schema subset.

Point your client at https://linear.sandboxapis.dev/graphql. Linear has no REST dialect — one GraphQL endpoint, and the served schema subset IS the coverage surface: introspection reflects exactly what's covered, and anything outside it returns an explicit coverage error in errors[], never a silent null.

request.sh
curl -X POST "https://linear.sandboxapis.dev/graphql" \
  -H 'content-type: application/json' \
  -d '{"query":"{ issues(filter: { updatedAt: { gt: \"-P2W\" } }, first: 5) { nodes { identifier title state { name } } } }"}'

02 / Drop in your client

@linear/sdk works unmodified.

The only change is the apiUrl — pointed here at the pinned *.snap. host, so the values below are the values you get. The live host re-rolls daily; the pin never moves.

typescript
import { LinearClient } from "@linear/sdk";

const client = new LinearClient({
  apiKey: "any-token",
  apiUrl: "https://linear-2026-08.snap.sandboxapis.dev/graphql",
});

const me = await client.viewer;                      // full User fragment resolves
const issues = await client.issues({
  filter: { updatedAt: { gt: "-P2W" } },             // delta-polling, deterministic
});

const issue = await client.issue("ARGO-59");         // state: Done
console.log(issue.branchName);                        // "add-per-tenant-backpressure" —
                                                      // head branch of the PR that resolved it

There is a client-free version of the same thing — ten lines of fetch, no dependency — proved end to end against the live host https://linear.sandboxapis.dev by a script that checks its output. Verified quickstarts →

03 / A real response

Live from the universe.

200 · application/json
{
  "data": {
    "issues": {
      "nodes": [
        {
          "identifier": "ORCL-2",
          "title": "Question: how should the onboarding flow behave when the upstream times out?",
          "branchName": "orcl-2-question-how-should-the-onboarding-flow",
          "state": {
            "name": "In Progress",
            "type": "started"
          },
          "assignee": {
            "displayName": "sisyphus"
          }
        },
        {
          "identifier": "ORCL-23",
          "title": "Config precedence ignores the env var",
          "branchName": "clio/types-node",
          "state": {
            "name": "Done",
            "type": "completed"
          },
          "assignee": null
        }
      ]
    }
  }
}

04 / Pin a snapshot

Deterministic CI.

That *.snap. apiUrl is the whole of it — byte-identical on every request. Relative durations (updatedAt: { gt: "-P2W" }) resolve against the snapshot's anchor, so polling loops are reproducible too.

.env
# reproducible in CI
export LINEAR_API_URL=https://linear-2026-08.snap.sandboxapis.dev/graphql

Tour the universe → · Coverage manifest → · Versioning & pinning →