API reference

Quickstart, authentication, streaming, routing and error handling for the AgentsRouter API. OpenAI-compatible: change the base URL and the key.

Every endpoint, generated from the same OpenAPI description the API publishes at /openapi.json, so it cannot list an endpoint the API lacks or miss one it has. Load that file into Postman, Insomnia or any OpenAPI code generator to build a client.

92 endpoints in 17 groups.

Inference

Model requests.

Create a chat completion

POST /v1/chat/completions

OpenAI-compatible. Routes to an endpoint your guardrails and preferences allow, dispatches on your own provider credential, and fails over to the next candidate before the first byte ships -- walking models[] to exhaustion.

With stream: true the response is server-sent events: one data: line per ChatCompletionChunk, SSE comments (lines starting with :) as keepalives, a final chunk carrying usage, then data: [DONE].

Authentication

Inference key (ar-v1-…) · Dashboard session

Parameters

X-AgentRouter-Metadata enum · header

enabled adds agentrouter_metadata: on the final chunk when streaming, and beside error on an error once routing has begun. X-Metadata is accepted too.

X-Session-Id string · header

A sticky-routing key, used when the body has no session_id.

X-Anthropic-Beta string · header

Forwarded to Anthropic as anthropic-beta.

HTTP-Referer string · header

Your app's URL, for attribution. The app listing keeps its path; each request row keeps only the scheme and host.

X-AgentsRouter-Agent string · header

The agent this request is for: 1 to 64 letters, digits, ., _, : or -, starting with a letter or digit. Recorded on the request, and bound by that agent's budgets (PUT /v1/agents/{agent}/budgets/{interval}) across every key it uses. A malformed value is a 400 before anything is spent.

X-Title string · header

Your app's name. X-App-Title and X-OpenRouter-Title are accepted too.

X-App-Visibility enum · header

hidden keeps the app out of public listings. Read on the app's first request only.

X-App-Categories string · header

Up to two comma-separated categories.

Request body

model string

A catalog id, author/slug, optionally with a variant (:free, :nitro, :floor, :exacto), or a ~author/family alias, which resolves to the author/family-latest row when the catalog publishes one and otherwise resolves to nothing -- it never picks a model for you by guessing at the name. An id listed in /v1/models always means that listing: some free tiers are catalog rows of their own, named author/slug:free, and a suffix is only split off the id when no such row can serve the request. It always applies as a variant either way.

models string[]

A fallback chain, tried in order. At most three are attempted.

messages ChatMessage[]
prompt string

Legacy: sent as a single user message when messages is absent.

stream boolean
Default: false
max_tokens integer
max_completion_tokens integer
temperature number
top_p number
top_k integer
stop string | string[]
seed integer
tools object[]
tool_choice any

none, auto, required, or {"type": "function", "function": {"name": …}}.

response_format object
reasoning object

{ effort: low|medium|high } or { max_tokens: n }. On Anthropic this becomes extended thinking with that budget (floor 1024) and the thinking text is returned as message.reasoning (or delta.reasoning when streaming); temperature, top_p and top_k are dropped there because the provider refuses them alongside thinking.

provider ProviderPreferences

Routing preferences (§6.2). Preferences reorder; constraints reject. Guardrails outrank everything here.

session_id string

Keeps a conversation on one endpoint so its prompt cache stays warm (§6.6).

prompt_cache_key string

Used as the sticky-routing key when there is no session_id.

routing_stability enum

Defaults to sticky when the request has a pin key, adaptive otherwise (§17.2).

One of: adaptive, sticky, pinned

Responses

200 The completion: JSON, or an event stream when `stream` is true.
id stringrequired

gen-…. Pass it to GET /v1/generation for the full record.

object anyrequired
created integerrequired

Unix seconds.

model stringrequired

The model that served, which differs from the one requested when a fallback fired.

provider string
choices object[]required
usage Usagerequired
agentrouter_metadata RouterMetadata

Why this model, this provider, this price (§7.5). Present only when X-AgentRouter-Metadata: enabled was sent.

400 `messages is required`, or `model is required`.
401 No key; an invalid, disabled or expired key; or an invalid session.
402 Not enough credit for the worst-case hold. Once a balance is negative every request is refused, free models included.
403 A budget is exhausted (`budget_exceeded`; the message names the level and the limit), a content filter blocked the prompt (`guardrail_blocked`; the message names the labels, never the matched text), or the key is a management key -- in which case the body reports `code: 401` and `invalid_credentials`. With a `run_id`, also `run_budget_exceeded` (§17.1). That one type covers every way a run refuses a turn -- its cost ceiling, `max_turns`, `max_tool_calls`, a `model_policy` that excludes the model, and a run that is no longer open. §17.1 specifies it for "the run's budget and caps", and the **message** names which one bound; a `model_policy` or closed-run refusal therefore carries a type that reads narrower than it is.
404 No endpoint can serve the model under your constraints (`no_endpoints_available`). `metadata.unsatisfied_filters` names the filters that emptied the set. Also `not_found` when `run_id` names a run this organization does not have -- a different problem from a run that ran out, and fixed differently.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.
502 Every candidate failed upstream (`provider_error`). `metadata.provider_code` and `metadata.provider_name` describe the last.
529 Every candidate was overloaded upstream. The body reports `code: 502`.

Preview how a request would route

POST /v1/route/preview

Answers "why would I get that model, from that provider?" before the money is spent. Router metadata answers it afterwards; this runs the same gates, the same filters and the same ranking against a request that is never dispatched.

It mirrors POST /v1/chat/completions gate for gate and in order: the same auth, the same budget checks in the same precedence, the same content filter applied before routing, the same per-model candidate load, filter and rank, the same fallback depth, and the same sticky hoist.

It has no side effects. No ledger hold, no run reservation, no request row, no sticky pin written, no provider contacted. Rate limited in its own bucket, so debugging a routing problem never eats the quota needed to fix it.

Read ordering before trusting cascade as a prediction: the default ranking is a weighted random draw, and sampled means a real request draws again.

Authentication

Inference key (ar-v1-…) · Dashboard session

Parameters

X-Session-Id string · header

A sticky-routing key, used when the body has no session_id.

Request body

model string

The model to route, exactly as /v1/chat/completions takes it -- :free, :nitro, :floor, :exacto suffixes and ~author/family-latest aliases included.

models string[]

A fallback chain, previewed in order. Walked to the same depth a real request would walk it.

provider ProviderPreferences

Routing preferences (§6.2). Preferences reorder; constraints reject. Guardrails outrank everything here.

tools object[]

Only the COUNT matters here: a non-empty list makes tools a required parameter, which is a hard filter under require_parameters.

messages ChatMessage[]

Optional. Supplied only so content filters and the sticky prefix hash can be evaluated; nothing is sent to a provider. Omit them and content_filter comes back null rather than a fabricated allow.

session_id string

Sticky-routing key, as on the chat route.

prompt_cache_key string
routing_stability enum
One of: adaptive, sticky, pinned
routing_seed string

Seeds the weighted draw so a preview is reproducible and quotable in a bug report. Named routing_seed, not seed: the latter is OpenAI's sampling seed on the chat route, and one name for two knobs on sibling endpoints is a trap.

Responses

200 How this request would route. A 200 with `would_dispatch: false` is the normal way to report "this would be refused" -- the preview succeeded, the request it describes would not.
requested stringrequired
strategy string

direct, fallback for a models[] chain, or latest for a ~alias.

would_dispatch booleanrequired

Whether a real request with this body would reach a provider at all.

limit_warning string | null

Set when the key is at or past a SOFT ceiling: the request would be served, with this sentence in its x-agentsrouter-limit-warning header.

refusal object | null

Why not, in the order the pipeline refuses: a budget binds before a filter runs, and both before routing is consulted. Null when would_dispatch is true.

ordering enum

Whether cascade is the order or one draw from it.

§8.9.1's default ranking is a weighted random shuffle, deliberately: a deterministic cheapest-first router starves every provider but one. So sampled means the real request draws again and may order the platform attempts differently — reproduce this draw with the same routing_seed. deterministic means the order is fixed (an explicit sort, a :floor/:nitro variant, or too few platform attempts to shuffle) and a real request will match it.

One of: deterministic, sampled
routing_seed string

The seed this preview actually used, echoed so an unseeded call is still reproducible.

winner RoutePreviewCandidate | null
cascade RoutePreviewCandidate[]required

The endpoints a real request would try, in order, after the sticky hoist and cut to the same fallback depth.

beyond_fallback_depth integer

Endpoints that survived every filter but sit past the fallback depth: eligible, and still unreachable.

endpoints object
refused object[]

One entry per excluded endpoint, carrying the FIRST constraint that removed it. unsatisfied_filters says which constraints emptied the set; this says which endpoints each one cost you.

unsatisfied_filters string[]

The same strings §7.7's 404 would carry.

unresolved_models string[]

Slugs in the chain that named no model we have — a typo, or a model since withdrawn.

guardrails object
content_filter object | null

Null when no messages were supplied. labels name the detectors that matched; the matched text never travels.

sticky object
400 `model is required`.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Catalog

Models, their endpoints and providers. Public.

List models

GET /v1/models

Public. Rate limited per client address: 120 a minute, 20,000 a day.

Authentication

None: this endpoint is public.

Parameters

limit integer · query

Page size.

offset integer · query

Rows to skip.

q string · query

A case-insensitive substring of the name or id.

model_authors string · query

Comma-separated authors, e.g. anthropic,openai.

context integer · query

Minimum context length, in tokens.

sort enum · query

Anything else sorts by id.

Responses

200 A page of models.
id stringrequired

author/slug.

canonical_slug string
name stringrequired
created integer | null

Unix seconds.

description string | null
context_length integer | null
architecture object | null

Input and output modalities.

supported_parameters string[] | null
default_parameters object | null
resale_permitted enum
One of: yes, no, via_reseller, unknown
byok_only boolean
is_free boolean
deprecation_date string | null
pricing object

The cheapest endpoint's price. Present on the list only.

429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get a model

GET /v1/models/{author}/{slug}

Public and rate limited, as the list is. pricing is absent here; the endpoints route has every endpoint's prices.

Authentication

None: this endpoint is public.

Parameters

author string · pathrequired

e.g. anthropic.

slug string · pathrequired

e.g. claude-sonnet-5.

Responses

200 OK.
id stringrequired

author/slug.

canonical_slug string
name stringrequired
created integer | null

Unix seconds.

description string | null
context_length integer | null
architecture object | null

Input and output modalities.

supported_parameters string[] | null
default_parameters object | null
resale_permitted enum
One of: yes, no, via_reseller, unknown
byok_only boolean
is_free boolean
deprecation_date string | null
pricing object

The cheapest endpoint's price. Present on the list only.

404 Not found, or not in your organization.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List a model's endpoints

GET /v1/models/{author}/{slug}/endpoints

Public and rate limited. Every provider serving the model, with its price, measured uptime and latency, and tool-call reliability.

Authentication

None: this endpoint is public.

Parameters

author string · pathrequired

e.g. anthropic.

slug string · pathrequired

e.g. claude-sonnet-5.

Responses

200 OK.
404 Not found, or not in your organization.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List providers

GET /v1/providers

Public and rate limited.

Authentication

None: this endpoint is public.

Responses

200 OK.
slug stringrequired
name stringrequired
hq_country string | null
datacenters string[] | null
privacy_policy_url string | null
tos_url string | null
status_page_url string | null
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Keys

API keys.

Inspect the calling key

GET /v1/key

For an inference key: its limit, what remains of it, and what it has spent plus what its in-flight requests have reserved.

Authentication

Inference key (ar-v1-…)

Responses

200 OK.
label string | null
limit number | null

The key's spend limit in USD, or null for none.

limit_remaining number | null

Ceiling minus usage, measured exactly as the spend gate measures it: within the reset window, including spend on your own provider credential only when include_byok_in_limit is set, plus the worst-case reservation of every request on this key still in flight -- released or replaced by the actual amount at settlement, so this can go down. Not a billing total -- see /v1/credits for money.

usage number

Spend counted against this key ceiling, measured exactly as the spend gate measures it: within the reset window, including spend on your own provider credential only when include_byok_in_limit is set, plus the worst-case reservation of every request on this key still in flight -- released or replaced by the actual amount at settlement, so this can go down. Not a billing total -- see /v1/credits for money.

limit_reset string | null
limit_mode enum

hard (the default) refuses a request once the key is at or past its ceiling, with 403 budget_exceeded. soft serves it and adds an x-agentsrouter-limit-warning header carrying the same sentence.

One of: hard, soft
include_byok_in_limit boolean

Count spend made on your own provider credential toward this ceiling, at IMPUTED LIST PRICE -- what the call would have cost on platform capacity, not the platform fee charged. Defaults to false, and while every model is BYOK-only that means the ceiling counts nothing and cannot bind.

is_free_tier boolean
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List keys

GET /v1/keys

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

include_disabled boolean · query

Include revoked keys.

Responses

200 OK.
hash string

Identifies the key in DELETE /v1/keys/{hash}. Not the key itself.

prefix string
name string | null
label string | null
class enum
One of: inference, management
disabled boolean
limit number | null

USD.

limit_nanodollars string | null

The same limit, raw.

limit_reset string | null
limit_mode enum

hard (the default) refuses a request once the key is at or past its ceiling, with 403 budget_exceeded. soft serves it and adds an x-agentsrouter-limit-warning header carrying the same sentence.

One of: hard, soft
include_byok_in_limit boolean
workspace_id string | null

The workspace (client) the key belongs to.

usage number | null

USD counted against the ceiling in its current window, measured exactly as the spend gate measures it (see KeyInfo.usage). Null for a key with no ceiling, or a revoked one.

limit_remaining number | null

Ceiling minus usage. Negative when a soft ceiling has been passed. Null when there is no ceiling.

limit_resets_at string | null

When the current window ends. Null for a lifetime ceiling or none.

created_at string
updated_at string
expires_at string | null
external_user string | null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Create a key

POST /v1/keys

The plaintext key is in this response and never again: only a salted hash is stored.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

name string
limit number

A spend limit in USD. Omit for none.

limit_reset string | null

The window the ceiling covers, in UTC calendar terms. Null means lifetime: the ceiling never resets.

One of: daily, weekly, monthly, lifetime, null
limit_mode enum

hard (the default) refuses a request once the key is at or past its ceiling, with 403 budget_exceeded. soft serves it and adds an x-agentsrouter-limit-warning header carrying the same sentence.

One of: hard, soft
Default: "hard"
workspace_id string

Put the key in this workspace (client). Omit for the caller's own workspace. Another organisation's id is a 404.

class enum
One of: inference, management
Default: "inference"

Responses

201 Created.
key stringrequired

The key. Store it now.

hash stringrequired
name string | null
class enumrequired
One of: inference, management
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Change a key's spend limit, or pause and resume it

PATCH /v1/keys/{hash}

Changes the ceiling, its window, its mode, or whether BYOK spend counts. Only these: a key's class, name and secret are fixed at creation. At least one field is required. The new ceiling applies at once on the instance serving this call and within the auth cache's 10-second TTL everywhere else.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

hash string · pathrequired

The key's hash, from the list.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

limit number | null

The new ceiling in USD; null removes it.

limit_reset string | null

The window the ceiling covers, in UTC calendar terms. Null means lifetime: the ceiling never resets.

One of: daily, weekly, monthly, lifetime, null
limit_mode enum

hard (the default) refuses a request once the key is at or past its ceiling, with 403 budget_exceeded. soft serves it and adds an x-agentsrouter-limit-warning header carrying the same sentence.

One of: hard, soft
include_byok_in_limit boolean
workspace_id string

Move the key to this workspace of the same organisation.

paused boolean

false resumes a key paused by a runaway flag or by hand; true pauses it by hand. A paused key answers 403 key_paused.

Responses

200 The key's ceiling as it now stands.
hash stringrequired
name string | null
class enum
One of: inference, management
limit number | null

USD.

limit_reset string | null
limit_mode enum
One of: hard, soft
include_byok_in_limit boolean
paused boolean
paused_reason enum
One of: runaway, manual, null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Revoke a key

DELETE /v1/keys/{hash}

Disables the key and ends every dashboard session it created.

Propagation is bounded at 10 seconds (§17.4: "publish that TTL"). The instance serving this call stops accepting the key immediately; any other instance stops within the auth cache's TTL. That cache serves no stale window — past ten seconds the entry is refused and the caller waits on the database — so ten seconds is the real bound, not the point at which a refresh merely begins.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

hash string · pathrequired

The key's hash, from the list.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Usage

What was requested, and what it cost.

List recent requests

GET /v1/activity

Newest first, keyset-paginated. Cost is summed per row, over the window.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

limit integer · query

Page size.

days integer · query

The window, ending now.

cursor string · query

The next_cursor of the previous page. Opaque; do not parse it.

model string · query

The model that served.

provider string · query

A provider slug.

workspace string · query

One client: a workspace id. A malformed id is a 400.

agent string · query

One agent: the X-AgentsRouter-Agent value.

status string · query

The request status, e.g. complete.

byok boolean · query

BYOK requests only, or none.

app string · query

An app_id.

Responses

200 A page of requests.
id string
created_at string
requested_model string
served_model string | null

Null until routing picks one; differs from requested_model when a fallback fired.

provider_slug string | null
status string
finish_reason string | null

Normalized to five values. native_finish_reason carries the provider's own.

One of: stop, length, tool_calls, content_filter, error, null
native_finish_reason string | null
is_byok boolean
is_cache_hit boolean
cancelled boolean
streamed boolean
quantization string | null
ttft_ms integer | null
generation_ms integer | null
routing_overhead_ms integer | null

Total time minus upstream provider time -- the latency this gateway added (§18). Null when nothing was dispatched, and on rows recorded before it was measured.

error_code string | null

The provider's own code where a provider refused, or a router.* reason code where this gateway is what failed -- a credential it could not open, a request it could not express. A stream that broke after it had committed is recorded on the upstream attempt, not here.

error_message string | null

The failure in English, for a developer. Where a provider refused, this names the provider, its HTTP status and its error code -- never the provider's own text, which can quote the prompt and is returned only in the response to the request itself. Where this gateway failed, its own sentence.

total_cost number

US dollars.

app_id string | null
app_title string | null
http_referer string | null
workspace_id string | null

The client (workspace) of the key that made the request.

agent string | null

The X-AgentsRouter-Agent the request named, if any.

400 `cursor is malformed`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Export a month of requests as CSV

GET /v1/activity/export.csv

One row per request in a UTC calendar month, for the account or one client (workspace). Columns: created_at, request_id, client, key, requested_model, served_model, provider, status, error_code, byok, prompt_tokens, completion_tokens, billed_usd (what this account was charged), list_price_usd (the tokens at catalogue list price: under BYOK, the number a client is re-billed on and the one budgets measure) and agent. Text cells that a spreadsheet would run as a formula are prefixed with a single quote. Capped at 200,000 rows, after which a # truncated line ends the file. At most 6 exports a minute per key.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

month string · query

YYYY-MM, UTC. Default: the current month.

workspace string · query

One client: a workspace id. Another organisation's is a 404.

Responses

200 The CSV, as an attachment.
400 `month must be YYYY-MM` or `workspace must be a workspace id`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
429 More than 6 exports a minute.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get activity totals

GET /v1/activity/summary

The KPI cards (§14.9), with the prior period of equal length beside them and a daily series for the sparklines. Both periods are read in one pass, so they cannot disagree about where "now" was.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

days integer · query

The window, ending now. The prior period is the same length again.

workspace string · query

One client: a workspace id. A malformed id is a 400.

Responses

200 Totals for the window.
requests integer
errors integer

Requests that ended with an error code. Counted zero on deployments older than the reason codes, where no code was recorded.

byok_requests integer
spend number

US dollars.

tokens integer

Prompt plus completion. cache_read and reasoning are subsets of those and are not added again.

prompt_tokens integer
cached_tokens integer

Prompt tokens served from the provider's cache -- a subset of prompt_tokens.

cache_hit_rate number | null

cached_tokens / prompt_tokens. Null when there were no prompt tokens, which is not the same as a rate of zero.

blended_per_million number | null

USD per million tokens, blended across every model and provider in the window. Null when there were no tokens.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List upstream provider requests

GET /v1/activity/upstream

One row per dispatch to a provider, newest first and keyset-paginated. A request that failed over twice appears three times -- filter by request to see a single generation's attempts in order.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

limit integer · query

Page size.

days integer · query

The window, ending now.

cursor string · query

The next_cursor of the previous page. Opaque; do not parse it.

request string · query

A generation id: every dispatch made for it.

provider string · query

A provider slug.

status enum · query

Whether the dispatch committed.

Responses

200 A page of dispatches.
id string

Opaque; the row's identity, not the request's.

request_id string

The generation this dispatch was made for. Several rows share it when a fallback fired.

created_at string
attempt integer

1-indexed position in the cascade.

model_id string
provider_slug string
endpoint_id string
is_byok boolean
status enum

Whether the dispatch committed. A stream that shipped bytes and then failed is ok with an error_message.

One of: ok, error
http_status integer | null

The upstream status. Null when the dispatch never reached the provider.

error_code string | null

The provider's own code where the provider answered, or a router.* reason code where this gateway is what failed -- a credential it could not open, a request it could not express, a stream that broke after it had committed.

error_message string | null

As on the request: the provider, status and code where the provider refused; never its own text.

duration_ms integer | null

Time inside the provider call. Null when nothing was dispatched.

ttft_ms integer | null
400 `cursor is malformed`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List conversations

GET /v1/activity/sessions

Generations grouped by session_id, most recently active first. Only requests that carried one are included.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

limit integer · query

Page size.

days integer · query

The window, ending now.

cursor string · query

The next_cursor of the previous page. Opaque; do not parse it.

Responses

200 A page of conversations.
session_id string
generations integer

Requests in this conversation, within the window.

errors integer
started_at string
last_seen_at string
spend number

US dollars.

tokens integer
models string[]

Every model the conversation was served by. More than one means it switched mid-session.

providers string[]
400 `cursor is malformed`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get one request's record

GET /v1/generation

Which endpoint served it, at which price version, and what each SKU cost -- enough to recompute the charge.

Not the content. Prompts and completions are stored only when your organisation has enabled input/output logging (§13.1), which is off by default and never retroactive; when it is on, the content is read from GET /v1/generation/{id}/content, not from here.

Authentication

Inference key (ar-v1-…)

Parameters

id string · queryrequired

The id of a chat completion.

Responses

200 OK.
id string
model string | null

The model that served.

provider_name string | null
endpoint_id string | null
quantization string | null
total_cost number

US dollars.

finish_reason string | null

Normalized to five values. native_finish_reason carries the provider's own.

One of: stop, length, tool_calls, content_filter, error, null
native_finish_reason string | null
streamed boolean
cancelled boolean
is_byok boolean
latency integer | null

Time to first token, in milliseconds.

generation_time integer | null

Milliseconds.

price_version_ids integer[]
usage_lines object[]
app_id string | null
app_title string | null
http_referer string | null

Scheme and host only. The path, query string and fragment are never stored on a request, because that is where an app puts the user it is serving.

user_agent string | null
created_at string
400 `id is required`.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get one generation's stored prompt and completion

GET /v1/generation/{id}/content

§13.1. Returns content only if your organisation had input/output logging enabled when the generation was made.

Stored encrypted (AES-256-GCM, data key wrapped by Cloud KMS) in a table separate from request metadata, under an AAD bound to your organisation — a credential ciphertext cannot be read here and content cannot be read from the credential path. Deleted automatically past your configured retention.

This is the only read path for stored content, and it is yours. §13.1 forbids using it for training or analytics, and there is no internal reader.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The generation id (gen-…).

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
request_id stringrequired
content objectrequired

The request messages, the completion, and any tool calls.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Credits

Balance and purchases.

Get the balance

GET /v1/credits

Held credits are reserved by requests in flight and are shown apart from the available balance.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
total_credits number

US dollars.

held_credits number

US dollars.

available_credits number

US dollars.

credit_line number

US dollars.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get the purchase fee schedule

GET /v1/credits/fees

Public. While nothing a credit can buy exists (BYOK-only), purchasable is false and the fee is reported as 0.

Authentication

None: this endpoint is public.

Responses

200 OK.
fee_bps integer

Basis points.

fee_min number

US dollars.

min_purchase number

US dollars.

configured boolean

A payment processor is configured.

purchasable boolean

A purchased balance could actually be spent.

451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List purchases

GET /v1/credits/history

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

limit integer · query

Page size.

cursor string · query

The next_cursor of the previous page. Opaque; do not parse it.

Responses

200 A page of purchases.
id string
created_at string
provider string
status string
gross number

US dollars.

fee number

US dollars.

net number

US dollars.

tax_jurisdiction string | null
has_document boolean
400 `cursor is malformed`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get a purchase's invoice or receipt

GET /v1/credits/purchases/{id}/invoice

Resolved from Stripe on demand; only the checkout session id is stored.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A purchase id.

Responses

200 OK.
kind enum
One of: invoice, receipt
url string
pdf_url string | null
number string | null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
409 The document is not ready yet; try again shortly. The body reports `code: 400`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.
502 Stripe could not be reached.
503 Billing is not configured on this deployment (`service_unavailable`).

Start a credit purchase

POST /v1/credits/checkout

Returns a Stripe Checkout URL. Refused with 409 while credits cannot be spent (BYOK-only, zero fee) -- checked before the body is read.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

amount numberrequired

USD.

Responses

200 OK.
url string
id string
fee number

US dollars.

400 `amount must be at least $2`, or no greater than $50,000.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
409 Credits cannot be purchased while nothing can spend them. The body reports `code: 400`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.
503 Billing is not configured (`service_unavailable`).

Receive a Stripe event

POST /v1/webhooks/stripe

For Stripe, not for you. Credits on checkout.session.completed and checkout.session.async_payment_succeeded only when payment_status is paid, once per session; logs async_payment_failed; acknowledges everything else with 200.

Authentication

Stripe signature

Responses

200 Accepted. Not wrapped in `data`.
received anyrequired
400 `Invalid signature`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.
503 The webhook secret is not configured (`service_unavailable`).

BYOK

Your provider credentials. Required before anything routes.

List provider credentials

GET /v1/byok

Keys are never returned, by this or any route -- only key_hint.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
provider_slug string
name string | null
key_hint string

The last characters of the key. The key itself is never returned.

tier enum
One of: prioritized, fallback
priority integer
fallback_policy enum
One of: platform, none_for_models, none_for_provider
model_filter object
disabled boolean
created_at string
last_used_at string | null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Add a provider credential

POST /v1/byok

Encrypted with a per-credential key before it is stored. Nothing routes until you have one for the provider that serves your model.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

provider stringrequired

A provider slug with an adapter, e.g. anthropic.

api_key stringrequired
name string
tier enum
One of: prioritized, fallback
priority integer
fallback_policy enum
One of: platform, none_for_models, none_for_provider
model_filter object

Responses

201 Created.
id string
provider_slug string
name string | null
key_hint string

The last characters of the key. The key itself is never returned.

tier enum
One of: prioritized, fallback
priority integer
fallback_policy enum
One of: platform, none_for_models, none_for_provider
model_filter object
disabled boolean
created_at string
last_used_at string | null
400 `provider is required`, `api_key is required`, or no adapter exists for the provider.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.
503 BYOK key management is not configured on this deployment (`service_unavailable`).

Enable or disable a provider credential

PATCH /v1/byok/{id}

Only disabled can change. To change the key, add a new credential and delete this one.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A credential id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

disabled booleanrequired

Responses

200 OK.
id string
disabled boolean
400 Only `disabled` may be patched.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Delete a provider credential

DELETE /v1/byok/{id}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A credential id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Alerts

Usage alerts: where you are told a key is nearing its spend ceiling.

List usage-alert channels

GET /v1/alerts/channels

Where the organisation is told that a key crossed a share of its spend ceiling. email_available says whether this deployment can send email. A webhook's signing secret is never returned here: only once, when the channel is created.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 The channels.
id string
kind enum
One of: webhook, slack, email
target string
name string | null
thresholds integer[]
disabled boolean
created_at string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Add a usage-alert channel

POST /v1/alerts/channels

A key crossing any of thresholds (percent of its ceiling, default 50, 80 and 100), or an agent crossing them on one of its budgets (agent_limit.threshold), is announced once per threshold per ceiling window, on every channel that lists that threshold; a key that jumps several thresholds in one request is announced once, at the highest. A webhook target must be a public https URL on the default port; a slack target must be a https://hooks.slack.com/... incoming-webhook URL; email needs the deployment to be configured for it. Webhook deliveries are JSON, signed in x-agentsrouter-signature: t=<unix>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> with the channel's signing_secret. At most 20 channels per organisation.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

kind enumrequired
One of: webhook, slack, email
target stringrequired

An https URL, or an email address for email.

name string
thresholds integer[]

Whole percentages from 1 to 100. Default [50, 80, 100].

Responses

201 Created.
id string
kind enum
One of: webhook, slack, email
target string
name string | null
thresholds integer[]
disabled boolean
created_at string
400 An unsafe or malformed target, a Slack target that is not hooks.slack.com, email not configured, bad thresholds, or the 20-channel cap.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Remove a usage-alert channel

DELETE /v1/alerts/channels/{id}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The channel id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Send a test alert

POST /v1/alerts/channels/{id}/test

Delivers a test event now and reports the outcome, so a wrong URL or a receiver that rejects the payload shows at once. The attempt is recorded in the delivery log.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The channel id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
status enumrequired
One of: sent, failed, skipped
http_status integer | null

What the receiver answered, when it answered.

error string | null

Why it failed, in our words. The receiver's response body is never read.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

What a runaway flag does

GET /v1/alerts/settings

alert: every flag is sent to the alert channels and nothing else happens (the default). pause: a flagged KEY is also paused -- it answers 403 key_paused until resumed with PATCH /v1/keys/{hash} {"paused": false}. An agent flag is never a pause: a tag is not a credential.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
runaway_action enumrequired
One of: alert, pause
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Choose what a runaway flag does

PUT /v1/alerts/settings

Requires config:write (Developer or above): this decides whether a heuristic may stop production traffic.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

runaway_action enumrequired
One of: alert, pause

Responses

200 OK.
runaway_action enumrequired
One of: alert, pause
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Runaway-spend flags

GET /v1/alerts/runaways

A key or an agent (X-AgentsRouter-Agent) flagged because its last 15 minutes were far above its own previous 24 hours: spend at least 3x its usual 15 minutes and at least $5, or requests at least 5x usual and at least 300. Checked every five minutes; a subject is flagged at most once an hour, and each flag is sent to every alert channel as a spend.runaway event. With runaway_action = pause (see /v1/alerts/settings) a flagged key is also paused; action says which happened. The last 30 days, at most 100.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
subject_kind enum
One of: key, agent
subject string

The agent tag, or the first 12 characters of the key hash.

key_name string | null
key_hash string | null

The full hash, for PATCH /v1/keys/{hash}; null for an agent.

key_paused boolean

Whether the key is paused right now (by this flag, a later one, or by hand).

action enum

What the sweep did with this flag.

One of: alert, paused
kind enum
One of: spend, rate
window_start string
window_end string
spend number

US dollars.

usual_spend number

US dollars.

requests integer
usual_requests number
created_at string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Recent alert deliveries

GET /v1/alerts/deliveries

The last 50 delivery attempts, newest first, each with its outcome. Kept for 90 days.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
channel_id string | null

Null once the channel has been removed.

channel_name string | null
channel_kind string | null
event enum
One of: key_limit.threshold, agent_limit.threshold, spend.runaway, test
threshold integer | null
status enum
One of: sent, failed, skipped
http_status integer | null
error string | null
key_name string | null
key_hash_prefix string | null
agent_tag string | null

For an agent-budget alert: the X-AgentsRouter-Agent value.

created_at string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Workspaces

One per client: separate keys, budgets and spend under one account.

List workspaces

GET /v1/workspaces

One workspace per client is the intended use: each has its own keys, budgets and spend, under one account and one balance. spend_this_month is measured the way a workspace budget is -- settled spend in the current UTC calendar month plus requests in flight, BYOK spend at list price included.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
name string
slug string
created_at string
active_keys integer
spend_this_month number

USD, current UTC calendar month, measured as a workspace budget measures it.

budgets object[]
is_current boolean

Whether this is the calling key's own workspace.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Create a workspace

POST /v1/workspaces

At most 200 per organisation. Put keys in it with workspace_id on POST /v1/keys, or move one with PATCH /v1/keys/{hash}.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

name stringrequired

Up to 80 characters, e.g. the client's name.

Responses

201 Created.
id string
name string
slug string
created_at string
active_keys integer
spend_this_month number

USD, current UTC calendar month, measured as a workspace budget measures it.

budgets object[]
is_current boolean

Whether this is the calling key's own workspace.

400 `name is required`, too long, or the 200-workspace cap.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Rename a workspace

PATCH /v1/workspaces/{id}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The workspace id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

name stringrequired

Responses

200 OK.
id string
name string
slug string
created_at string
active_keys integer
spend_this_month number

USD, current UTC calendar month, measured as a workspace budget measures it.

budgets object[]
is_current boolean

Whether this is the calling key's own workspace.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Remove a workspace

DELETE /v1/workspaces/{id}

Refused with 409 while the workspace has an active key, and for the organisation's last workspace. Revoked keys keep their history and lose the link; provider credentials are never removed with a workspace.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The workspace id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
409 The workspace has active keys, or it is the last one.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List a workspace's budgets

GET /v1/workspaces/{id}/budgets

An unknown workspace is an empty list, not a 404.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A workspace id.

Responses

200 OK.
interval enum
One of: daily, weekly, monthly, lifetime
limit_nanodollars string

Nanodollars (1e-9 USD), an integer as a string.

include_byok boolean

Count spend made on your own provider credential toward this ceiling, at IMPUTED LIST PRICE -- what the call would have cost on platform capacity, not the platform fee charged. Defaults to false, and while every model is BYOK-only that means the ceiling counts nothing and cannot bind.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Set a workspace budget

PUT /v1/workspaces/{id}/budgets/{interval}

Budgets must shrink as the interval narrows: a daily budget above the weekly one is refused. Each interval is its own ceiling; they are never summed.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A workspace id.

interval enum · pathrequired

The budget's period.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

limit_usd numberrequired
include_byok boolean

Responses

200 OK.
workspace_id string
interval string
limit_usd number

US dollars.

400 An unknown interval, a negative limit, or a budget larger than a longer interval's.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Delete a workspace budget

DELETE /v1/workspaces/{id}/budgets/{interval}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A workspace id.

interval string · pathrequired

The budget's period.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Status

Measured service status.

Measured uptime, last 90 days

GET /v1/status/uptime

From an external probe, not from traffic: a Cloud Monitoring uptime check requests /health (which queries the database) every minute from four regions. One entry per UTC day, oldest first. A day with fewer than 60 probes is uptime: null -- unmeasured, never counted as up. overall is over measured days, weighted by probes, and null when none is measured. Public, limited per client address, cached for a minute.

Authentication

None: this endpoint is public.

Responses

200 The last 90 days.
days object[]
overall number | null

0 to 1, over measured days.

measured_days integer
429 Too many requests from this address.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Reports

White-label client reports: the agency's branding, and a client's month as numbers.

Report branding

GET /v1/branding

The name, logo and footer printed on client reports in place of ours. Any role may read it.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
org_name string

The organisation's own name; the fallback when brand_name is unset.

brand_name string | null
brand_logo string | null

A PNG or JPEG data URI.

brand_footer string | null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Set report branding

PUT /v1/branding

Owner or Developer. Every field is optional; an absent or empty one clears it. The logo is a PNG or JPEG data URI under 150 KB, checked by its bytes: nothing is ever fetched from a URL, and SVG is refused.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

brand_name string | null

Up to 80 characters.

brand_logo string | null

data:image/png;base64,... or data:image/jpeg;base64,..., under 150 KB.

brand_footer string | null

Up to 200 characters, e.g. a contact line.

Responses

200 OK.
org_name string

The organisation's own name; the fallback when brand_name is unset.

brand_name string | null
brand_logo string | null

A PNG or JPEG data URI.

brand_footer string | null
400 A field too long, or a logo that is not a small PNG or JPEG.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

A client's month, as a report

GET /v1/workspaces/{id}/report

Totals, by model, by agent and by day for one workspace in a UTC calendar month, with the branding to print it under. Amounts are list price -- what the tokens were worth, the number a client is re-billed on and the one budgets measure -- and the same measurement as the CSV export. A usage report, not an invoice: no tax, due date or markup.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The workspace id.

month string · query

YYYY-MM, UTC. Default: the current month.

Responses

200 OK.
workspace object
month string
period object
branding Branding
totals ReportLine
by_model ReportLine[]
by_agent ReportLine[]
by_day ReportLine[]
generated_at string
400 `month must be YYYY-MM`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Members

Team seats. Roles: Owner (everything, including members), Developer (keys, provider keys, limits, workspaces, alerts, agents; reads billing), Finance (usage, exports, credits and checkout; changes nothing else). A management key with no person behind it has full power. Every management route shares one rate limit per key: 300 requests a minute, 20,000 a day (429 past it).

List members

GET /v1/members

Everyone with an active seat in the organisation, owners first, and the caller's own role. Any role may read this.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 The members.
user_id string
role enum

owner = Owner, member = Developer, billing = Finance. What each may do: see the Members tag.

One of: owner, admin, member, billing, readonly
role_name string
joined_at string
name string | null
email string | null
avatar_url string | null
provider string | null
is_you boolean
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Change a member's role

PATCH /v1/members/{userId}

Owners only. The organisation's last owner cannot be demoted (409).

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

userId string · pathrequired

The member's user id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

role enumrequired
One of: owner, member, billing

Responses

200 OK.
user_id string
role string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
409 The last owner.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Remove a member

DELETE /v1/members/{userId}

Owners only. The seat is deactivated and the person's dashboard key in this organisation is revoked, ending their sessions here at once; their other organisations are untouched. The last owner cannot be removed (409).

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

userId string · pathrequired

The member's user id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
removed boolean
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
409 The last owner.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Open invitations

GET /v1/invitations

Owners only. Invitations not yet accepted, revoked or expired. Links are not listed: only their hashes are kept.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
role string
created_at string
expires_at string
created_by_name string | null
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Invite someone

POST /v1/invitations

Owners only. Returns a link, in this response and never again: single use, valid seven days, revocable. The invitee opens it, signs in, and accepts. At most 50 open invitations.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

role enumrequired
One of: owner, member, billing

Responses

201 Created.
id string
role string
created_at string
expires_at string
url string

The invitation link. Store or send it now.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Revoke an invitation

DELETE /v1/invitations/{id}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

The invitation id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
revoked boolean
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

What an invitation is for

POST /v1/invitations/preview

Public, limited per address. The token travels in the body to stay out of access logs. An unknown, used, revoked or expired token all answer the same 404.

Authentication

None: this endpoint is public.

Request body

token stringrequired

Responses

200 OK.
org_name string
role string
role_name string
expires_at string
404 Not found, or not in your organization.
429 Too many requests from this address.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Accept an invitation

POST /v1/invitations/accept

Requires a signed-in PERSON (a dashboard session); an API key has no seat and gets 403. Takes the seat, and replaces the session with one in the inviting organisation (Set-Cookie).

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

token stringrequired

Responses

200 OK.
org_id string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Agents

Agents named in X-AgentsRouter-Agent: their spend, and budgets that bind across every key they use.

List agents

GET /v1/agents

Every agent named in X-AgentsRouter-Agent in the last 31 days, plus any with a budget but no traffic yet: requests and spend this UTC month (list price included, measured as an agent budget measures it) and budgets. Newest first; at most 500.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
agent string
last_seen string | null
requests_this_month integer
spend_this_month number

US dollars.

budgets object[]
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Set an agent budget

PUT /v1/agents/{agent}/budgets/{interval}

A hard ceiling on one agent's spend in a window, across every key it uses: a request naming the agent is refused with 403 budget_exceeded once the agent is at or past it. Narrower windows must have smaller caps. BYOK spend counts at list price by default.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

agent string · pathrequired

The X-AgentsRouter-Agent value.

interval string · pathrequired

daily, weekly, monthly or lifetime.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

limit_usd numberrequired
include_byok boolean
Default: true
mode enum

hard refuses at the ceiling (403 budget_exceeded); soft serves, adds the sentence to x-agentsrouter-limit-warning, and still alerts.

One of: hard, soft
Default: "hard"
workspace_id string | null

Scope the budget to this client workspace: it binds only requests from that workspace's keys. Omitted or null: the whole organisation. Both may exist for one agent and interval.

Responses

200 OK.
agent string
interval string
limit_usd number
include_byok boolean
mode enum
One of: hard, soft
workspace_id string | null
400 A bad agent, interval or amount, or a budget above a wider window's.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Remove an agent budget

DELETE /v1/agents/{agent}/budgets/{interval}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

agent string · pathrequired

The X-AgentsRouter-Agent value.

interval string · pathrequired

The window.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

workspace_id string · query

Remove the budget scoped to this client workspace. Absent: the organisation-wide one.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Guardrails

Policy: allowlists, budgets, privacy and content filters.

Get input/output logging settings

GET /v1/logging

§13.1. An organisation that has never configured this gets the defaults: off, 90-day retention, consented to self_access only.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
enabled booleanrequired

§13.1: off by default. Cannot be set true in this release — gated on the review of PIA-2026-001.

key_include string[]required

Key hashes to capture. Empty means every key.

key_exclude string[]required

Exclusion wins over inclusion — a key in both is not captured.

retention_days integerrequired

At least 90 (§13.1's ~3-month floor). Content past this is deleted hourly.

locked booleanrequired

Settings frozen; further changes refused until cleared.

purposes enum[]required

What this organisation consented to. self_access is its own replay and debugging; rag would be indexing, and is a separate grant because consent is purpose-specific.

consented_at string | null

When logging was enabled. Null while it is off.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Configure input/output logging

PUT /v1/logging

Management key only: an inference key must not be able to change what is recorded about its own traffic.

`enabled: true` is refused in this release. It is gated on the review of PIA-2026-001, whose adequacy conclusion depends on prompt content not being stored. Every other field configures now.

locked: true freezes the settings; further changes are refused until it is cleared — and changing the setting needs the Owner or Developer role (a management key with no person behind it has full power).

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

enabled boolean

Refused when true in this release.

key_include string[]

Key hashes to capture. Empty means every key.

key_exclude string[]

Exclusion wins over inclusion — a key in both is not captured.

retention_days integer

§13.1's ~3-month floor.

locked boolean
purposes enum[]

What the organisation consents to. Must include self_access; to stop storing, disable logging instead. Consent is purpose-specific — agreeing that we store your prompts so you can replay them is not agreeing that we may index them.

Responses

200 OK.
enabled booleanrequired

§13.1: off by default. Cannot be set true in this release — gated on the review of PIA-2026-001.

key_include string[]required

Key hashes to capture. Empty means every key.

key_exclude string[]required

Exclusion wins over inclusion — a key in both is not captured.

retention_days integerrequired

At least 90 (§13.1's ~3-month floor). Content past this is deleted hourly.

locked booleanrequired

Settings frozen; further changes refused until cleared.

purposes enum[]required

What this organisation consented to. self_access is its own replay and debugging; rag would be indexing, and is a separate grant because consent is purpose-specific.

consented_at string | null

When logging was enabled. Null while it is off.

400 A refused `enabled: true`, a locked organisation, a retention below the floor, or an unknown purpose.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List guardrails

GET /v1/guardrails

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
name string
model_allowlist string[]
provider_allowlist string[]
allowed_data_regions string[]

Regions an endpoint may run in, matched exactly against the endpoint deployment_region field and lower-cased on write. Empty is unrestricted. This fails CLOSED: an endpoint that declares no region is not in one, so it is refused. A region no ready endpoint declares is rejected at write time rather than refusing every request later.

budget_nanodollars string | null

Nanodollars (1e-9 USD), an integer as a string.

budget_reset string | null
include_byok_spend boolean

Count spend made on your own provider credential toward this ceiling, at IMPUTED LIST PRICE -- what the call would have cost on platform capacity, not the platform fee charged. Defaults to false, and while every model is BYOK-only that means the ceiling counts nothing and cannot bind.

zdr_flags object
content_filter_builtins string[]
content_filters ContentFilter[]
filter_allowlist string[]
created_at string
assignments object[]
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Create a guardrail

POST /v1/guardrails

Assign it to a key to take effect. Policy merges organization, then workspace, then key, and each level can only narrow what the one above allows (§4.2).

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

name stringrequired
limit_usd number

A budget in USD, evaluated per key.

reset_interval enum
One of: daily, weekly, monthly, lifetime
allowed_models string[]
allowed_providers string[]
allowed_data_regions string[]

Regions an endpoint may run in, matched exactly against the endpoint deployment_region field and lower-cased on write. Empty is unrestricted. This fails CLOSED: an endpoint that declares no region is not in one, so it is refused. A region no ready endpoint declares is rejected at write time rather than refusing every request later.

include_byok_in_budgets boolean

Count spend made on your own provider credential toward this ceiling, at IMPUTED LIST PRICE -- what the call would have cost on platform capacity, not the platform fee charged. Defaults to false, and while every model is BYOK-only that means the ceiling counts nothing and cannot bind.

enforce_zdr object
content_filter_builtins enum[]
content_filters ContentFilter[]
filter_allowlist string[]

Phrases a filter must never act on.

Responses

201 Created.
id string
name string
budget_nanodollars string | null

Nanodollars (1e-9 USD), an integer as a string.

budget_reset string | null
content_filter_builtins string[]
content_filters ContentFilter[]
filter_allowlist string[]
400 `name is required`, or the filter policy is invalid; the message names the filter.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Delete a guardrail

DELETE /v1/guardrails/{id}

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A guardrail id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Responses

200 OK.
deleted anyrequired
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Assign a guardrail

POST /v1/guardrails/{id}/assign

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

id string · pathrequired

A guardrail id.

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

subject_type enumrequired
One of: key, member
subject_id stringrequired

For a key, its hash.

Responses

200 OK.
guardrail_id string
subject_type string
subject_id string
400 `subject_type and subject_id are required`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Auth

Dashboard sign-in.

The organisations I belong to

GET /v1/auth/orgs

Every organisation the signed-in person has an active seat in, with their role and which one this session is in.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 OK.
id string
name string
role string
role_name string
current boolean
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Switch organisation

POST /v1/auth/switch

Moves the session into another organisation the person has a seat in (Set-Cookie). 404 for one they do not.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

org_id stringrequired

Responses

200 OK.
org_id string
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get the current session

GET /v1/auth/session

Authentication

Dashboard session

Responses

200 OK.
expires_at string
org_id string
key_name string | null
user object | null
auth_method string
401 No session, or it is invalid or has expired. An invalid one also clears the cookies.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Exchange a management key for a dashboard session

POST /v1/auth/session

Sets the httpOnly ar_session cookie and the script-readable ar_csrf cookie. Rate limited per client address.

Authentication

None: this endpoint is public.

Request body

key stringrequired

A management key.

Responses

200 OK.
expires_at string
org_id string
key_name string | null
user object | null
auth_method string
400 `key is required`.
401 `Invalid credentials` -- for any failure, a wrong key class included.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Sign out

DELETE /v1/auth/session

Always succeeds, and clears both cookies.

Authentication

Dashboard session

Responses

200 OK.
ended anyrequired
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Describe an OAuth PKCE authorization request

GET /v1/auth/authorize

What the consent screen at /auth renders. Public and rate limited: it validates the query string and echoes back the terms a key issued under it would carry. It reads nothing about any account, so a malformed request is refused before the user is asked to sign in.

Authentication

None: this endpoint is public.

Parameters

callback_url string · query

Where to return the code. Omit for the headless flow, which then requires S256 and key_label. https only, except on localhost.

code_challenge string · query

The PKCE challenge, 43-128 characters.

code_challenge_method enum · query

Must be S256, sent explicitly. plain and none are refused for every flow, redirect included: a code returned to a localhost callback can be read by any process on that machine, and only a hashed challenge keeps it from minting a key there (RFC 7636 §7.2).

key_label string · query

Names the issued key on the Keys screen. Defaults to the callback host.

Responses

200 OK.
callback_url string | nullrequired

Where the code will be returned. Null for the headless flow.

host string | nullrequired

The callback's host — what the consent screen puts in front of the user. Null when headless, and then key_label is the only name there is.

headless booleanrequired

No callback URL: the code is displayed for manual entry. Requires S256 and key_label.

key_label stringrequired

Names the issued key on the Keys screen, so access can be revoked by app.

code_challenge_method enumrequired

Always S256; plain and none are refused for every flow.

One of: S256
limit numberrequired

The spend ceiling the issued key will carry. Served rather than hardcoded in the consent screen, so the terms shown are the terms applied.

expires_in_days integerrequired

How long the issued key will last.

400 An invalid `code_challenge_method`, a challenge of the wrong length, or a headless request missing `S256` or `key_label`.
405 The callback URL is not `https` (and is not localhost), or uses a scheme other than http/https. s16.2 specifies 405 here rather than 400.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Approve an app and mint a one-time code

POST /v1/auth/authorize

Called by the consent screen once the user agrees. The request is re-validated from the body: the preceding GET is a convenience and proves nothing about this call. Codes are single-use and expire in ten minutes.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Parameters

x-csrf-token string · header

Required with a session cookie on any method but GET: the value of the ar_csrf cookie. Not used with a bearer key.

Request body

callback_url string
code_challenge string
code_challenge_method enum
One of: S256, plain, none
key_label string

Responses

200 OK.
code stringrequired

The one-time code. Ten minutes, single use.

redirect_to string | nullrequired

The callback URL with code appended, preserving any query the app already put there. Null when headless.

expires_at stringrequired
400 As for the `GET`.
401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
405 As for the `GET`.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Exchange a PKCE code for an API key

POST /v1/auth/keys

Unauthenticated by design -- the caller is an app that has no key yet, and the code plus its verifier are the credential. This is the one route that answers cross-origin (Access-Control-Allow-Origin: *), because a single-page app is exactly the public client PKCE exists for. Access-Control-Allow-Credentials is never set, so no cookie can ride along.

The code is consumed the moment it is looked up, including when verification then fails: a caller gets one attempt at the verifier, not a grinding window (RFC 6749 §4.1.2). The issued key is an inference key carrying a spend ceiling and an expiry, and is revocable from the Keys screen like any other.

Authentication

None: this endpoint is public.

Request body

code stringrequired

The code from the callback, or pasted from the headless flow.

code_verifier string

The PKCE verifier. Required unless the code was issued under none.

code_challenge_method enum

Optional. If sent it must MATCH the method the code was issued under; it never replaces it.

One of: S256, plain, none

Responses

200 OK.
key stringrequired

The inference key. In this response and never again — only a salted hash is stored.

hash stringrequired

Identifies the key for revocation.

label stringrequired

The app this key was issued to.

limit number | nullrequired

The spend ceiling, in US dollars.

expires_at stringrequired
400 `code is required`, or a `code_challenge_method` that disagrees with the stored one.
403 `code is invalid, already used, or expired` -- one message for all three, so a guess cannot confirm that a code exists -- or `code_verifier does not match the code_challenge`.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

List the sign-in methods this deployment offers

GET /v1/auth/methods

Authentication

None: this endpoint is public.

Responses

200 OK.
management_key any
oauth enum[]
wallet boolean
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Start signing in with Google or GitHub

GET /v1/auth/oauth/{provider}/start

A browser navigation, not an API call: redirects to the provider with PKCE and a state cookie (ar_oauth, ten minutes).

Authentication

None: this endpoint is public.

Parameters

provider enum · pathrequired

The identity provider.

Responses

302 To the provider's consent screen.
404 Not found, or not in your organization.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

The OAuth redirect target

GET /v1/auth/oauth/{provider}/callback

Always redirects: to /workspace with a session on success (?welcome=1 for a new user), otherwise to /signin?error= with one of cancelled, provider_error, expired, state_mismatch, no_code, not_configured, exchange_failed.

Authentication

None: this endpoint is public.

Parameters

provider enum · pathrequired

The identity provider.

code string · query

The authorization code.

state string · query

Must match the ar_oauth cookie.

error string · query

Set by the provider when sign-in did not happen.

Responses

302 To the dashboard.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Start signing in with a wallet

POST /v1/auth/wallet/nonce

Returns a Sign-In with Ethereum message to sign. The challenge lasts five minutes.

Authentication

None: this endpoint is public.

Request body

address stringrequired

Responses

200 OK.
message string
nonce string
expires_at string
400 `address must be a 0x-prefixed 20-byte hex string`.
404 Not found, or not in your organization.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Finish signing in with a wallet

POST /v1/auth/wallet/verify

Sets the session cookies on success.

Authentication

None: this endpoint is public.

Request body

address stringrequired
signature stringrequired
nonce stringrequired

Responses

200 OK.
expires_at string
org_id string
is_new_user boolean
400 A field is missing, or no valid challenge exists for this address and nonce: unknown, expired, or already used. The challenge is consumed by the attempt either way.
401 `Signature does not match the address`.
404 Not found, or not in your organization.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Runs

Agent runs (§17.1): one goal, many turns, one budget enforced across all of them.

List runs

GET /v1/runs

Newest first, for the calling key's organization.

Authentication

Inference key (ar-v1-…)

Parameters

limit integer · query

Page size, capped at 100.

Responses

200 OK.
id stringrequired
status enumrequired

exhausted is set by settlement when spend reaches the budget, and paused by the §17.4 runaway detector. A caller may set only completed or cancelled — a client able to claim either of the other two could report a breach that never happened.

paused is the one non-open status that is recoverable: see POST /v1/runs/{id}/resume.

One of: open, paused, completed, exhausted, cancelled
goal string | null
budget numberrequired

The ceiling. Required at creation, always positive: a run without one is what §17.1 exists to prevent.

spent numberrequired

Settled. Imputed list price — what the turns were worth at catalog prices, not money billed through us. Under the BYOK-only policy inference is billed by your own provider, and a run sized on our fee would report every turn as free (§9.6).

reserved numberrequired

Worst-case imputed cost committed by turns admitted but not yet settled. Published because budget - spent alone overstates the headroom a run actually has.

remaining numberrequired

budget - spent - reserved.

max_turns integer | null
turns_used integerrequired
max_tool_calls integer | null
tool_calls_used integerrequired
model_policy object
max_cost_per_minute number | null

§17.4 threshold, in USD. Null means the cost-velocity detector is off for this run.

max_tool_calls_without_progress integer | null

§17.4 threshold. Null means the no-progress detector is off for this run.

tool_calls_without_progress integer

Consecutive tool calls with no answering turn between them. Reset to 0 by any turn finishing with something other than tool_calls.

pause_reason string | null

Which detector paused this run. Set only by settlement — the actionable half, since a paused run without a stated cause leaves you guessing which threshold to raise.

One of: cost_velocity, no_progress, null
paused_at string | null
created_at string
updated_at string
closed_at string | null
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Open a run

POST /v1/runs

A run is one goal across many turns, with one budget enforced across all of them. Pass its id as run_id on each completion.

Inference class, not management: the thing that opens a run is the agent, and requiring a management key would hand every agent the credential that mints keys and moves budgets. The run is scoped to the organization, so a supervisor and its workers may hold different keys and share one.

Authentication

Inference key (ar-v1-…)

Request body

goal string

Free text, at most 2000 characters.

budget_usd numberrequired

Required, and positive. There is no way to open an uncapped run.

max_turns integer

Bounds how many times the agent may ask. A failed turn refunds its money but not its turn — see packages/docs/RUNS.md.

max_tool_calls integer

Checked at turn admission, so a single turn can exceed it by its own call count.

model_policy object
max_cost_per_minute_usd number

§17.4 runaway detector, opt-in. Pauses the run when spend inside a tumbling one-minute window exceeds this. Omit to leave it off.

The window is tumbling, not sliding: a burst straddling a boundary can be split across two windows and escape. Acceptable for a safety net whose job is to stop a loop running overnight; it is not a billing control.

max_tool_calls_without_progress integer

§17.4 runaway detector, opt-in. Pauses the run after this many consecutive tool calls with no answering turn between them — "progress" meaning a turn that finished with something other than tool_calls. Omit to leave it off.

Responses

201 Opened.
id stringrequired
status enumrequired

exhausted is set by settlement when spend reaches the budget, and paused by the §17.4 runaway detector. A caller may set only completed or cancelled — a client able to claim either of the other two could report a breach that never happened.

paused is the one non-open status that is recoverable: see POST /v1/runs/{id}/resume.

One of: open, paused, completed, exhausted, cancelled
goal string | null
budget numberrequired

The ceiling. Required at creation, always positive: a run without one is what §17.1 exists to prevent.

spent numberrequired

Settled. Imputed list price — what the turns were worth at catalog prices, not money billed through us. Under the BYOK-only policy inference is billed by your own provider, and a run sized on our fee would report every turn as free (§9.6).

reserved numberrequired

Worst-case imputed cost committed by turns admitted but not yet settled. Published because budget - spent alone overstates the headroom a run actually has.

remaining numberrequired

budget - spent - reserved.

max_turns integer | null
turns_used integerrequired
max_tool_calls integer | null
tool_calls_used integerrequired
model_policy object
max_cost_per_minute number | null

§17.4 threshold, in USD. Null means the cost-velocity detector is off for this run.

max_tool_calls_without_progress integer | null

§17.4 threshold. Null means the no-progress detector is off for this run.

tool_calls_without_progress integer

Consecutive tool calls with no answering turn between them. Reset to 0 by any turn finishing with something other than tool_calls.

pause_reason string | null

Which detector paused this run. Set only by settlement — the actionable half, since a paused run without a stated cause leaves you guessing which threshold to raise.

One of: cost_velocity, no_progress, null
paused_at string | null
created_at string
updated_at string
closed_at string | null
400 `budget_usd is required and must be a positive number`, or a malformed cap or `model_policy`.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Get a run and its tree

GET /v1/runs/{id}

§17.1's tree: turns, per-turn cost, cache hit rate, failed tool calls, model switches and a terminal status.

Authentication

Inference key (ar-v1-…)

Parameters

id string · pathrequired

The run id.

Responses

200 OK.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
404 Not found, or not in your organization.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Close a run

POST /v1/runs/{id}/close

Terminal. Closing an already-closed run is a 409 naming its current status, never a silent no-op.

Authentication

Inference key (ar-v1-…)

Parameters

id string · pathrequired

The run id.

Request body

status enum
One of: completed, cancelled
Default: "completed"

Responses

200 OK.
id stringrequired
status enumrequired

exhausted is set by settlement when spend reaches the budget, and paused by the §17.4 runaway detector. A caller may set only completed or cancelled — a client able to claim either of the other two could report a breach that never happened.

paused is the one non-open status that is recoverable: see POST /v1/runs/{id}/resume.

One of: open, paused, completed, exhausted, cancelled
goal string | null
budget numberrequired

The ceiling. Required at creation, always positive: a run without one is what §17.1 exists to prevent.

spent numberrequired

Settled. Imputed list price — what the turns were worth at catalog prices, not money billed through us. Under the BYOK-only policy inference is billed by your own provider, and a run sized on our fee would report every turn as free (§9.6).

reserved numberrequired

Worst-case imputed cost committed by turns admitted but not yet settled. Published because budget - spent alone overstates the headroom a run actually has.

remaining numberrequired

budget - spent - reserved.

max_turns integer | null
turns_used integerrequired
max_tool_calls integer | null
tool_calls_used integerrequired
model_policy object
max_cost_per_minute number | null

§17.4 threshold, in USD. Null means the cost-velocity detector is off for this run.

max_tool_calls_without_progress integer | null

§17.4 threshold. Null means the no-progress detector is off for this run.

tool_calls_without_progress integer

Consecutive tool calls with no answering turn between them. Reset to 0 by any turn finishing with something other than tool_calls.

pause_reason string | null

Which detector paused this run. Set only by settlement — the actionable half, since a paused run without a stated cause leaves you guessing which threshold to raise.

One of: cost_velocity, no_progress, null
paused_at string | null
created_at string
updated_at string
closed_at string | null
400 `status must be one of: completed, cancelled`.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
404 Not found, or not in your organization.
409 The run is already `completed`, `cancelled` or `exhausted`. The body reports `code: 400` and `invalid_request`, although the status is 409.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Resume a run the runaway detector paused

POST /v1/runs/{id}/resume

The counterpart to pausing rather than cancelling (§17.4): the detector stops a run so a human can look at it, and this is how they say "I looked, carry on".

Clears the counters that caused the pause — without that it would pause again on the very next settlement and this call would appear to do nothing. The budget and turn counts are deliberately not reset: those are the caps the run was opened under, and a pause is not an amnesty.

Only a paused run resumes. exhausted and cancelled are genuinely finished, and resuming those would be a way to reopen a budget that has already bound.

Authentication

Inference key (ar-v1-…)

Parameters

id string · pathrequired

The run id.

Responses

200 OK.
id stringrequired
status enumrequired

exhausted is set by settlement when spend reaches the budget, and paused by the §17.4 runaway detector. A caller may set only completed or cancelled — a client able to claim either of the other two could report a breach that never happened.

paused is the one non-open status that is recoverable: see POST /v1/runs/{id}/resume.

One of: open, paused, completed, exhausted, cancelled
goal string | null
budget numberrequired

The ceiling. Required at creation, always positive: a run without one is what §17.1 exists to prevent.

spent numberrequired

Settled. Imputed list price — what the turns were worth at catalog prices, not money billed through us. Under the BYOK-only policy inference is billed by your own provider, and a run sized on our fee would report every turn as free (§9.6).

reserved numberrequired

Worst-case imputed cost committed by turns admitted but not yet settled. Published because budget - spent alone overstates the headroom a run actually has.

remaining numberrequired

budget - spent - reserved.

max_turns integer | null
turns_used integerrequired
max_tool_calls integer | null
tool_calls_used integerrequired
model_policy object
max_cost_per_minute number | null

§17.4 threshold, in USD. Null means the cost-velocity detector is off for this run.

max_tool_calls_without_progress integer | null

§17.4 threshold. Null means the no-progress detector is off for this run.

tool_calls_without_progress integer

Consecutive tool calls with no answering turn between them. Reset to 0 by any turn finishing with something other than tool_calls.

pause_reason string | null

Which detector paused this run. Set only by settlement — the actionable half, since a paused run without a stated cause leaves you guessing which threshold to raise.

One of: cost_velocity, no_progress, null
paused_at string | null
created_at string
updated_at string
closed_at string | null
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
404 Not found, or not in your organization.
409 The run is not paused. The body reports `code: 400` and `invalid_request`, although the status is 409.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

MCP

Model Context Protocol, for coding agents. Every tool adapts a route documented above.

Not offered: this server has no server-initiated stream

GET /v1/mcp

Always 405. MCP's transport requires that status from a server offering no SSE stream, and it is answered deliberately rather than left to a 404, because a client probing for a stream must read "not offered" rather than "wrong URL".

Authentication

None: this endpoint is public.

Responses

405 `This MCP server offers no server-initiated stream; use POST.` The body reports `code: 400` and `invalid_request`, although the status is 405.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Call the MCP server

POST /v1/mcp

JSON-RPC 2.0 over MCP's Streamable HTTP transport, protocol revision 2025-06-18. Methods: initialize, tools/list, tools/call, ping, and the notifications/* messages.

Every tool is an adapter over a route documented elsewhere in this file, dispatched through this gateway's own router — so an MCP call passes the same authentication, rate limits, spend ceilings, guardrails and ledger as the HTTP call it stands for. See packages/docs/MCP.md.

Requires an inference key; a management key gets 403, as it does on /v1/chat/completions. The OAuth PKCE flow (/auth) is the intended way for a coding tool to obtain one.

Stateless: no Mcp-Session-Id, because several instances run behind a load balancer with no sticky routing. JSON-RPC batching is refused — revision 2025-06-18 removed it.

Authentication

Inference key (ar-v1-…)

Request body

jsonrpc anyrequired
id string | integer | null

Omit the field entirely for a notification, which is answered with 202 and no body. An explicit null is a request, not a notification.

method stringrequired

e.g. tools/call.

params object

Responses

200 A JSON-RPC response. A TOOL that failed returns a success envelope with `result.isError: true`; `error` is reserved for protocol faults — a malformed request, an unknown method, an unknown tool.
jsonrpc anyrequired
id string | integer | nullrequired
result object

For tools/call: content (an array of text blocks) and isError.

error object
202 A notification was accepted. No body, by design: a client must not try to correlate a response to a request it never made.
401 No key; an invalid, disabled or expired key; or an invalid session.
403 A management key, or a dashboard session where only an inference key will do; or a session request without a valid `x-csrf-token`. The body reports `code: 403` and `permission_denied`. (Until 2026-09-27 it carried the 401 envelope; `ERRORS.fromAuth` now decides the envelope from the status.)
429 `MCP rate limit exceeded`. Every POST counts against a per-key MCP quota of 300 a minute and 50,000 a day, separate from the inference quota that a `tools/call` also meets inside the route it calls. The `X-RateLimit-*` headers are on every response.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Meta

Health, region, and this document.

Health

GET /health

Public; for load balancers and uptime checks. A 500 means the database is unreachable.

Liveness, the version this document already publishes, and whether the rate limiter is shared across instances — nothing here varies with account activity. The cache statistics that used to be served here are at GET /v1/health, behind a management key.

Authentication

None: this endpoint is public.

Responses

200 Serving.
status anyrequired
version string

The same value this document publishes as info.version.

rate_limiting object
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Operational diagnostics

GET /v1/health

Everything /health reports, plus the hot-path cache counters and the age of the routing health snapshot.

Behind a management key of an operator organisation (OPERATOR_ORG_IDS): it describes the deployment rather than serving a model, and its counters move with every tenant's traffic, so any other management key receives 404. Unlike /health it is not exempt from the jurisdiction gate — it is authenticated and for operators, so a region we decline to serve reaches nothing here.

Authentication

Management key (ar-mgmt-…) · Dashboard session

Responses

200 Serving, with diagnostics.
status anyrequired
version string
rate_limiting object
caches object

Hit, miss, stale and size counters for the hot-path caches (auth, guardrails, budgets, byok, catalog). Authenticated because the first four move with real account activity: served publicly they were a side channel anyone could poll.

endpoint_health_age_ms integer | null

Age of the routing health snapshot. Growing without bound means the refresher has died and routing is deciding on stale tiers — which looks exactly like healthy routing.

401 No key; an invalid, disabled or expired key; or an invalid session. The body reports `code: 401` and `invalid_credentials`. (Until 2026-09-27 every management route sent the 403 envelope here; `requireManagementKey` in lib/auth.ts now decides the envelope from the status.)
403 An inference key on a management route, or a session request without a valid `x-csrf-token`.
404 Not an operator organisation: the key is valid but its organisation is not in `OPERATOR_ORG_IDS`. The same 404 as an unknown route, so a customer learns nothing about the surface.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

Receive a Content-Security-Policy violation report

POST /v1/csp-report

For browsers, not for you: the dashboard's CSP names this path in report-uri. Accepts the legacy application/csp-report document and the Reporting API's application/reports+json array; the violation's document, directive, blocked URI and source are logged (each clipped to 200 characters, at most ten per report) and nothing is stored. Unauthenticated and rate-limited per address like the catalog.

Authentication

None: this endpoint is public.

Responses

204 Logged.
429 Rate limited. `X-RateLimit-Reset` says when the window resets.
451 Refused by jurisdiction: the edge reported a subdivision this service does not operate in yet. **`code` is a string here, and there is no `metadata`.**
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

The caller's coarse region

GET /v1/geo

As the edge reported it. The dashboard uses it to choose a language, so it is never refused by region.

Authentication

None: this endpoint is public.

Responses

200 OK.
region string | null
subdivision string | null
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

This document, as JSON

GET /openapi.json

Authentication

None: this endpoint is public.

Responses

200 The OpenAPI 3.1 document.
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

This document, as YAML

GET /openapi.yaml

Authentication

None: this endpoint is public.

Responses

200 The OpenAPI 3.1 document.
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

This document, as JSON

GET /v1/openapi.json

Authentication

None: this endpoint is public.

Responses

200 The OpenAPI 3.1 document.
500 An unexpected failure. `metadata.request_id` identifies it in our logs.

This document, as YAML

GET /v1/openapi.yaml

Authentication

None: this endpoint is public.

Responses

200 The OpenAPI 3.1 document.
500 An unexpected failure. `metadata.request_id` identifies it in our logs.