Authentication and permissions

API keys and OAuth, scopes, organization binding, rate limits, and fixing 401, 403 and 429.

Every request to the REST API or the MCP server carries one token. This page covers which tokens work, what a token may do, and why a request is refused.

Credentials

Two kinds of token work:

  • API keys start with v12p_. You create them in Settings → Developer; see Create an API key. They suit scripts and CI.
  • OAuth access tokens start with v12a_. MCP clients and the v12 CLI get one when you sign in through the browser and approve the consent page.

Send the token in the Authorization header as Bearer <token>. The API looks nowhere else: a token in a cookie or in the query string is ignored.

Scopes

A token's scopes decide which operations it can call:

ScopeLabelAllows
user:readView account and membersYour account, the member directory (usernames, emails and roles) and the credit balance.
repos:readView repositories and focusesRepositories, branches, pull requests, tags and focus instructions.
runs:readView runsRuns, their progress and context documents.
runs:writeCreate runsEstimate and start runs; add or archive context documents.
runs:manageManage runsCancel runs.
findings:readView findingsThe inbox: findings, their status, reports and comments.
findings:writeEdit findingsChange status, comment and update triage decisions.
  • The Create key dialog starts with no scope ticked, and a key needs at least one.
  • Starting a run spends credits, so the consent page marks Create runs with Uses credits.
  • GET /runs/{run}/findings (the MCP tool list_run_findings) needs both runs:read and findings:read.
  • An MCP client sees only the tools its token's scopes allow.
  • v12 auth login asks for every scope unless you pass --scopes.
  • A missing scope gets 403 insufficient_scope. details.required lists the scopes the operation needs, and so does scope in the WWW-Authenticate header.

The REST reference lists the scopes each endpoint needs.

Which organization a token reads

Each token reads exactly one workspace, and the API cannot switch it:

  • An API key reads the workspace that was active when you created it.
  • An OAuth token reads the workspace named on the consent page: the one active in the app when you signed in.

The consent page shows:

  • the app: Authorize V12 CLI for V12's own CLI, or "app wants to access V12" with a Third-party app tag for anything else;
  • your username and email, with Not you? to sign in as someone else;
  • the workspace's name, marked "Personal workspace" or "Organization";
  • the scopes the app asks for, grouped under Read and Act;
  • Cancel and Authorize followed by the app's name.

The consent page has no workspace picker. To connect an app to another workspace:

If the app is already connected, revoke it under Settings → Developer → Authorized apps while its current workspace is active.

Switch to the other workspace with Switch organization in the account menu.

Connect the app again. The consent page now names the new workspace.

An id from another workspace gets 404 not_found, even when you belong to both.

Organization permissions

A token can do only what its user's role allows in the workspace, and the API checks the role on every request, so a role change applies at once.

Admins and Members can both call every operation. The one exception: archiving a context document that someone else created needs an Admin, and a Member gets 403 forbidden. In GET /members, Admins have the role owner.

When access ends

The API answers 401 unauthenticated when:

  • the API key was revoked;
  • the OAuth token expired, or its app was revoked under Authorized apps, which "invalidates its tokens immediately";
  • you changed or reset your password, which revokes every API key and OAuth token you hold, in every workspace;
  • your membership in the workspace was removed;
  • your account was suspended.

v12 auth logout revokes the CLI's OAuth tokens before it deletes them from your machine.

Rate limits

Budgets count per user, across all of your tokens and across REST, MCP and the CLI. The ip budget counts per IP address instead, before the token is checked.

BucketLimitWindowWhat counts
ip3,0001 minuteEvery REST and MCP request
mcp1,5001 minuteEvery authenticated MCP request
reads1,2001 minuteOperations that read
findings:write1201 minuteFinding changes and comments; a bulk change counts once per finding
runs:estimate301 hourRun estimates
runs:write201 hourRun starts
runs:manage601 minuteRun cancellations
documents6010 minutesUpload slots, new documents and archiving
  • Each response reports the bucket it charged in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, the seconds until the window resets.
  • Over a limit, the API answers 429 rate_limited with a Retry-After header, and details.bucket names the bucket.
  • Run estimates, run starts and document operations also share a limit of 60 a minute per user with the same actions in the app.
  • If the rate limiter itself is down, reads carry on, but findings:write, runs:estimate, runs:write, runs:manage and documents answer 503 rate_limit_unavailable with Retry-After: 30.

Troubleshooting

Every error body carries a requestId. Keep it when you report a problem.

SymptomCauseFix
401 unauthenticated, and WWW-Authenticate has no errorNo Authorization header reached the API.Send Authorization: Bearer <token>.
400 invalid_request with details.field: "Authorization"The header is not exactly Bearer and one token: another scheme, a comma, or extra text.Send one token after Bearer .
401 unauthenticated with error="invalid_token"The token is not a V12 token, or it was revoked or expired; a password change or reset revokes all of yours. Or your membership was removed, or your account suspended.Create a new key, or sign in again.
403 insufficient_scopeThe token lacks a scope that details.required names.Create a key with those scopes, or connect the app again and approve them.
403 forbiddenYour role does not allow the action.Ask an Admin.
404 not_found for something you can see in the appThe token reads another workspace.Check organization in GET /me, then see Which organization a token reads.
429 rate_limitedA budget is spent; details.bucket names it.Wait Retry-After seconds, then retry.
503 public_api_disabledThe API is turned off for now; the app still works.Retry later.
503 platform_unavailableA V12 service behind the API is unavailable, did not answer, or is rate limiting V12.Wait Retry-After seconds (also in details.retryAfterSeconds) when present, else retry later.
503 rate_limit_unavailableThe rate limiter is down, so writes and spending pause.Retry after 30 seconds.
500 internal_errorAn unexpected failure inside V12.Retry once; if it persists, report it with the requestId.

On this page