Base URL and versioning
/v1, for example POST /v1/runs and GET /v1/usage. Three routes need no credential: GET /healthz, GET /readyz and GET /openapi.json, which serves the OpenAPI 3.1 document the server itself emits. Every other customer route under /v1 takes Authorization: Bearer <credential>; see Authentication.
Requests
- JSON in, JSON out. Send request bodies as JSON with
Content-Type: application/json. Field names are snake_case. - Unknown fields are refused. Every request body rejects fields it does not define, so a typo fails loudly instead of being ignored. On
POST /v1/runsthis is400 invalid_bodywith the messagethe request body is not valid: the body does not match the schema. On other routes the refusal is a plain-text 422 (see Errors). - Unknown query parameters are refused.
GET /v1/runsaccepts onlylimitandoffset; the usage routes accept only their documented parameters. - Body caps.
POST /v1/runsandPOST /v1/router/selectaccept up to 4 MiB (4,194,304 bytes) and answer413 body_too_largeabove it. Every other route accepts up to 1 MiB and answers a plain-text 413. - One header for idempotency.
POST /v1/runsrequires anIdempotency-Keyheader of 1 to 255 printable ASCII bytes. No other route reads it. See Idempotency.
Identifiers
Every id is an opaque string with a type prefix. Store ids as strings and compare them exactly; do not parse the part after the prefix.
The two run id spellings name the same run. Both are accepted wherever a run id is expected, so you can take a
run_id from the ledger and pass it to GET /v1/runs/{run_id}, or the other way round.
input_digest and output_digest on a run are 64-character lowercase hex SHA-256 digests.
Timestamps and dates
- Every timestamp is a string in exactly
YYYY-MM-DDTHH:MM:SSZform: UTC, second precision, a literalZ. For example2026-09-24T10:12:03Z. - Timestamp fields end in
_at:created_at,last_used_at,revoked_at,start_at,end_at. - Daily rows use a date,
YYYY-MM-DD, for example"date": "2026-09-24".
+00:00, with 400 invalid_parameter.
Time windows
GET /v1/usage, GET /v1/usage/ledger and GET /v1/usage/daily take an optional window.
Because windows are half-open, consecutive windows never double-count. September 2026 is
start_at=2026-09-01T00:00:00Z&end_at=2026-10-01T00:00:00Z.
Money
Every amount is an integer in micro-USD. Amount fields end in_micros.
balance_micros and the amount_micros of a billing transaction are signed: a usage row is negative, a purchase is positive. Every other amount is zero or positive. Integers avoid floating-point rounding; divide by 1,000,000 only when you display a dollar value.
Optional fields
Absent values are represented in two ways, depending on the route family:- Run responses omit them. A run that produced no output has no
output_digestkey at all, and a verdict run has nodecisionkey. - Keys, usage and billing responses send
null. A key that was never used has"last_used_at": null; a quota with no period limit has"period_spend_limit_micros": null.
null value.
Pagination
Lists are newest first. See Pagination.
CORS
The API answers browser preflights for the allowed origins configured on the service. When CORS applies:
Keep API keys out of browser code. A key in a web page is readable by anyone who loads it; call the API from your server instead. See API key security.
Example: a request that uses every convention
This call reads usage for a half-open window, sends its own request id, and gets integer micro-USD amounts back.What goes wrong
Related
- Request ids - trace one request across your logs and ours.
- Limits - every numeric bound in one table.
- Errors - the error envelope and what each status means.
- Pagination - walk run lists and the usage ledger.