API overview
REST, MCP and the CLI over one API: what they cover and the rules they share.
V12's API has three ways in: REST for scripts and services, an MCP server for AI assistants, and the v12 command-line tool. All three cover findings, runs and their guest links, context documents, repositories, Steers and members. REST offers all 35 operations. MCP offers 32 of them as tools, plus send_feedback, which has no REST endpoint: 33 tools in all.
API quickstart
Create an API key and make a REST call.
Connect an MCP client
Connect an AI assistant to https://v12.sh/api/mcp.
CLI
The v12 command, for terminals and CI.
Recipes
Triage findings and start runs within a price limit.
REST reference
Every endpoint, scope and error.
Authentication
Send Authorization: Bearer <token> on every request, with an API key or OAuth access token. See Authentication and permissions.
Addresses
A token acts on one workspace, an organization or your personal workspace. Every REST endpoint except GET /api/v2/me sits under https://v12.sh/, where <org> is that workspace's slug. Short paths in these docs, such as POST /runs, omit that prefix.
- Get the slug from
organization.sluginGET https://v12.sh/, open to any valid token, or the app's address bar:api/ v2/ me v12.sh/<org>/…. - Another workspace's slug gets 404
not_found, even for an organization you belong to. So does a path without/orgs/<org>. - A slug from before the workspace was renamed still works.
- MCP takes no slug: a connection acts on its token's workspace.
Address things as the app shows them:
- Finding: key
F-42or number42. In a JSON body or an MCP call the number may be an integer,{"finding": 42}. - Run: number in the workspace,
12. - Repository: slug or full name
owner/name,acme/api. A repository V12 hosts goes by its slug,v12/api. - Assignee:
me,none, or a member's email or username. - Steer: slug,
auth. - Context document: id, a UUID.
- Guest link: number within its run, from 1.
PATCH /runs/12/links/2changes link 2 of run 12.
Responses use the same addresses, and findings, runs, repositories and Steers carry a webUrl that opens them in the app.
Positions
Operations that read code state take an optional at, default when left out:
at | Position |
|---|---|
default | Head of the default branch V12 uses for the repository |
branch:<name> | Head of a branch |
pr:<number> | Head of a pull request |
commit:<sha> | A commit, by its full 40-character SHA |
Responses report the evaluated commit in position.at, pinned to its SHA: branch:main@<sha>. Send it back to stay on that commit after the branch moves.
An unresolvable position gets 409 position_unavailable, with details.reason such as unknown-branch or unknown-pull-request.
Pagination
Lists, except refs, return items and nextCursor. For the next page, repeat the request with the same filters and cursor set to nextCursor, until it is null. A cursor sent with different filters gets 400 invalid_request.
Where a list takes limit, it sets the page size: 1 to 100, default 50.
The refs list, GET /repositories/ (MCP list_refs), is the exception. It returns branches, pullRequests and tags arrays, not items, and takes no cursor: nextCursor is always null. Its limit is 1 to 200, default 100, and applies to each array; truncated says which may hold more, so narrow the list with q.
Safe retries
Run starts and comments take an idempotency key, Idempotency-Key in REST or requestId in MCP. A retry with the same key and request returns the original result. A replayed start adds replayed: true, shows the run as it was when it started, and doesn't count toward the run-start limit. Reusing a key for a different request gets 409 idempotency_key_reused. POST /runs needs a key or an estimate's quote. The CLI sends a key for you.
Finding changes and repository adds are safe to repeat without a key: they leave findings already in the requested state unchanged and list them in skipped, and return an existing repository with added: false.
Errors and limits
REST errors and MCP errors have the same JSON body, with a requestId matching the X-Request-Id header. See the error reference. The exception is an MCP protocol error, such as an unknown tool name or malformed JSON-RPC: it is a JSON-RPC error object that sets X-Request-Id but has no requestId. See Results and errors.
Request budgets appear in X-RateLimit-* headers on the responses to requests that were charged. Preflights, the OpenAPI document and the retired /api/v1 carry none. See Rate limits.
/api/v1 is retired and answers 410 api_version_retired. Use /api/v2 and CLI 1.0 or later; v12 repos create and v12 repos push need 1.2.