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

CircleCI API quickstart

Builds that really ran.

A CircleCI API v2-compatible host for the simulated org. Point mcp-server-circleci or the CircleCI CLI at it with a base-URL swap and read the CI story: pipelines over the commits that were built, the workflows inside them, the jobs inside those, and the people who triggered them. Every SHA fetches on the GitHub host, every job matches a GitHub Actions check run, and the verdicts agree — because all four hosts are rendering one canonical build.

01Point your client at circleci.
02Walk a build
03Know what is not here

01 / Base URL

Change the base URL. Nothing else.

Requests go to https://circleci.sandboxapis.dev. Paths, envelopes, statuses and pagination mirror circleci.com/api/v2. Any credential is accepted, and so is none — send a Circle-Token header if your client insists on one.

shell
# mcp-server-circleci
export CIRCLECI_BASE_URL="https://circleci.sandboxapis.dev"

# the CircleCI CLI
circleci --host "https://circleci.sandboxapis.dev" ...

# or plain HTTP
curl -H "Circle-Token: anything" "https://circleci.sandboxapis.dev/api/v2/me"

Both spellings of a project slug work — the unescaped gh/olympus-labs/parthenon every client sends, and the percent-encoded gh%2Folympus-labs%2Fparthenon a client that treats the slug as one parameter sends. They return the same bytes.

02 / A build

Commit → pipeline → workflow → job.

A pipeline here is one commit that CI ran on. Its workflows are the runs on that commit — ci, lint, release — and its jobs are their jobs. That is not a re-labelling: the simulated universe records a CI run per workflow per commit, which is exactly the shape CircleCI describes.

shell
# the project
curl "https://circleci.sandboxapis.dev/api/v2/project/gh/olympus-labs/parthenon"

# its pipelines, newest first (paged with next_page_token)
curl "https://circleci.sandboxapis.dev/api/v2/project/gh/olympus-labs/parthenon/pipeline"

# the workflows in one pipeline
curl "https://circleci.sandboxapis.dev/api/v2/pipeline/<pipeline-id>/workflow"

# the jobs in one workflow, then one job in full
curl "https://circleci.sandboxapis.dev/api/v2/workflow/<workflow-id>/job"
curl "https://circleci.sandboxapis.dev/api/v2/jobs/<job-id>"

The pipeline's vcs.revision is a real 40-character SHA. Take it to gh.sandboxapis.dev and the commit is there, with the same author, the same files and the same Actions run carrying the same job names and the same pass/fail verdicts. The project's vcs_info.vcs_url is that repository's own address on the GitHub host, so the link out of this host lands somewhere real.

03 / Boundaries

What this host does not answer.

60 of 61 read endpoints are served and verified — every read operation CircleCI's own API description declares, bar one. That includes the aggregate insights summaries, branch and workflow metrics and job time-series, and the whole deploy family: components, the environments they ship to and each component's versions. Nothing is stubbed and nothing returns a plausible-looking placeholder — a field this universe cannot answer is left out where the document makes it optional, and said so where it does not. See the coverage table for the row-by-row picture.

One more is finished without being served, and it reads differently on purpose: a usage-export job by id. It names an object that cannot exist because nothing ever created one — the job is made by a POST this universe refuses — so the true answer is CircleCI's own 404 rather than anything a replica could invent. It carries x-sandboxapis-refusal with the manifest row you can look the ruling up under, and it uses CircleCI's second error envelope ({ error: … }), which is the shape its own document declares for that family and the only place on this host you will see it.

Every write is refused with CircleCI's own 403 Permission denied. envelope: the universe is read-only, so triggering a pipeline, cancelling a job or approving a hold all answer the same shape a real token without permission would get.

04 / Known deviations

Three places this replica is not the mirror.

A user's avatar_url is null. CircleCI describes it as the URL of the user's avatar on the VCS, its own API serves no avatar bytes, and the field is declared nullable. Pointing it at a URL this universe cannot serve would be worse than saying nothing.

Environment-variable values are masked, and there is no secret behind the mask. A project environment variable reads back as xxxx plus four characters, which is exactly what the real API returns and what the CircleCI website shows. Those four characters are derived from the project and the environment; this universe stores no credential anywhere, so there is nothing to reveal and nothing that would authenticate against anything. A context's variables publish no value at all, because CircleCI's own schema declares none. One field keeps upstream's odd wire format on purpose: a project variable's created-at is the Clojure reader literal #joda/inst … that CircleCI's own documentation exemplifies, not the RFC-3339 string every other timestamp on this host uses.

Error bodies are sent as application/json. Real circleci.com sends its JSON error bodies with content-type: text/plain;charset=utf-8; its own published OpenAPI declares application/json for every one of them. The document is the shape of record here, and the divergence is recorded in the coverage notes rather than left to be discovered.

05 / Pinning

A frozen universe for CI.

For a build that cannot drift, use the pinned host https://circleci-v2-g11.snap.sandboxapis.dev instead of the live one. The build walk in section 02 answers there in full: the pipelines, workflows, jobs and people this renderer reads have been in the simulated universe since its second generation, so no pin is too old for any of them.

The CI configuration rows are the exception, and the reason is the one above — they read canon that arrives in hello-15. On a pin frozen before that, a schedule, a checkout key, a flaky-test report or a policy decision answers the generation-gap 404 that names hello-15, permanently, because a pin's bytes never move. circleci-v2-g11 carries that generation, so those rows answer there — and it was registered under its own name rather than by moving an older pin, which is why nothing you have already tested against changed. How pinning works →