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:

  1. Device login — POST /api/device/code then POST /api/device/token. Issues a launch-preset key valid for 90 days. See Getting started.
  2. The agent page — create a key from a preset in the browser.
  3. 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.