Every API call carries a key scoped to a workspace. Scopes are the product's authorization boundary: the same policy is enforced for the CLI, the MCP server and a customer's own CI.
Getting a key
Three ways:
- Device login —
POST /api/device/codethenPOST /api/device/token. Issues alaunch-preset key valid for 90 days. See Getting started. - The agent page — create a key from a preset in the browser.
POST /api/v1/api-keys(keys:manage) — create, rotate and revoke keys.
GET /api/v1/api-keys, GET /api/v1/api-keys/:id, POST /api/v1/api-keys/:id/rotate and POST /api/v1/api-keys/:id/revoke all need
keys:manage. A key can never grant scopes it does not itself hold.
The scope table
| Scope | Grants |
|---|---|
targets:read / targets:write |
Read the registered target / register, verify, attach identities |
assessments:read |
List and read assessments and sandboxes |
assessments:create |
Launch assessments, sandboxes, forks; drive a live sandbox (exec, files, egress) |
assessments:cancel |
Stop a sandbox, cancel an assessment, stop a swarm |
findings:read |
Read findings, evidence, regression checks |
findings:remediate |
Open a remediation pull request |
scans:read / scans:create |
Read a code scan / upload an archive |
checks:create |
Run a pull-request check |
loops:read / loops:write |
Read / create and stop scheduled loops |
billing:read / billing:write |
Read balance and ledger / checkout and top-up |
keys:manage |
List, create, rotate, revoke API keys |
A route whose scope the key lacks returns 403:
{ "error": "api_key_action_forbidden", "scope": "assessments:create" }
assessments:create versus loops:write
This is the distinction people get wrong, and it is deliberate.
loops:write lets a key describe work: create a schedule, and plan a
dependency or compromise sweep. It cannot spend money.
assessments:create is the spending boundary. Launching a sandbox, driving
exec, forking, and running a planned sweep all require it, because all of
them reserve credit and start compute.
So a key that manages schedules for a team can be handed out without also handing over the ability to burn the workspace balance. Planning endpoints are free; the run endpoints are not.
| Call | Scope | Spends |
|---|---|---|
POST /api/v1/sweeps/plan |
loops:write |
no |
POST /api/v1/sweeps/synthesize |
loops:read |
no |
POST /api/v1/sweeps/run |
assessments:create |
yes |
POST /api/v1/compromise/plan |
loops:write |
no |
POST /api/v1/compromise/synthesize |
loops:read |
no |
POST /api/v1/compromise/run |
assessments:create |
yes |
POST /api/v1/sandboxes |
assessments:create |
yes |
POST /api/v1/scans |
scans:create |
yes |
POST /api/v1/loops |
loops:write |
no (each run spends on its own schedule) |
Presets
POST /api/v1/api-keys accepts a preset instead of an explicit scope list:
| Preset | Label | Scopes |
|---|---|---|
read |
Read only | targets:read, assessments:read, findings:read, scans:read, loops:read, billing:read |
existing |
Use existing sandboxes | read + assessments:cancel |
launch |
Create and use sandboxes | targets:read/write, assessments:create/read/cancel, findings:read, scans:create/read, checks:create, loops:read/write, billing:read/write |
full |
Full access | every scope above, including keys:manage and findings:remediate |
custom |
Custom | the explicit scopes list you pass |
Target restriction
A key may be limited to a set of hostnames (allowedTargets). Any route that
addresses a target, sandbox, assessment or swarm by id is checked against that
set before the handler runs. POST /api/v1/sandboxes/:id/config is exempt
because it is the call that attaches a target in the first place, though the
target it sets is still checked.
Error envelope
Errors are { "error": "<code>", … }. Some add message, and a few add
structured fields: scope, allowedTargets, limit, minimum, maximum,
setupUrl, checkoutUrl, requiredMicros, balanceMicros.
Idempotency
Launch routes accept an Idempotency-Key header, or an idempotencyKey body
field, of up to 100 characters. The same key returns the original resource with
200 instead of creating a second one with 201. Batch launches suffix the key
per member, so a retried batch reuses every member rather than duplicating some.
Rotation and revocation
POST /api/v1/api-keys/:id/rotate issues a new secret and keeps the key's
scopes and restriction, so a rotation does not silently widen access.
POST /api/v1/api-keys/:id/revoke stops the key being accepted. Only the
SHA-256 hash of a key is stored; the secret is shown once at creation.