Public API
Authenticate with personal access tokens or OAuth, and connect to V12's REST API or MCP server.
V12 exposes a REST API at /api/v1/* and a remote MCP server at /api/mcp, so you can run audits, read findings, and triage results from your own tools and scripts.
- Connecting an IDE or agent → MCP server
- Calling from scripts or CI → REST API or the CLI
- Full schema →
/api/v1/openapi.json
Authentication
Authenticated API requests take a Bearer token in the Authorization header:
curl -H 'Authorization: Bearer <token>' https://v12.sh/api/v1/meTwo token kinds work on authenticated endpoints:
- Personal access tokens (
v12p_*)—created at Settings → Developer for the currently selected organization. Shown once on creation. PATs don’t expire by default. - OAuth access tokens (
v12a_*)—issued via the OAuth flow. Access tokens last 1 hour; refresh tokens (when issued) last 30 days. The consent flow binds them to the organization you had selected when you approved the client.
OAuth
Use OAuth when a third-party tool needs to act on your behalf. Paste the V12 server URL into the tool and complete the sign-in prompt—the tool handles the token exchange itself. Claude Desktop, Cursor, Codex, VS Code, and Windsurf are known to work end-to-end. Manage authorized apps at Settings → Developer.
Implementing your own OAuth client
If you’re building the client side yourself:
-
Read the metadata from
GET /.well-known/oauth-authorization-server. -
Register at
POST /api/oauth/register(Dynamic Client Registration, RFC 7591). -
Send the user to
/oauth/authorize:/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>&state=<random>&code_challenge=<S256(verifier)>&code_challenge_method=S256&scope=runs:read+runs:write -
Exchange the returned code at
POST /api/oauth/token(grant_type=authorization_code). -
Refresh with
grant_type=refresh_tokenonly if the client registered with the explicitrefresh_tokengrant. DCR clients default to authorization-code-only access.
Scopes
| Scope | Label | Description |
|---|---|---|
runs:read |
View runs | List runs, read reports and findings |
runs:write |
Create runs | Start new audit runs and upload source archives. Consumes credits. |
runs:manage |
Manage runs | Cancel runs you own through the public API and MCP |
findings:write |
Edit findings | Change finding status, add comments, and update triage decisions |
repos:read |
View repositories | List GitHub repositories connected to V12 for your account |
user:read |
View profile | Read your account information and credit balance |
New tokens default to runs:read, user:read, and repos:read. Writes (runs:write, runs:manage, findings:write) are opt-in via the scope picker in Settings → Developer.
Vocabulary
Severity (severity filter values, PATCH payloads, finding fields): critical, high, medium, low, info, qa.
Validity (validity filter values, PATCH payloads, finding fields): valid, invalid (false positive), unreviewed, acknowledged (real but accepted).
Run states (state field on run objects): queued, running, completed, failed, cancelled. Terminal states (no further transitions): completed, failed, cancelled.
Rate limits
Most limits are per-user buckets: one budget per account, shared across REST, MCP and the CLI, and independent of which organization your token is bound to. The MCP transport adds one per-IP bucket that is charged before authentication, so callers behind a shared egress IP—an office NAT, a CI runner—share that budget with each other.
Hitting a limit returns 429 Too Many Requests with a Retry-After header in seconds.
| Bucket | Limit | Window | Keyed by | Charged by |
|---|---|---|---|---|
reads |
1200 | 1 minute | user | Metadata reads: profile, run list and detail, findings list, comments, context documents, export status, and the equivalent MCP tools and resources |
reads:artifact |
300 | 1 minute | user | Stored artifacts: run report, report.md, finding detail, PoC, and fix |
reads:github |
300 | 1 minute | user | Repository listing (GET /api/v1/repos, v12_repos), which runs on your own GitHub REST budget |
findings:write |
120 | 1 minute | user | Triage updates and finding comments |
runs:manage |
60 | 1 minute | user | Cancelling and sharing runs |
runs:write |
20 | 1 hour | user | Run creation, ZIP and context uploads, archiving context documents |
runs:estimate |
30 | 1 hour | user | Cost estimates |
exports:create |
100 | 1 hour | user | Finding-export creation |
mcp:protocol |
1500 | 1 minute | user | Every /api/mcp request that passes the browser-origin check and authenticates, on top of any tool or resource bucket the request goes on to dispatch. A rejected origin or an invalid token exits before this counter |
mcp:protocol:ip |
3000 | 1 minute | client IP | Every /api/mcp request, before the origin check and before authentication. The one bucket no account is exempt from |
Reads are split by what they cost us, not by which endpoint you called: reads covers work our own database answers, while reads:artifact and reads:github cover reads that pull bytes out of object storage or fan out to GitHub. Nothing got tighter in the split—those two keep the ceiling that used to apply to all reads.