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

Buildkite

Point a Buildkite client at https://buildkite.sandboxapis.dev — paths are /v2/… exactly as on api.buildkite.com. go-buildkite takes WithBaseURL; pybuildkite takes base_url; the bk CLI reads BUILDKITE_REST_API_ENDPOINT. Auth is 'Authorization: Bearer <anything>', and no token at all works too. WHAT IS RENDERED: the CI walk, end to end — the organization, its pipelines (one per repository CI workflow), each pipeline's builds, each build's jobs, and each job's LOG OUTPUT with the real command output, exit code and per-line timestamps. The builds are the SAME runs gl./bb./ado. serve for the same commits, at the same SHAs, by the same people — a build here and a pipeline run there are one run in two dialects. ALSO SERVED, AS HONEST EMPTY LISTS: the organization's agent list and the build/job annotation lists. Buildkite returns only CONNECTED AND STOPPING agents and no build here is in flight — every one has finished — so [] is the true answer rather than a gap; an annotation is uploaded by 'buildkite-agent annotate' from inside a job, and no job in this universe ran it. Those are 200s with an empty array, never 404s. WHAT IS NOT: the custom emoji catalogue, because Buildkite publishes it in a repository that carries no licence of any kind — there is no grant to republish it, and serving the handful of entries the docs quote would misstate how complete it is. It answers an explicit coverage 404 rather than an invented value. The whole of cluster, queue, schedule, template, trigger, portal and rule administration is uncovered and says so. TWO GENERATION NOTES: job logs need universe generation hello-13 or later, and ARTIFACTS — metadata, content and download — need hello-15, the generation that gave the canonical build artifact the job link a Buildkite artifact's job_id hangs off. Live rides hello-19, which inherits it, and so do the buildkite-v2-g11 pin on hello-16 and the buildkite-v2-g10 pin on hello-15 itself. On an older snapshot each answers a 404 that names the generation and the rest of the walk still serves. Check https://sandboxapis.dev/providers/buildkite for the row-by-row picture.

REST · 78 endpointsMCP-ready

Coverage badge

Deep75/78 · 96%

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.

Live host
buildkite.sandboxapis.dev
Pinned hosts
4 — generation 8, 9, 10, 11
Serving since
2026-09-05
Lifecycle
Live

Served & verified

45 / 58%

Answering with real universe data, each response checked against Buildkite's published spec by the conformance suite on this build.

The full read API

78

Every read surface Buildkite publishes, deferred long tail included, minus the rows excluded by policy. Writes are out of scope: this universe is read-only.

Not served yet

33

Each returns an explicit, provider-shaped coverage error naming the gap — never invented data.

01 / The swap

Point your client at a different base URL.

No SDK of ours, no shim, no recorded fixtures. The same client library you already use, one environment variable different.

shell
curl "https://buildkite.sandboxapis.dev/v2/organizations/olympus-labs/pipelines"

Verified drop-in clients

No client library is version-pinned against Buildkite yet, so this page claims none. What is asserted is the wire: every response above is validated against Buildkite's own published spec by the conformance suite. Client pins live in coverage/client-pins.yaml and are added as each SDK joins the suite.

This spec is hand-authored, and that is a weaker guarantee.

Buildkite publishes no machine-readable API description, so the spec we conform to is written from their documentation plus live captures (retrieved 2026-09-05) and re-diffed against those pages on a schedule. Where a vendored spec makes conformance a mechanical check, this one is only as good as our reading of the docs — which is why it is stated here rather than buried in the manifest.

Spec of record: buildkite/docs REST API pages (no machine-readable spec for this API - hand-authored shape-of-record) + live captures 2026-09-05

02 / Coverage by family

Every read surface, grouped the way Buildkite groups it.

All 78 rows the coverage manifest carries for Buildkite, 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

served & verified
answers with real universe data, and this build checked that response against the vendor's spec.
deviation
served and faithful to the real provider, but failing the vendored spec — usually a bug in the spec.
retired
the vendor removed the endpoint; snapshots pinned before that date still serve it.
planned / deferred
not served yet — an explicit coverage error naming the gap, never invented data.
excluded
out of the claim by policy (writes, and surfaces we refuse); not in any denominator on this page.

Mode — what kind of answer a row gets

derive
the response is computed from artifact rows that already exist
generate
canon does not carry this yet; the generator will produce it, then derive
empty
the true answer for this universe is an empty collection — reason + reviewed date required
refuse
mirror the provider's OWN refusal (e.g. its 403 for a non-admin token) — reason + reviewed date required
read-only
a write named in the manifest because clients probe it; the read-only 403 IS its final response, and it never joins the badge denominator

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.

Held — buildable, deliberately not built yet

data-licensing
the values are VENDOR reference data this project holds no grant to redistribute. Serving needs an independently licensed source, so the outstanding work is a licensing answer, not code.

held is a label, not a mode and not a status. A held row is one somebody looked at, judged buildable, and left with the blocker named — so it is not covered, counts against the ratio above exactly like any other unbuilt row, and still answers the coverage 404. It buys nothing; it only tells you why.

Badge — where Deep starts

Full
every published read row has a final answer
Deep
60% up to 100%
Partial
25% up to 60%
Preview
under 25%

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.

78 read surfaces in 24 families

clusters1 of 10 served & verified
GET
/v2/organizations/{org.slug}/clusters

olympus-labs defines no Buildkite cluster - a cluster is an isolated set of AGENTS and pipelines, this canon carries no runner entity at all (the founder's 2026-09-05 agents ruling, which serves `[]` from this same host), and the pipeline objects this host serves deliberately omit `cluster_id`; Buildkite's own answer for an organization with no cluster is `[]` at 200. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

served & verified
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/agent-images

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/agent-images/{id}

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/cache_volumes

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/network_ranges

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/queues

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/queues/{id}

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/tokens

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{cluster.id}/tokens/{id}

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
GET
/v2/organizations/{org.slug}/clusters/{id}

refuse — ENTAILED by the empty beside it: .../clusters serves `[]` on this same host, so no cluster id resolves and every sub-resource under it is Buildkite's own not-found (the agents/{id} precedent); the operation declares only a 200, so the body is Buildkite's documented `{"message": ...}` envelope per REFUSE_SHAPES.md's nearest-published rule - which is also why refusal is the only terminal option here, the 200 shape being undocumented. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
teams5 of 8 served & verified
GET
/v2/organizations/{org.slug}/teams

SERVED 2026-09-12 (wave W6): the organization's four teams - reader.listTeams(), the SAME canonical rows gh., gl., jira. and linear. already serve, which is why refusing the family on Buildkite's own teams PLAN GATE was rejected: the gate is real (rest_api__teams.md:366-372 publishes a 403 reading 'Your plan doesn't include team permissions'), but it is a claim about an ACCOUNT, and nothing this host serves says olympus-labs is on a plan without teams while five other hosts serve its four (invariant #5). THREE JUDGMENTS, STATED: `description` is the same sentence renderer-github composes from the same two columns, so one canonical team has one description across the fleet; `privacy` is `visible`, a translation of the `closed` GitHub already publishes for these teams rather than a new claim; `default` is false on all four and it is a COUNT rather than a constant - Buildkite's own words are 'whether users join this team by default', the largest team here holds five of seventeen members, and conformance re-runs the count. created_at is orgCreatedEpoch() (canon has no team epoch and the generator builds the teams in the same scaffolding pass as the org), and created_by is the founding member, read off the membership ledger's one added_by NULL row where hello-16 canon exists and off the organization's first member otherwise - conformance asserts the two name one person

served & verified
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}

the same Team object addressed by uuid, from the same accessor and with the same three judgments

served & verified
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/members

SERVED 2026-09-12 (the response-shape wave): a team's seats, from reader.listMemberships('team', team). THE CANON WAS ALREADY WIRED - reader.teamMembers() has answered the documented user_id filter on /teams since wave W6 - and what was missing was the vendored page (pages/apis/rest_api/teams/members.md). `created_at` is the ASSOCIATION's own instant, which the page states in those words, and it is why this row reads the membership table rather than the bare roster: only membership.added_epoch carries it. `role` collapses canon's five roles into Buildkite's closed member/maintainer enum. Conformance asserts created_at IS added_epoch, so a later wave cannot substitute the person's own first-seen date. Gated on hasMemberships() (hello-16)

served & verified
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/members/{user.uuid}

SERVED 2026-09-12: one seat by the uuid the team roster publishes, from the same pairing; a uuid on no seat is Buildkite's 404

served & verified
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/suites

no team here has a Test Engine suite, and the fact was already on the record rather than found for this ruling: Buildkite Test Engine is a DIFFERENT PRODUCT served under the /v2/analytics prefix, and coverage/provider-pins.yaml states in as many words that this universe has no canon for its suites, tests or runs. Canon's test_case.suite_slot is an attribute OF A CASE - which unit or integration bucket it belongs to - not a Suite entity with a uuid, a name, a slug and a default branch. An organization that has not enabled Test Engine answers the empty page. The TEAM still resolves: a uuid naming no team is Buildkite's 404, not a cheerful `[]`. RULED by the founder 2026-09-12 on the unclassified sweep's proposal, as proposed.

served & verified
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/pipelines

derive — RE-PRICED 2026-09-13 (the unjudged-47 pass) from the `generate` the 2026-09-13 response-shape wave proposed. THAT WAVE'S PREMISE WAS 'NO TEAM-TO-PIPELINE EDGE IN CANON', AND THE EDGE EXISTS: hello-17's grant rows put a TEAM principal on a repository membership - packages/generator/src/grants.ts:216-229 writes one `maintainer` grant per repository for the team that owns most of its components, and :233-248 a `member` grant for every team that reviews it - and reader.teamGrants('repo', repo) is the accessor that reaches them, the same one renderer-bitbucket/src/app.ts:1529 already serves permissions-config/groups from. A Buildkite pipeline on this host IS a canonical (repo, workflow) pair (renderer-buildkite/src/render.ts:125, listPipelines), so a team's pipelines are the pipelines of the repositories it holds a grant on; `created_at` is the grant's own added_epoch - the ASSOCIATION's instant, exactly as the members rows beside it already read it - and `pipeline_id` and `pipeline_url` are the uuid and URL /pipelines publishes today. DEFERRED RATHER THAN GREEN, on the ADO chartimages precedent that `derive` states where a row is going while `deferred` says it is not built, because TWO JUDGMENTS ARE STILL OWED: whether a team's authority over a REPOSITORY projects onto every pipeline OF that repository, and how canon's five MembershipRoleKeys collapse into Buildkite's closed three-value access_level enum (read_only / build_and_read / manage_build_and_read), the same collapse the members rows made for `role`. It needs the hello-17 generation gate too, for the reason gap.test.ts exists: teamGrants() answers `[]` rather than throwing on an older pin, and an `[]` here would be a plausible lie about the UNIVERSE rather than the truth about a SNAPSHOT

deferred
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/pipelines/{uuid}

derive — one team pipeline by the uuid the list beside it publishes, off the same reader.teamGrants('repo', repo) x listPipelines() fold; a uuid naming no pipeline of that team is Buildkite's 404. RE-PRICED 2026-09-13 with the list - the same edge, the same two owed judgments, the same hello-17 gate

deferred
Next
GET
/v2/organizations/{org.slug}/teams/{team.uuid}/suites/{uuid}

refuse — ENTAILED by the empty beside it: the team's suite list serves `[]` on this same host, so no uuid names a suite. CLAUSE WITHDRAWN THE SAME DAY, and recorded rather than quietly deleted: this reason also argued that refusal was the ONLY terminal option because the 200 shape was undocumented. It is documented - rest_api/teams/suites.md was vendored hours later in the response-shape wave, and the spec of record now carries a TeamSuite schema. The refusal stands on the entailment ALONE, which was always the stronger half. The body is the captured generic `{"message":"Not Found"}` for the reason wave W6's thirteen carry it, and x-sandboxapis-refusal is the wire difference. RULED by the founder 2026-09-12 on the unclassified sweep's proposal, as proposed.

deferred
Next
artifacts7 of 7 served & verified
GET
/v2/organizations/{org.slug}/jobs/{job.id}/artifacts

hello-15 + PR C (#383) closed BOTH deferrals this family had. The first, at classification: canon's `Artifact` carried no job link and every Buildkite artifact route is job-scoped - closed by `Artifact.job`. The second, found by the serving wave on 2026-09-07: `sha1sum` is REQUIRED on the `Artifact` schema and EVERY route returning one `$ref`s it, so a row without bytes served nothing, list or show. PR C gave every artifact real TEXT content - a JUnit document built from the run's own TestResult rows, a size report over real blob lengths, an SBOM over the repository's real dependency manifest - with a compiler-MEASURED `size_bytes` and a SHA-1 and SHA-256 of exactly those bytes. Reads `reader.jobArtifacts()`/`reader.runArtifacts()`; `state` is `finished` because every job here completed and really uploaded, and `glob_path`/`original_path` are the null the provider itself sends on two deprecated fields

served & verified
Next
GET
/v2/organizations/{org.slug}/jobs/{job.id}/artifacts/{id}

the same artifact addressed by id, from `reader.jobArtifacts()`. Buildkite publishes an org-scoped and a build-scoped path to one artifact and the object carries ONE canonical `url` - the build-scoped spelling its own examples print - so a caller cannot see the same artifact claim two different canonical URLs depending on how it was reached

served & verified
Next
GET
/v2/organizations/{org.slug}/jobs/{job.id}/artifacts/{id}/download

the download, from `reader.artifactContent()` - the bytes PR C composed. MIRRORS THE DOCUMENT EXACTLY: 302, the URL in the `Location` header AND in a one-field JSON body, which is what the page describes (`The URL will be returned in the response body and the Location HTTP header`). The target is a SIGNED path on the service host rather than these bytes served inline: serving 200 here would break every SDK that reads the signed URL with `redirect: manual`, and pointing at buildkiteartifacts.com would send a caller out of the universe to a URL that 404s. It is the founder's 2026-09-02 ruling for GitHub's five 302 operations applied unchanged, and it is also what Buildkite's own sentence describes for a customer using their own bucket

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/artifacts

hello-15 + PR C (#383) closed BOTH deferrals this family had. The first, at classification: canon's `Artifact` carried no job link and every Buildkite artifact route is job-scoped - closed by `Artifact.job`. The second, found by the serving wave on 2026-09-07: `sha1sum` is REQUIRED on the `Artifact` schema and EVERY route returning one `$ref`s it, so a row without bytes served nothing, list or show. PR C gave every artifact real TEXT content - a JUnit document built from the run's own TestResult rows, a size report over real blob lengths, an SBOM over the repository's real dependency manifest - with a compiler-MEASURED `size_bytes` and a SHA-1 and SHA-256 of exactly those bytes. Reads `reader.jobArtifacts()`/`reader.runArtifacts()`; `state` is `finished` because every job here completed and really uploaded, and `glob_path`/`original_path` are the null the provider itself sends on two deprecated fields

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/artifacts

hello-15 + PR C (#383) closed BOTH deferrals this family had. The first, at classification: canon's `Artifact` carried no job link and every Buildkite artifact route is job-scoped - closed by `Artifact.job`. The second, found by the serving wave on 2026-09-07: `sha1sum` is REQUIRED on the `Artifact` schema and EVERY route returning one `$ref`s it, so a row without bytes served nothing, list or show. PR C gave every artifact real TEXT content - a JUnit document built from the run's own TestResult rows, a size report over real blob lengths, an SBOM over the repository's real dependency manifest - with a compiler-MEASURED `size_bytes` and a SHA-1 and SHA-256 of exactly those bytes. Reads `reader.jobArtifacts()`/`reader.runArtifacts()`; `state` is `finished` because every job here completed and really uploaded, and `glob_path`/`original_path` are the null the provider itself sends on two deprecated fields

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/artifacts/{id}

the same artifact addressed by id, from `reader.jobArtifacts()`. Buildkite publishes an org-scoped and a build-scoped path to one artifact and the object carries ONE canonical `url` - the build-scoped spelling its own examples print - so a caller cannot see the same artifact claim two different canonical URLs depending on how it was reached

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/artifacts/{id}/download

the download, from `reader.artifactContent()` - the bytes PR C composed. MIRRORS THE DOCUMENT EXACTLY: 302, the URL in the `Location` header AND in a one-field JSON body, which is what the page describes (`The URL will be returned in the response body and the Location HTTP header`). The target is a SIGNED path on the service host rather than these bytes served inline: serving 200 here would break every SDK that reads the signed URL with `redirect: manual`, and pointing at buildkiteartifacts.com would send a caller out of the universe to a URL that 404s. It is the founder's 2026-09-02 ruling for GitHub's five 302 operations applied unchanged, and it is also what Buildkite's own sentence describes for a customer using their own bucket

served & verified
Next
jobs7 of 7 served & verified
GET
/v2/organizations/{org.slug}/jobs/{job.id}
served & verified
Core
GET
/v2/organizations/{org.slug}/jobs/{job.id}/env

SERVED 2026-09-12 (wave W6): the job's environment, which is the build, the job, the pipeline, the organization and the commit FLATTENED - so every key is read off the same derivation the corresponding JSON field uses (BUILDKITE_COMMIT is shapeBuild's `commit`, BUILDKITE_REPO is shapePipeline's `repository`, BUILDKITE_COMMAND is shapeJob's `command`) and a caller that reads a value twice cannot get two answers. The two AGENT keys the documented example prints - BUILDKITE_AGENT_NAME and BUILDKITE_AGENT_META_DATA_LOCAL - are deliberately ABSENT, entailed by the empty agent list this same host serves: a canonical Job records what ran and never what it ran ON, so naming an agent here would contradict the answer beside it. BUILDKITE_TAG and BUILDKITE_ARTIFACT_PATHS are the empty string the provider's own example carries, and BUILDKITE_PULL_REQUEST is the pull's number when the run's commit really belongs to one (pullsForCommit) and the documented "false" otherwise. text/plain and the `.txt` suffix serve the documented KEY=VALUE form

served & verified
Core
GET
/v2/organizations/{org.slug}/jobs/{job.id}/log
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/env

the build-scoped twin of the org-scoped row above. Buildkite publishes both spellings and serving one without the other would make the same environment reachable at one URL and a 404 at the other - the pairing rule the log and artifact routes on this host already follow

served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/log
served & verified
Core
pipeline-triggers0 of 5 served & verified
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/triggers

refuse — pipeline triggers are a public preview feature whose reads require Full Access to the pipeline (rest_api__pipeline_triggers.md: 'Required permission: Full Access to the pipeline'), which this token does not have; Buildkite answers it the 403 its own pipelines pages exemplify

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/triggers/{id}

refuse — pipeline triggers are a public preview feature whose reads require Full Access to the pipeline (rest_api__pipeline_triggers.md: 'Required permission: Full Access to the pipeline'), which this token does not have; Buildkite answers it the 403 its own pipelines pages exemplify

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/triggers/{trigger.id}/deliveries

refuse — pipeline trigger deliveries are a public preview feature whose reads require Full Access to the pipeline (rest_api__pipeline_trigger_deliveries.md states it on all three reads), which this token does not have

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/triggers/{trigger.id}/deliveries/{delivery.id}

refuse — pipeline trigger deliveries are a public preview feature whose reads require Full Access to the pipeline (rest_api__pipeline_trigger_deliveries.md states it on all three reads), which this token does not have

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/triggers/{trigger.id}/deliveries/{delivery.id}/request

refuse — pipeline trigger deliveries are a public preview feature whose reads require Full Access to the pipeline (rest_api__pipeline_trigger_deliveries.md states it on all three reads), which this token does not have

deferred
Long tail
builds4 of 4 served & verified
GET
/v2/builds
served & verified
Core
GET
/v2/organizations/{org.slug}/builds
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}
served & verified
Core
annotations3 of 3 served & verified
GET
/v2/organizations/{org.slug}/jobs/{job.id}/annotations

an annotation is uploaded by `buildkite-agent annotate` from inside a job; this canon's jobs carry a log and nothing else, so a job that never ran `annotate` truthfully answers `[]` (founder ruling 2026-09-05)

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/annotations

the build-scoped half of the same fact: no job in this build ever ran `buildkite-agent annotate`, so the build carries no annotation and `[]` is the whole truthful answer (founder ruling 2026-09-05)

served & verified
Next
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/jobs/{job.id}/annotations

the build-scoped route to the same job annotation list Buildkite also publishes org-scoped; both answer `[]` for the same reason, and serving one without the other would make the same fact reachable at one URL and a 404 at the other (founder ruling 2026-09-05)

served & verified
Next
organizations3 of 3 served & verified
GET
/v2/organizations
served & verified
Core
GET
/v2/organizations/{org.slug}
served & verified
Core
GET
/v2/organizations/{org.slug}/rate_limit

SERVED 2026-09-13 on the founder's ruling. THE REST SCOPE IS THE CALLER'S REAL WINDOW - limit and current are the budget the gateway actually enforces for whoever is asking (the anonymous rung for an anonymous caller, their key's rung for a keyed one), handed to the renderer's shape by ProviderDef.rateLimitBody so the body and the `ratelimit-*` headers of the SAME response can never disagree; conformance drives a real gateway and asserts current + remaining == limit on one call. This is the GitHub /rate_limit arrangement applied a second time - the renderer owns the shape, the gateway owns the numbers (2026-08-30). NOT EXEMPT, and that is the one difference from GitHub's twin: real GitHub documents that it excludes its own /rate_limit from the budget, Buildkite's page documents no exemption, and inventing one would be a claim about this provider we cannot support - so the request is charged like any other and the gateway simply does not DEFER that charge on this path. THE GRAPHQL SCOPE IS TRUE ABOUT A SURFACE THIS HOST DOES NOT SERVE: `current` is 0 because NOTHING HAS EVER BEEN DRAWN FROM THAT BUDGET - there is no Buildkite GraphQL API here - and `enforced` is false in the field's own documented sense, because this replica meters no complexity; `limit` is the 50,000 the vendored page's worked example publishes for the organization scope, quoted rather than enforced, and the `enforced: false` beside it is what says so on the wire. Shape from rest_api/organizations/rate_limits.md, vendored at buildkite/docs bcad57cfcdd63b3888570a04781898d1811a5be0. A PROPERTY OF THE CALLER RATHER THAN OF THE WORLD: nothing here reads the artifact, so every generation and all 127 registered pins answer this identically for the same caller and the row carries no generation gate

served & verified
Core
pipelines2 of 3 served & verified
GET
/v2/organizations/{org.slug}/pipelines
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}
served & verified
Core
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/github-webhooks

refuse — olympus-labs is not enrolled in Buildkite's expanded webhook triggers feature, and the vendored page states the consequence in its own words - `These endpoints return 404 Not Found if your organization is not enrolled in the expanded webhook triggers feature` (rest_api__pipelines.md:1437-1438) - so Buildkite's own 404 is the whole answer. The sentence served is that condition, truncated to the disjunct that holds here, which is the transcription-and-truncation rule the repository_connections class already follows. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

planned
Core
repository-connections0 of 3 served & verified
GET
/v2/organizations/{org.slug}/repository_connections

refuse — reading repository connections requires organization-administrator privileges, and the operation's own documented 403 is 'the token does not have the read_organization_repository_connections scope, or the authenticated user does not have the change_organization permission'

deferred
Long tail
GET
/v2/organizations/{org.slug}/repository_connections/{id}

refuse — reading repository connections requires organization-administrator privileges, and the operation's own documented 403 is 'the token does not have the read_organization_repository_connections scope, or the authenticated user does not have the change_organization permission'

deferred
Long tail
GET
/v2/organizations/{org.slug}/repository_connections/{id}/repositories

refuse — reading repository connections requires organization-administrator privileges, and the operation's own documented 403 is 'the token does not have the read_organization_repository_connections scope, or the authenticated user does not have the change_organization permission'

deferred
Long tail
agents1 of 2 served & verified
GET
/v2/organizations/{org.slug}/agents

Buildkite documents List agents as returning only an organization's CONNECTED AND STOPPING agents. An agent is connected because a build is in flight, and no build here is: every canonical run and job concludes success or failure, and the renderer serves all of them in Buildkite's terminal passed/failed/canceled — never running or scheduled. This canon carries no CI runner entity at all, so `[]` withholds nothing (founder ruling 2026-09-05)

served & verified
Next
GET
/v2/organizations/{org.slug}/agents/{id}

refuse — ENTAILED by the honest empty beside it: GET .../agents serves `[]` on this same host (Buildkite documents List agents as returning only CONNECTED AND STOPPING agents, and no build in this universe is in flight), so no id can name an agent and Buildkite's own not-found is the whole answer. The status and the body are BOTH the document's rather than derived - getAgent declares its own 404 against the Error envelope, and the literal `{"message":"No agent found"}` is published twice on the same resource's pause and resume operations - which makes this the one Buildkite refusal whose sentence was neither authored nor truncated. Left unclassified by the 2026-09-05 refuse wave because it arrived after that wave's three families were scoped; a refusal entailed by a served answer cannot drift (founder ruling 2026-09-06, coverage/reviews/2026-09-06-zendesk-rulings.md)

deferred
Next
audit-events0 of 2 served & verified
GET
/v2/organizations/{org.slug}/audit_events

refuse — ENTERPRISE PLAN, and this is the row whose blocker moved furthest. The shape is in hand (AuditEventPage, a fourth cursor family, vendored 2026-09-12) and canon HOLDS audit rows - reader.listAuditEvents(), 14 fields, true on the current pins - so the obvious reading was to serve it. The page forbids exactly that: docs/rest_api__organizations__audit_events.md says "The audit log is only available to Buildkite customers on the Enterprise plan, and is only accessible to Buildkite organization administrators", and both operations publish a 403 whose condition it states itself - "The organization's plan does not include audit logging, the user cannot view the audit log, or the token does not have the read_audit_events scope." Serving canon's audit rows here would assert an Enterprise plan olympus-labs does not have, which is a claim about the UNIVERSE rather than a gap in it. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.

deferred
Long tail
GET
/v2/organizations/{org.slug}/audit_events/{uuid}

refuse — the same documented 403, on the same page, for the same operation family - the by-uuid read publishes it verbatim beside the list's. NOT entailed by the row beside it, and the distinction is worth keeping: the list is REFUSED rather than empty, so it entails nothing at all, and this row is gated in its own right by the same Enterprise-plan sentence. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.

deferred
Long tail
meta2 of 2 served & verified
GET
/
served & verified
Core
GET
/v2/meta
served & verified
Core
notification-services1 of 2 served & verified
GET
/v2/organizations/{org.slug}/services

no notification service is configured in this organization - canon carries no Buildkite-scoped notification target (its Slack canon is channels and messages, and its only webhook rows are PagerDuty-scoped), so nothing here forwards build events; an empty array needs no item schema, which is what makes this row terminal despite the undocumented payload. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

served & verified
Long tail
GET
/v2/organizations/{org.slug}/services/{uuid}

refuse — ENTAILED by the empty beside it: .../services serves `[]`, so no uuid resolves - and, like the list, the refusal needs no 200 shape. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
organization-members2 of 2 served & verified
GET
/v2/organizations/{org.slug}/members

SERVED 2026-09-12 (the response-shape wave): the organization roster, from reader.listMemberships('org', org) paired with the people it names. UNBLOCKED BY DOCUMENTATION RATHER THAN BY CANON - the page lives at pages/apis/rest_api/organizations/members.md, a sub-directory the 2026-09-05 wave did not walk, and until it was vendored the evidence set asserted no field names. `id` is the USER's uuid, which the page states outright is what the member's id is, so it is the same uuid /v2/user and every embedded actor already publish for that person; `email` is the fleet's login@<org>.dev convention; `role` collapses canon's five MembershipRoleKeys into Buildkite's CLOSED admin/member enum (owner and admin administer, the rest do not) - the GitHub org-membership.role case rather than the Jira one, and the map lives in one place. `sso_mode` is OMITTED because the page says it is returned only to an organization administrator or on the caller's own membership, and this token is neither. Gated on hasMemberships() (hello-16): an older artifact has the roster but not the seats

served & verified
Long tail
GET
/v2/organizations/{org.slug}/members/{user.uuid}

SERVED 2026-09-12: the same member by the uuid the roster publishes, from the same pairing. A uuid naming nobody is Buildkite's 404, which the roster entails

served & verified
Long tail
organization-settings0 of 2 served & verified
GET
/v2/organizations/{org.slug}/api-settings

refuse — organization ADMINISTRATORS only, and the sentence is the vendored page's rather than ours: docs/rest_api__organizations__api_settings.md says in as many words that "These endpoints require a read_organization_settings or write_organization_settings access token scope, and the authenticated user must be a Buildkite organization administrator", and its own 403 row states the condition this host serves back - "The token does not have the read_organization_settings scope, or the authenticated user is not an organization administrator." The viewer this replica authenticates as holds athena's personal token and neither that scope nor that role, and canon holds no organization-settings entity either - so even an administrator's token would have nothing to report here. The entitlement-gate class this fence already had ten rows of, with a DOCUMENTED sentence rather than an authored one. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipeline-settings

refuse — organization ADMINISTRATORS only, and this page names the permission outright: docs/rest_api__organizations__pipeline_settings.md says "Both read and write operations require organization administrator privileges (the change_organization permission)" - VERBATIM the sentence the three repository-connection refusals on this fence already serve - and its own 403 row states the condition this host serves back, "The token does not have the read_organization_settings scope, or the authenticated user does not have organization administrator privileges." The viewer this replica authenticates as holds athena's personal token and neither that scope nor that role, and canon holds no organization-settings entity either - so even an administrator's token would have nothing to report here. The entitlement-gate class this fence already had ten rows of, with a DOCUMENTED sentence rather than an authored one. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.

deferred
Long tail
pipeline-schedules2 of 2 served & verified
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/schedules

SERVED 2026-09-12 (wave W6): the pipeline's schedules - reader.listWorkflowSchedules(repo) narrowed to this workflow. All sixteen required fields have sources: label/message from the canonical name and description, cronline a lossless re-encoding of canon's timetable triple (per_hour, hours_of_day, days_of_week) in the crontab syntax the document itself names, branch resolved to the ref this repository really has, commit the document's own HEAD default because canon's schedule names a branch and no revision, created_by the person canon records, pipeline the workflow it starts, env `{}` because the schedule sets none, enabled true because it started real runs (runsForSchedule), and failed_message/failed_at null because no run of it records a scheduling failure. next_build_at is computed from the cronline and the ARTIFACT'S ANCHOR, never Date.now(), so a pinned host answers the same instant forever. The SAME canonical row is a Bitbucket pipeline_schedule and a CircleCI schedule trigger, and the builds it started are the runs GET .../builds already lists. Gated on hasCiConfig(): workflow_schedule is hello-15 canon, so an older artifact answers the generation gap rather than `[]`, which would say a pipeline that really runs nightly runs on demand only

served & verified
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/schedules/{id}

the same schedule addressed by id, from the same accessor. A uuid that names no schedule on this pipeline is the ordinary not-found

served & verified
Long tail
pipeline-templates0 of 2 served & verified
GET
/v2/organizations/{org.slug}/pipeline-templates

refuse — pipeline templates are an Enterprise plan feature - the vendored page opens 'Enterprise plan feature' and 'Pipeline templates are only available on an Enterprise plan' - so olympus-labs is not entitled to this API at all, and Buildkite answers an unentitled caller its own 403 rather than an empty list

deferred
Long tail
GET
/v2/organizations/{org.slug}/pipeline-templates/{uuid}

refuse — pipeline templates are an Enterprise plan feature - the vendored page opens 'Enterprise plan feature' and 'Pipeline templates are only available on an Enterprise plan' - so olympus-labs is not entitled to this API at all, and Buildkite answers an unentitled caller its own 403 rather than an empty list

deferred
Long tail
portals1 of 2 served & verified
GET
/v2/organizations/{org.slug}/portals

no portal is defined in this organization - a portal is a stored GraphQL operation published at an authenticated URL, this replica serves no Buildkite GraphQL surface at all and canon carries no stored query; Buildkite's own answer for an organization with none is `[]` at 200. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

served & verified
Long tail
GET
/v2/organizations/{org.slug}/portals/{portal.slug}

refuse — ENTAILED by the empty beside it: .../portals serves `[]`, so no slug resolves and Buildkite's own not-found is the whole answer. The body is the CAPTURED generic `{"message":"Not Found"}`: the portals page publishes error tables only on its writes, so authoring a sentence would be a claim this repository cannot check. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
rules1 of 2 served & verified
GET
/v2/organizations/{org.slug}/rules

no rule is defined in this organization - a Buildkite rule grants one pipeline or cluster an action against another (`pipeline.trigger_build.pipeline`), every rule object names a source and target UUID, and this universe has no cluster and no trigger relationship between its three pipelines; Buildkite's own answer for an organization with none is `[]` at 200. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

served & verified
Long tail
GET
/v2/organizations/{org.slug}/rules/{uuid}

refuse — ENTAILED by the empty beside it: .../rules serves `[]`, so no uuid resolves. The body is the CAPTURED generic `{"message":"Not Found"}` - the rules page publishes error tables only on its writes. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
step-uploads1 of 2 served & verified
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/step-uploads

no build in this universe has a step upload - a step upload is a pipeline configuration posted by `buildkite-agent pipeline upload` from inside a job, and this canon's pipelines are STATIC documents (ci_config_document, source and compiled) whose jobs carry a log and nothing else; it is the annotations ruling (2026-09-05) one family along. THE AGE BRANCH IS RULED TOO, because the same operation publishes a second answer: a build past its thirty-day retention lifetime is Buildkite's documented `410 Gone` carrying the document's own wording, and the age is measured from the BUILD'S OWN FINISHING INSTANT against the ARTIFACT'S ANCHOR - this universe's now - never against the wall clock, which would make a pinned host's answer change every day. A ninety-day window therefore partitions this organization's builds: the newest month answers `{items: [], links: ...}` and everything older answers the 410, and both are the provider's own answer to the same request. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

served & verified
Long tail
GET
/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/step-uploads/{uuid}

refuse — ENTAILED by the empty beside it: the build's step-upload page is `{items: [], links: ...}`, so no uuid resolves and Buildkite's own not-found is the answer. Its 410 for an expired build is the same operation's other documented refusal and OUTRANKS this one - the guard is about the BUILD, so a caller asking for an upload on a build Buildkite has aged out is told that rather than that no upload matched. The body is the CAPTURED generic `{"message":"Not Found"}` and not an authored sentence: the page publishes a 404 DESCRIPTION ("No step upload matches the given UUID for this build") rather than a body literal, so asserting one would be a claim about a Buildkite string this repository cannot check. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal

deferred
Long tail
access-token1 of 1 served & verified
GET
/v2/access-token

SERVED 2026-09-12 (wave W6): the token the caller authenticated with - reader.getPersonByLogin("athena") + credentialsForPerson(id, 'personal-token'), the viewer's own ACTIVE row. This replica accepts any credential or none (the standing posture), so what this describes is not the bytes the caller sent but the token the VIEWER really holds in the artifact - the same substitution GET /v2/user makes for the account, and the only honest reading available. uuid/scopes/description/created_at/expires_at/user all read off columns; the canon TokenScope -> Buildkite scope spelling is the per-renderer mapping renderer-github/src/render-credentials.ts:198 established, total over the canon enum and explicit about the six scopes that name resources the Pipelines API does not have (repository, issue, package), which are DROPPED rather than invented. created_at is rendered ISO rather than the document example's "2025-07-16 06:07:42 UTC", the fleet decision render.ts `iso` already records. GATED on hasCredentials(): person_credential is hello-16 canon, so an older artifact answers the generation gap naming hello-16 rather than a 404 that would read as 'no such endpoint'

served & verified
Next
emojis0 of 1 served & verified
GET
/v2/organizations/{org.slug}/emojis

generateheld · data-licensing — static provider catalog, and NOT `derive`: `derive` is defined in this file as computed from artifact rows that already exist, and a vendor catalog is not an artifact row. LICENCE: NO GRANT. The catalogue is published in exactly one place, github.com/buildkite/emojis (805 Buildkite entries in img-buildkite-64.json plus 1,900 Apple-derived Unicode entries in img-apple-64.json, 2,705 total); that repository carries no LICENSE, LICENSE.md, LICENSE.txt or COPYING at its default branch, GitHub's own licence detector reports license: null, neither its package.json nor the published npm package @buildkite/emojis@2.0.1009 declares a license field, and its README's entire `## License` section reads "Each logo is owned by their respective creators" - a DISCLAIMER of ownership naming third parties as the owners, which is not a grant and leaves none to fall back on. Every `url` value in the vendored example points at buildkiteassets.com, so mirroring the set would also hand our callers somebody else's CDN, the objection already recorded on /v2/meta's webhook_ips. Serving only the six entries the MIT-licensed docs/rest_api__emojis.md happens to quote was considered and rejected: six of 2,705 presented as "the organization's emojis" is a false claim about completeness, which is worse than not answering. THE VERDICT IS THE 2026-09-05 ONE, UNCHANGED - a static `derive` ruling conditioned on a provenance check that then FAILED - and the only new thing is that the row now carries the LABEL for it rather than no entry at all, verbatim the classification github's own GET /emojis has held since 2026-09-09. THE FOUNDER'S OPEN QUESTION IS UNTOUCHED: whether to re-rule this `refuse` with the licence as the stated reason. CLASSIFIED 2026-09-13 (the unjudged-47 pass); no number moves, because a held row stays in the badge denominator and out of its numerator

deferred
Next
user1 of 1 served & verified
GET
/v2/user

SERVED 2026-09-12 (wave W6): the account that owns the token - the SAME person gh. answers GET /user with and gl. answers GET /api/v4/user with, so a caller reading its own identity from three of our hosts gets one answer (invariant #5). shapeUser already emits the six required members for every job creator; created_at is reader.personFirstSeen(), the fleet's own 'when this person came into being here' and the derivation Sentry's dateJoined makes for the same people, and avatar_url resolves on this host's own /avatars/{uuid} route

served & verified
Next

03 / What's simulated

One data set, rendered in Buildkite's dialect.

What another provider has to agree with, and the test that makes it

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.

05 / Pinned snapshots

Frozen universes, on their own hostnames.

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.

PinHostUniverse generationBuildkite API versionRepository files
buildkite-v2-g10buildkite-v2-g10.snap.sandboxapis.devgeneration 10v2served
buildkite-v2-g11buildkite-v2-g11.snap.sandboxapis.devgeneration 11v2served
buildkite-v2-g8buildkite-v2-g8.snap.sandboxapis.devgeneration 8v2served
buildkite-v2-g9buildkite-v2-g9.snap.sandboxapis.devgeneration 9v2served

Need an endpoint that is not served yet?

Every row Buildkite'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.

Request coverage