Point @linear/sdk at https://linear.sandboxapis.dev/graphql via the client's apiUrl option (Linear is GraphQL-only — one endpoint). Both auth forms pass: a bare API key in Authorization, or an OAuth Bearer token.
Coverage badge
Deep14/17 · 82%
The share of read rows with a final answer — served and verified, a deviation, retired upstream, or a reviewed empty/refusal. Computed in the coverage manifest, copied here.
linear.sandboxapis.devServed & verified
13 / 76%
Answering with real universe data, each response checked against Linear's published spec by the conformance suite on this build.
The full read API
17
Every GraphQL entity Linear publishes, deferred long tail included, minus the rows excluded by policy. Writes are out of scope: this universe is read-only.
Not served yet
4
Each returns an explicit, provider-shaped coverage error naming the gap — never invented data.
01 / The swap
No SDK of ours, no shim, no recorded fixtures. The same client library you already use, one environment variable different.
curl -X POST "https://linear.sandboxapis.dev/graphql" -H 'content-type: application/json' -d '{"query":"{ rateLimitStatus { kind } }"}'Verified drop-in clients
@linear/sdk91.0.0The versions the conformance suite drives against this host on every build — pinned in coverage/client-pins.yaml, and watched weekly for upstream releases, because a client library moving without us is how a shipped integration breaks silently.
02 / Coverage by family
All 17 rows the coverage manifest carries for Linear, deferred long tail included and nothing capped. Open a family, or filter by path to find the exact endpoint your client calls.
Status — what a conformance test found
Mode — what kind of answer a row gets
A row with no mode shown has not been judged yet. Modes are the manifest's own words, from coverage/MODES.yaml; every empty and refuse carries a written reason and a review date before it counts as final.
Badge — where Deep starts
REST + GraphQL + git rows that are not `excluded`. Write operations are NOT rows (DECISIONS 2026-09-01 decision 9): they are counted in meta.write_operations and never enter this ratio.
17 read surfaces in 1 family
SERVED 2026-09-12: a Linear Attachment is an issue-to-external-URL LINK rather than an uploaded file (the SDL's own `url` description), so the canonical row behind it is canon's `issue.closed_by_pull` edge - the same edge GitHub renders as a pull request's closing reference and GitLab as a merge request's closed issue, now with a fourth rendering and nothing new asserted. NOT canon's `attachment` table, whose only parents are ticket_comment and macro; reading it that way would have made this row a canon widening instead of a rendering. `url` points at gh.sandboxapis.dev, which is exactly what renderer-github prints as the pull's html_url, so the link resolves (invariant #5) - the VCS_ORIGIN precedent renderer-circleci already set. NO generation gate: closed_by_pull is a base-schema column, so an older pin answers FEWER attachments rather than an error. `subtitle` and `bodyData` are null because composing Linear's widget copy would be authoring it
SERVED 2026-09-12 (hello-17): IssueRelation is the Linear rendering of canon's issue_link - the same row GitLab serves at /issues/{iid}/links. Linear names both ends outright (issue and relatedIssue) and keeps ONE word for the relationship, so Issue.relations is the rows an issue is the source of and Issue.inverseRelations the rows it is the target of, and neither needs GitLab's direction-dependent vocabulary. The type is mapped to Linear's own set - canon `relates` is Linear `related`, canon `duplicates` is `duplicate` - and the mapping THROWS on a kind with no Linear spelling rather than guessing; `parent` has no member in IssueRelationType at all and carries no row. IssueRelation.type is String! and not the enum, copied verbatim from the pinned SDL. On an artifact without the domain every relation field answers an explicit generation error in `errors` rather than an empty connection, which would assert that nothing in this universe is linked (invariant #4's GraphQL clause)
generate — new canon entity Initiative
derive
generate — new canon entity ProjectUpdate
refuse — Linear itself deprecated this surface: packages/conformance/specs/linear/graphql/schema.graphql:39381 marks `roadmap` and :39419 marks `roadmaps` with @deprecated(reason: `Roadmaps are deprecated, use initiatives instead.`), so mirroring it would advertise a surface Linear tells clients to stop calling (the ruled vendor-removed precedent, coverage/reviews/2026-09-01-gitlab-modes.md)
03 / What's simulated
Every Linear call resolves against the same simulated data set every other provider serves. Counted from the linear-2026-08-g11 artifact (universe generation 11):
Each line below is one assertion in the conformance suite named beside it — run on every build, over one artifact, through the real renderers. Nothing is claimed here that no expect checks.
The same issue identifier and title, the same `estimate`, the same history instants in the same order, and the same comment count and comment instants as Jira serves for that issue; per team, cycle names and `startsAt`/`endsAt` equal the Jira board's sprint names and windows.
Against Jira · parity/tracker-parity.test.ts
The history instants for an issue equal the revision instants Azure Boards serves for the same work item, and the `estimate` equals its `StoryPoints`.
Against Azure DevOps · parity/tracker-parity.test.ts
`issue.branchName` is the head branch of the pull request that closed the issue, it round-trips through `issueVcsBranchSearch`, and GitHub serves a branch under that exact name.
Against GitHub · parity/branch-parity.test.ts
`organization.createdAt` — and every team's `createdAt` — is the same instant GitHub's `/orgs/{org}`, GitLab's group and the Zendesk account's locale records return.
Against GitHub, GitLab, Zendesk · parity/org-member-parity.test.ts
The issue at hop 2 of the four-hop walk carries the same identifier, title, estimate and state Jira serves, and the `branchName` at hop 3 is the head branch of the GitHub pull request and the GitLab merge request — on the live hosts and on the frozen `-g6` pins.
Against Slack, Jira, GitHub, GitLab · parity/pinned-cross-category.test.ts
05 / Pinned snapshots
Each pin regenerates byte-identically on every request, so a test written against one never drifts. Generations are DIFFERENT universes, not versions of one — never swap a suffix expecting the same data.
linear-2026-08.snap.sandboxapis.devgeneration 1unversionednot in this generationlinear-2026-08-g10.snap.sandboxapis.devgeneration 10unversionedservedlinear-2026-08-g11.snap.sandboxapis.devgeneration 11unversionedservedlinear-2026-08-g2.snap.sandboxapis.devgeneration 2unversionedservedlinear-2026-08-g3.snap.sandboxapis.devgeneration 3unversionedservedlinear-2026-08-g5.snap.sandboxapis.devgeneration 5unversionedservedlinear-2026-08-g6.snap.sandboxapis.devgeneration 6unversionedservedlinear-2026-08-g8.snap.sandboxapis.devgeneration 8unversionedservedlinear-2026-08-g9.snap.sandboxapis.devgeneration 9unversionedservedNeed an endpoint that is not served yet?
Every row Linear's manifest carries is on this page, so “not here” is an answer rather than a gap in the rendering. Tell us which path and which client, and it moves up the queue — the order is set by what people ask for.