REST reference
Every /api/v2 endpoint: method, path, scopes, parameters, body fields and errors.
The REST API lives at https://v12.sh/api/v2 and acts on the workspace your token reads; Authentication and permissions covers API keys, OAuth and scopes. The OpenAPI 3.1 document is public, needs no token and may be cached for five minutes: https://v12.sh/api/v2/openapi.json.
Requests
{repository}in a path is two segments,owner/name:/repositories/acme/api/findings.repositoryandatgo in the query string on everyGETthat takes them, and on bothPATCHoperations.- Query lists are comma-separated:
status=open,fixed. - Other
POSTandPATCHfields go in a JSON body withContent-Type: application/json; any other type returns415. - Bodies are limited to 1 MiB; a larger one returns
413.
Errors
Every error response has the same body. details is optional, and requestId matches the X-Request-Id response header; include it when you ask for help.
{
"error": {
"code": "invalid_request",
"message": "The request is invalid.",
"details": { "issues": [{ "path": "limit", "message": "Too big: expected number to be <=100" }] }
},
"requestId": "e3fbc6dc-1c5e-4b2d-b998-e820319d6cf7"
}| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | The input, a header, the cursor or the quote is invalid. Validation errors list details.issues. |
repository_required | 400 | The finding is in several repositories. Pick one from details.repositories. |
unauthenticated | 401 | The Authorization header is missing or its token is invalid. See WWW-Authenticate. |
insufficient_credits | 402 | The workspace lacks the credit for the run. |
spend_cap_reached | 402 | The workspace's monthly spending limit is reached. |
insufficient_scope | 403 | The token lacks a scope listed in details.required. |
forbidden | 403 | The token's user has a role that does not allow the action. |
not_found | 404 | No such resource in the token's workspace, or the finding is not at that position. |
method_not_allowed | 405 | The path does not take this method. See Allow. |
position_unavailable | 409 | V12 cannot resolve the position. details.reason says why. |
decision_active | 409 | A won't-fix or risk-accepted decision blocks this status. details.findings lists the findings. |
conflict | 409 | The request conflicts with the current state, such as cancelling a completed run. |
price_exceeds_max | 409 | The price, details.priceCents, is above maxPriceCents. |
quote_stale | 409 | The quote expired or what it priced changed. Estimate again. |
change_refused | 409 | The change cannot be reviewed. details.reason is thin, identical, large or detached. |
nothing_to_review | 409 | Nothing in scope has files to review at this position. |
repository_not_runnable | 409 | Access to the repository was revoked, or it is not on GitHub. See details.reason. |
idempotency_key_reused | 409 | The Idempotency-Key was used for a different request. |
api_version_retired | 410 | /api/v1 is retired. Use /api/v2. |
payload_too_large | 413 | The body is larger than details.limitBytes. |
unsupported_media_type | 415 | The body is not application/json. |
rate_limited | 429 | A rate limit is used up. Wait Retry-After seconds; details.bucket names the limit. |
internal_error | 500 | An unexpected failure on V12's side. |
upstream_error | 502 | A GitHub request V12 needed failed. |
platform_unavailable | 503 | V12's findings service is unavailable, or is rate limiting V12. Wait Retry-After seconds when it is present. |
repository_access_unavailable | 503 | V12 could not confirm its GitHub access to the repository. |
rate_limit_unavailable | 503 | Rate limiting is down, so V12 refuses changes and run estimates. Retry after 30 seconds. |
public_api_disabled | 503 | The API is turned off for now; the web app still works. |
platform_unavailable (503) means V12's findings service answered 429, 502, 503 or 504, or did not answer. When the service said when to retry, the response carries Retry-After and the same seconds in details.retryAfterSeconds. A 500 from that service reaches you as internal_error (500).
Me
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| Get the token identity | GET /me | user:read | get_me |
Get the token identity
GET /me · operationId getMe · Scopes: user:read · MCP tool get_me
The user, organization and scopes behind this credential, plus the organization credit balance when the user may view billing.
Returns 200 with user, organization, token, credits. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Members
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| List organization members | GET /members | user:read | list_members |
List organization members
GET /members · operationId listMembers · Scopes: user:read · MCP tool list_members
Members of the token organization, sorted by username, then id. Use a member id, email or username to assign findings.
Returns 200 with nextCursor, items. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Focuses
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| List focuses | GET /focuses | repos:read | list_focuses |
| Get a focus | GET /focuses/{focus} | repos:read | get_focus |
List focuses
GET /focuses · operationId listFocuses · Scopes: repos:read · MCP tool list_focuses
Organization-wide focus documents that steer runs and Autopilot rules.
Returns 200 with nextCursor, items. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Get a focus
GET /focuses/{focus} · operationId getFocus · Scopes: repos:read · MCP tool get_focus
One focus by slug, including the instructions its runs follow.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
focus | path | string | yes | Focus slug; 1–200 characters |
Returns 200 with slug, title, emoji, revision, webUrl, instructions. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Repositories
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| List repositories | GET /repositories | repos:read | list_repositories |
| Get a repository | GET /repositories/{owner}/{repo} | repos:read | REST only |
| List branches, pull requests and tags | GET /repositories/{owner}/{repo}/refs | repos:read | list_refs |
List repositories
GET /repositories · operationId listRepositories · Scopes: repos:read · MCP tool list_repositories
Repositories registered in the organization, sorted by full name, with their GitHub access and mirror state.
Returns 200 with nextCursor, items. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Get a repository
GET /repositories/{owner}/{repo} · operationId getRepository · Scopes: repos:read · REST only
One registered repository by "owner/name".
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
owner | path | string | yes | Repository owner |
repo | path | string | yes | Repository name |
Returns 200 with id, fullName, defaultBranch, access, mirror, webUrl. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
List branches, pull requests and tags
GET /repositories/{owner}/{repo}/refs · operationId listRefs · Scopes: repos:read · MCP tool list_refs
Refs V12 has mirrored for a repository, with whether each head was reviewed. Not paginated; narrow the list with q.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
owner | path | string | yes | Repository owner |
repo | path | string | yes | Repository name |
q | query | string | no | Case-insensitive name filter; at most 200 characters |
limit | query | integer | no | 1–200; default 100 |
Returns 200 with defaultBranch, branches, pullRequests, tags, asOf, nextCursor. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Findings
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| List a repository's findings at a position | GET /repositories/{owner}/{repo}/findings | findings:read | list_repository_findings |
| List organization findings | GET /findings | findings:read | list_findings |
| Change findings | PATCH /findings | findings:write | update_findings |
| Get a finding | GET /findings/{finding} | findings:read | get_finding |
| Change a finding | PATCH /findings/{finding} | findings:write | REST only |
| Comment on a finding | POST /findings/{finding}/comments | findings:write | comment_on_finding |
List a repository's findings at a position
GET /repositories/{owner}/{repo}/findings · operationId listRepositoryFindings · Scopes: findings:read · MCP tool list_repository_findings
The repository inbox at one commit: every finding with its code state there. Without at, the default branch head. The response pins the evaluated commit in position.at; later pages stay on it.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
owner | path | string | yes | Repository owner |
repo | path | string | yes | Repository name |
at | query | string | no | Repository position: "default", "branch:<name>[@<sha>]", "pr:<number>[@<sha>]" or "commit:<sha>". Omitted means "default"; at most 512 characters |
status | query | ("open" | "investigating" | "fixed" | "wont-fix" | "false-positive" | "duplicate")[] | no | at most 6 items |
severity | query | ("info" | "low" | "medium" | "high" | "critical")[] | no | at most 5 items |
assignee | query | string | no | "me", "none", a member id, a member email, or a member username. A username several members share matches all of them; 1–320 characters |
focus | query | string | no | Focus slug; 1–200 characters |
dismissed | query | boolean | no | true lists only dismissed findings; false (default) hides them; default false |
limit | query | integer | no | 1–100; default 50 |
cursor | query | string | no | `nextCursor` from the previous page; 1–2048 characters |
Returns 200 with nextCursor, items, repository, position, reviewCoverage, counts, total, asOf. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
List organization findings
GET /findings · operationId listFindings · Scopes: findings:read · MCP tool list_findings
Findings across every registered repository at its default branch head, newest first.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
status | query | ("open" | "investigating" | "fixed" | "wont-fix" | "false-positive" | "duplicate")[] | no | at most 6 items |
severity | query | ("info" | "low" | "medium" | "high" | "critical")[] | no | at most 5 items |
assignee | query | string | no | "me", "none", a member id, a member email, or a member username. A username several members share matches all of them; 1–320 characters |
focus | query | string | no | Focus slug; 1–200 characters |
dismissed | query | boolean | no | true lists only dismissed findings; false (default) hides them; default false |
limit | query | integer | no | 1–100; default 50 |
cursor | query | string | no | `nextCursor` from the previous page; 1–2048 characters |
Returns 200 with nextCursor, items, counts, total, asOf, unavailableRepositories. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Change findings
PATCH /findings · operationId updateFindings · Scopes: findings:write · MCP tool update_findings
Apply one change to up to 100 findings: status (with reason), severity, assignee, or dismissal (with reason). open, fixed and false-positive are recorded per commit, at the default branch head unless repository/at say otherwise. Findings already in the requested state are skipped.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
repository | query | string | integer | no | Needed with `at` when a finding spans several repositories |
at | query | string | no | Repository position: "default", "branch:<name>[@<sha>]", "pr:<number>[@<sha>]" or "commit:<sha>". Omitted means "default"; at most 512 characters |
| Body field | Type | Required | Notes |
|---|---|---|---|
findings | string[] | yes | 1–100 items |
status | "open" | "investigating" | "fixed" | "wont-fix" | "false-positive" | no | open, fixed and false-positive apply per commit; the others to the finding |
reason | string | no | Required with status and with dismissed: true; 1–5000 characters |
severity | "info" | "low" | "medium" | "high" | "critical" | no | |
assignee | integer | string | null | no | Member id, member email, member username, "me", or null to unassign. A username several members share is refused; use the id or email |
dismissed | boolean | no |
Returns 200 with findings, asOf. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Get a finding
GET /findings/{finding} · operationId getFinding · Scopes: findings:read · MCP tool get_finding
One finding with its reports, analysis, evidence, activity and comments. With at, code state and status are read at that commit.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
finding | path | string | yes | Finding id, or its display key such as "F-42"; 1–255 characters |
repository | query | string | integer | no | Repository: "owner/name" or its numeric id |
at | query | string | no | Repository position: "default", "branch:<name>[@<sha>]", "pr:<number>[@<sha>]" or "commit:<sha>". Omitted means "default"; at most 512 characters |
Returns 200 with id, key, title, severity, status, investigating, decision, duplicateOf, dismissed, assignee, focus, firstObservedAt, reportCount, sources, runs, repositories, webUrl, description, reports, evidence, activity, related, asOf. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Change a finding
PATCH /findings/{finding} · operationId updateFinding · Scopes: findings:write · REST only
Apply one change to a finding: status (with reason), severity, assignee, or dismissal. A change that would not alter the finding is skipped.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
finding | path | string | yes | Finding id, or its display key such as "F-42"; 1–255 characters |
repository | query | string | integer | no | Needed with `at` when a finding spans several repositories |
at | query | string | no | Repository position: "default", "branch:<name>[@<sha>]", "pr:<number>[@<sha>]" or "commit:<sha>". Omitted means "default"; at most 512 characters |
| Body field | Type | Required | Notes |
|---|---|---|---|
status | "open" | "investigating" | "fixed" | "wont-fix" | "false-positive" | no | open, fixed and false-positive apply per commit; the others to the finding |
reason | string | no | Required with status and with dismissed: true; 1–5000 characters |
severity | "info" | "low" | "medium" | "high" | "critical" | no | |
assignee | integer | string | null | no | Member id, member email, member username, "me", or null to unassign. A username several members share is refused; use the id or email |
dismissed | boolean | no |
Returns 200 with findings, asOf. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Comment on a finding
POST /findings/{finding}/comments · operationId commentOnFinding · Scopes: findings:write · MCP tool comment_on_finding
Add a comment, or a reply to one, to a finding.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
finding | path | string | yes | Finding id, or its display key such as "F-42"; 1–255 characters |
Idempotency-Key | header | string | no | Repeat a request safely: the same key and body return the original result; 1–255 characters |
| Body field | Type | Required | Notes |
|---|---|---|---|
body | string | yes | 1–20000 characters |
replyTo | string | no | Comment id to reply to; 1–255 characters |
Returns 201 with comment. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Runs
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| List runs | GET /runs | runs:read | list_runs |
| Start a run | POST /runs | runs:write | start_run |
| Get a run | GET /runs/{run} | runs:read | get_run |
| List a run's findings | GET /runs/{run}/findings | runs:read, findings:read | list_run_findings |
| Estimate a run | POST /runs/estimate | runs:write | estimate_run |
| Cancel a run | POST /runs/{run}/cancel | runs:manage | cancel_run |
List runs
GET /runs · operationId listRuns · Scopes: runs:read · MCP tool list_runs
Runs of the organization, newest first.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
repository | query | string | integer | no | Repository: "owner/name" or its numeric id |
focus | query | string | no | Focus slug; 1–200 characters |
state | query | ("queued" | "in_progress" | "compilation_in_progress" | "compilation_completed" | "compilation_failed" | "analysis_in_progress" | "analysis_completed" | "analysis_failed" | "complete" | "failed" | "cancelled")[] | no | at most 11 items |
limit | query | integer | no | 1–100; default 50 |
cursor | query | string | no | `nextCursor` from the previous page; 1–2048 characters |
Returns 200 with nextCursor, items. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Start a run
POST /runs · operationId startRun · Scopes: runs:write · MCP tool start_run
Start a paid run. Send the quote from estimate_run and a maxPriceCents the user approved; the run is refused when its price exceeds it. A repeated Idempotency-Key (REST) or requestId (MCP) returns the run it started.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
Idempotency-Key | header | string | no | Repeat a request safely: the same key and body return the original result; 1–255 characters |
| Body field | Type | Required | Notes |
|---|---|---|---|
repository | string | integer | yes | Repository: "owner/name" or its numeric id |
at | string | no | Full review position (default "default"). Mutually exclusive with change; at most 512 characters |
change | object | no | Review a change: { pr } or { branch, since? } |
focuses | string[] | no | Focuses whose documents steer the run; at most 5 items |
scope | string[] | no | at most 200 items |
repositories | object[] | no | Extra repositories reviewed alongside; full reviews only; at most 8 items |
steering | string | no | Instructions for the reviewers; at most 65536 characters |
documents | string[] | no | |
maxPriceCents | integer | yes | Highest price, in cents, the caller accepts; at least 0 |
quote | string | no | 1–4096 characters |
Returns 200, 201 with run, priceCents. Errors: 400, 401, 402, 403, 404, 409, 413, 415, 429, 503 (Errors).
Get a run
GET /runs/{run} · operationId getRun · Scopes: runs:read · MCP tool get_run
One run with its stages, progress, cost and finding import state. A failed or partial import says why in import.reason and on the inbox stage.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
run | path | integer | string | yes |
Returns 200 with id, title, state, phase, terminal, createdAt, startedAt, endedAt, repository, ref, sha, extraRepositories, retryOf, trigger, costCents, findings, error, webUrl, kind, cancellationRequested, focuses, change, repositories, stage, stages, import, publicUrl, pullRequestCommentUrl. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
List a run's findings
GET /runs/{run}/findings · operationId listRunFindings · Scopes: runs:read, findings:read · MCP tool list_run_findings
Findings a completed run added to the inbox. Empty until import.state is "imported" or "partial"; a partial import lists only the findings that reached the inbox, and import.reason says why the rest did not.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
run | path | integer | string | yes | |
limit | query | integer | no | 1–100; default 50 |
cursor | query | string | no | `nextCursor` from the previous page; 1–2048 characters |
Returns 200 with nextCursor, items, import, asOf. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Estimate a run
POST /runs/estimate · operationId estimateRun · Scopes: runs:write · MCP tool estimate_run
Price a full review (at) or a change review (change) without starting it. Returns a quote to pass to start_run within 15 minutes.
| Body field | Type | Required | Notes |
|---|---|---|---|
repository | string | integer | yes | Repository: "owner/name" or its numeric id |
at | string | no | Full review position (default "default"). Mutually exclusive with change; at most 512 characters |
change | object | no | Review a change: { pr } or { branch, since? } |
focuses | string[] | no | Focuses whose documents steer the run; at most 5 items |
scope | string[] | no | at most 200 items |
repositories | object[] | no | Extra repositories reviewed alongside; full reviews only; at most 8 items |
steering | string | no | Instructions for the reviewers; at most 65536 characters |
documents | string[] | no |
Returns 200 with kind, priceCents, balanceCents, targets, focusRevisions, change, quote, expiresAt. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Cancel a run
POST /runs/{run}/cancel · operationId cancelRun · Scopes: runs:manage · MCP tool cancel_run
Cancel a queued or running run. A run that has already started stops at its next checkpoint (result "cancelling").
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
run | path | integer | string | yes |
Returns 200 with run, result. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Documents
| Operation | Endpoint | Scopes | MCP tool |
|---|---|---|---|
| Request a document upload slot | POST /documents/uploads | runs:write | request_document_upload |
| Create a context document | POST /documents | runs:write | create_document |
| List context documents | GET /documents | runs:read | list_documents |
| Get a context document | GET /documents/{document} | runs:read | REST only |
| Archive a context document | DELETE /documents/{document} | runs:write | archive_document |
Request a document upload slot
POST /documents/uploads · operationId requestDocumentUpload · Scopes: runs:write · MCP tool request_document_upload
Get a URL to PUT a file to (up to 50 MiB, valid 10 minutes), then call create_document with the slot id.
| Body field | Type | Required | Notes |
|---|---|---|---|
filename | string | yes | 1–255 characters |
contentType | string | yes | MIME type of the file; 1–255 characters |
Returns 201 with upload. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
Create a context document
POST /documents · operationId createDocument · Scopes: runs:write · MCP tool create_document
Add a context document runs can read: an uploaded file ({ upload }) or a text note ({ note }). Identical content returns the existing document.
| Body field | Type | Required | Notes |
|---|---|---|---|
upload | object | no | |
note | object | no |
Returns 201 with document. Errors: 400, 401, 403, 404, 409, 413, 415, 429, 503 (Errors).
List context documents
GET /documents · operationId listDocuments · Scopes: runs:read · MCP tool list_documents
Context documents of the organization, optionally those used with a repository.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
repository | query | string | integer | no | Repository: "owner/name" or its numeric id |
archived | query | boolean | no | default false |
limit | query | integer | no | 1–100; default 50 |
cursor | query | string | no | `nextCursor` from the previous page; 1–2048 characters |
Returns 200 with nextCursor, items. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Get a context document
GET /documents/{document} · operationId getDocument · Scopes: runs:read · REST only
One context document by id.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
document | path | string | yes | Context document id |
Returns 200 with document. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).
Archive a context document
DELETE /documents/{document} · operationId archiveDocument · Scopes: runs:write · MCP tool archive_document
Archive a document so new runs stop using it. Only its creator or an owner may archive it.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
document | path | string | yes | Context document id |
Returns 200 with document. Errors: 400, 401, 403, 404, 409, 429, 503 (Errors).