ClawRouter serves discovery, proxy, session, and administration APIs from one Worker. This page lists the HTTP surface; deployment and resource configuration live in Deploy ClawRouter on Cloudflare, and state ownership and failure behavior live in Architecture.
Proxy credentials use Authorization: Bearer <clawrouter-key>. Authentication resolves the issued credential and its policy from the serialized ACCESS_CONTROL Durable Object authority before any provider secret is used.
Browser session, playground, and OAuth routes require a verified Cloudflare Access session. Admin routes accept that verified session for configured admins or Authorization: Bearer <admin-token> against CLAWROUTER_ADMIN_TOKEN_SHA256. Admin status does not grant provider access, and provider access does not grant admin status. A pool-submission route accepts only its one-time scoped ticket; neither proxy nor admin credentials are interpreted there.
The Docker self-hosting profile has no Cloudflare Access identity. Its admin API uses the bearer token, and clients use normal proxy credentials.
The optional /private/v1/{models,catalog,responses} facade has its own pinned owner or isolated-workload authentication and never accepts generic proxy/admin credentials. See the private alias contract for exact paths, configuration, protocol limits, and isolation requirements. It is absent from public discovery.
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/health |
Service health, application version, and observability mode |
GET |
/v1/providers |
Compiled provider snapshot |
GET |
/v1/routes |
Compiled OpenAI-compatible, manifest, and native route catalog |
GET |
/v1/models |
Model list scoped to the authenticated credential or session |
GET |
/v1/catalog |
Authorized configured providers and operation/model/transport offers |
GET |
/v1/me |
Proxy-credential identity and policy summary |
GET |
/v1/usage |
Caller policy or principal budget and usage summary |
GET |
/v1/pool |
Opt-in policy pool inventory, quota eligibility and seven-day grant/lane totals |
GET |
/v1/key/inspect |
Proxy-credential verification and readiness status |
Usage summaries, provider totals, and daily totals remain shared across each
authorized policy. The budget follows the policy's configured policy or principal
scope. Recent usage.events on /v1/usage and /v1/session/usage contain only
events attributed to the authenticated principal. A service key without a
principal sees only events with its exact credential ID and no principal.
Unattributed historical events without that credential ID remain admin-only;
reassigning a credential does not transfer a previous principal's event history.
This tightens earlier releases' policy-wide recent-event visibility. Administrators
use /v1/admin/usage for the complete audit; their personal session endpoint
still returns only their own events. Retained request content remains admin-only.
GET /v1/catalog is the client integration contract. It reports scope
(auth type, credential, principal) and observedAt. Only authorized configured
providers appear; configured but unavailable rows remain inspectable. Static
/v1/providers and /v1/routes describe registration, not caller eligibility.
Use those endpoints for setup inventory; client catalogs can omit unconfigured
providers.
nativeBaseUrl preserves the v1 route-location string: /v1/native/<provider>,
or /v1 for Fusion, including in session catalogs. It is legacy location
metadata, not authorization or transport eligibility. Consumers should select a
matching eligible offer under the returned scope, rather than infer access
from nativeBaseUrl, routes, or provider-wide readiness.
Each provider's offers identifies an endpoint, model (or an operation form
without a selected model), route, transport, selected policy and generation,
eligible, and an optional reasonCode. affordability is exact-covered, exact-blocked, or
request-dependent: fixed tariffs can be compared with observed balances,
while token-priced requests depend on their actual input and parameters. Free
token counting and declared zero-price operations remain available at exhausted
positive limits. A configured zero limit still blocks ordinary requests.
Affordability describes the plain operation; request parameters and hosted
tools are assessed again at dispatch. A form without a selected model remains
request-dependent when an eligible declared model is available; it does not
make a model-less or opaque request priced.
The projection observes the actual principal's policy ledger and the provider ledger without reserving budget, selecting credentials, refreshing accounts, or probing upstream. Observations are advisory; HTTP dispatch and every WebSocket create authenticate, check current grants, and reserve budget independently. Policy order is chosen by grant/transport eligibility before price or budget; an exhausted selected policy never causes a switch to a richer policy. Within that policy, deterministic credential/configuration checks precede grant priority, so an unusable account cannot displace a configured sibling.
Session offers target HTTP playground routes only. A session catalog cannot
certify an issued key's native or WebSocket access; fetch the catalog with that
key. The same session projection appears in /v1/session's
entitlements.catalog and in /v1/entitlements's catalog.
Policies without tenantId now use the dispatch/ledger tenant default
consistently during Access selection and discovery, instead of inheriting the
session tenant only in those two views.
/v1/models uses the same eligible model projection. Grant-pool ownership stays
unchanged: when canonical selection reports a configured pool with no available
candidate, discovery does not reopen environment authorization. Fusion's
advertised model uses the same concrete aggregator eligibility.
Fusion emits one HTTP clawrouter/fusion offer for the synthesizer's selected
policy and generation. Its route is /v1/chat/completions for a key or
/v1/playground/v1/chat/completions for a session. A sibling native or WebSocket
operation cannot make that Chat offer eligible. Fixed-price observations account
for the synthesizer first and shared adviser budgets; if only a subset of
advisers may fit, the usable offer remains request-dependent. Unavailable
advisers fail open. These observations do not replace request-time admission or
the separate administrator preview of an unsaved Fusion configuration.
Proxy (including native), admin, and pool-submission route identifiers are
decoded once. Invalid percent escapes or invalid percent-encoded UTF-8 return
HTTP 400 with invalid_path_encoding, after applicable authentication checks.
Semantic identifier and path validation still applies after decoding.
| Method | Path | Contract |
|---|---|---|
POST |
/v1/chat/completions |
OpenAI-compatible chat routing |
POST |
/v1/responses |
OpenAI Responses routing |
GET |
/v1/responses/<response-id> |
Retrieve or resume an originally authorized response |
POST |
/v1/responses/<response-id>/cancel |
Bodyless cancellation on the original response owner |
GET upgrade |
/v1/responses, qualified native Responses paths |
Authenticated, bounded Responses WebSocket sessions |
POST |
/v1/embeddings |
OpenAI-compatible embeddings routing |
POST |
/v1/messages |
Anthropic Messages routing |
POST |
/v1/messages/count_tokens |
Anthropic token counting |
| manifest-defined | /v1/proxy/<provider>/<endpoint> |
Manifest request, query, header, path, and auth mapping |
| manifest-defined | /v1/native/<provider>/<provider-native-path> |
Provider-native request and response formats |
OpenAI-compatible requests select a provider-qualified model in the request body, for example openai/gpt-4.1-mini. Native and manifest routes resolve the provider and endpoint from the compiled snapshot instead of accepting arbitrary upstream URLs.
Unified routes require matching OpenAI request and response formats. A provider's
Chat or embeddings capability alone is insufficient: use its native route when
the protocol differs, such as Cohere's /v2/embed with texts input.
Native routes preserve the selected provider's upstream model namespace. For example, openai/gpt-6-astra in an OpenRouter request remains an OpenRouter model identifier. Known models must support the selected endpoint. Caller-supplied unknown models require that endpoint's explicit modelPassthrough declaration; this preserves opaque model selection on the bundled model-provider routes without claiming model availability or copying another model's capabilities. Such models remain absent from discovery and unpriced unless the endpoint declares an applicable pricing reference. The local Chat endpoint explicitly retains its zero API charge. Either a policy or provider budget requires declared pricing or an explicit fixed request tariff before dispatch.
Opaque native IDs are preserved even when they begin with the provider's routing
prefix. For example, use openrouter/free on the native OpenRouter Chat route;
the same model on unified Chat is openrouter/openrouter/free.
Native Responses JSON and SSE routes include:
| Provider | ClawRouter path | Upstream contract |
|---|---|---|
| Azure OpenAI | /v1/native/azure-openai/openai/v1/responses |
Deployment name in model; endpoint and API key required; no inherited dated api-version |
| OpenRouter | /v1/native/openrouter/v1/responses |
OpenRouter model identifier in model; bearer credential required; OPENROUTER_SITE_URL attribution optional |
Azure's legacy deployment chat and embeddings routes still require AZURE_OPENAI_API_VERSION. The default azure-openai/deployment model is listed only when AZURE_OPENAI_DEPLOYMENT is configured; explicit native deployment routes remain available without that default. The placeholder Azure deployment and OpenRouter auto catalog entries do not attest a particular model's Responses support or price. See the upstream Azure Responses contract and OpenRouter Responses contract; operators must verify model access with their own provider account.
A manifest proxy request for Tavily looks like this:
curl "$CLAWROUTER_BASE_URL/v1/proxy/tavily/search" \
-H "authorization: Bearer $CLAWROUTER_KEY" \
-H "content-type: application/json" \
--data '{"body":{"query":"openclaw"},"query":{"topic":"news"}}'clawrouter/fusion is an optional virtual model on /v1/chat/completions. It fans a bounded text-only prompt out to configured adviser models and asks one configured synthesizer for the final response. Every subrequest uses normal policy, budget, readiness, retention, and usage-accounting paths. See Fusion routing.
Responses HTTP/SSE and WebSocket routes preserve upstream previous_response_id and
x-codex-turn-state bytes. The router binds returned identities to the caller's
authorization scope, provider route, and credential owner before publishing them.
Every continuation still checks current authorization, grant eligibility, and
budgets. It cannot fail over to another account. Ordinary stateless requests keep
their configured pool routing and failover behavior.
Owner-controlled token refresh preserves the binding. Replacing credentials or
account identity, revoking a grant, changing the route, or losing the original
owner requires a restart. HTTP 409 continuation_restart_required means resend
full input without either continuation field. Unknown or expired identities are
rejected before upstream dispatch, including environment/API-key routes. This
also applies after upgrading: state created before this binding contract must be
restarted with full input. Stateless requests and known environment/API-key
continuations remain supported.
Bindings expire 30 days after first publication, independently of upstream state
retention. Each authorization scope admits up to one million hashed identities;
it never evicts a live binding to admit another. Binding-store failure or capacity
exhaustion returns continuation_unavailable before headers, or terminates an
already-started stream. The upstream call can still incur charges. Response IDs
are limited to 256 UTF-8 bytes and turn state to 8 KiB. Output/frame size is not
limited by HTTP identity observation, and raw identities and model output are not
stored in this index.
HTTP/SSE and WebSocket requests with previous_response_id consume the parent's
durable tool-pricing evidence before budget reservation. Only a final token_only
fact allows ordinary token pricing, subject to the current request. Retained
hosted fees or server work and unknown ancestry return pricing_required under
either policy or provider monthly budgets unless a fixed policy tariff applies.
Fixed tariffs and unmetered requests retain their existing behavior.
Deploy the proof producer before the consumer; upgrading does not establish that
old writers have drained. Legacy, pending, corrupt, future-version, and opaque
history remain unqualified, and a later proof cannot clear the unknown fact already
captured by a request. Turn-state aliases establish routing affinity, not tool
pricing. Full-input conversation and item_reference state, encrypted compaction
witnesses, and complete compaction metering remain separate work; no compact route
is added. See spend control.
Reusing a response identity belonging to another
producer fails publication with continuation_unavailable; resend full input.
This also applies to older claim-less writers encountering qualified-producer
rows. Transient proof-finalization failure after a confirmed identity claim can
preserve delivery and accounting while leaving evidence unqualified.
This contract covers previous_response_id and Codex turn state. Responses
conversation selectors remain an unpinned, separate contract gap; do not rely on
pooled account affinity for them. WebSockets also use the connection contract below.
HTTP endpoint deadlines start after preflight and end after response normalization, before continuation-header registration and body delivery. For streaming SSE requests, normalization includes bounded first-event inspection; for successful JSON responses, it ends at the response headers. These deadlines do not impose a total or idle timeout on body delivery. Caller cancellation remains active through EOF. Fusion advisers retain their separate consumption deadline. Caller cancellation, router deadline, and upstream or publication failure retain the first observed cause in usage receipts. The selected HTTP status stays separate from that outcome. An accepted HTTP rejection keeps its status when JSON or SSE error details cannot be read; later cancellation still ends delivery. A parsed terminal usage event retains its measured charge if delivery later stops; dispatched work without final usage retains its estimate. This does not guarantee that every transport reports an idle client disconnect.
Cancellation accounting starts when the runtime reports an ingress abort or response-body cancellation. A client-local abort alone does not guarantee a prompt server notification or a receipt within ten seconds for an indefinitely idle HTTP response.
pnpm test:scripts includes the seven affirmative deadline and cancellation
cases: progressing JSON/SSE delivery, initial-response deadlines, active-delivery
cancellation, and cancellation observed after independently delayed JSON output.
Each cancellation case requires upstream shutdown, one receipt, and settlement
in both real budget ledgers. The strict idle-disconnect reproduction is explicit:
pnpm diagnostic:http-idle-disconnectThat command replays the original eight-case sequence, including the final
whitespace-only response. It retains every strict assertion and exits nonzero
when the limitation reproduces. Its nested entry point is outside the default
test/*.test.mjs script-test glob; no result is suppressed or treated as a pass.
At commit 750a4093c9685829e9423f124c56022e7a2ce838, the
hosted diagnostic run
used Node 24.21.0, Miniflare 5.20260918.0-alpha and workerd 1.20260918.1.
The delayed-output case received HTTP 200/gzip headers and zero decoded body
bytes before the client aborted at 1.048 seconds. Independent payload output
started at 2.044 seconds; ingress abort followed at 2.452 seconds, before EOF.
It recorded one 200/client_error receipt with unknown tokens and a fixed
charge of 7 micros in both ledgers. The unchanged whitespace-only case recorded no
ingress abort or receipt, and both reservations remained unsettled during the
observation window. That limitation remains unresolved.
The pinned KJ disconnect contract allows detection to remain pending without a write; its HTTP implementation discusses the half-close tradeoff. Node 24.21.0 Fetch abort reaches HTTP/1 socket destruction, whose ordinary-close path is distinct from reset. The fixture does not establish physical FIN/RST behavior, compression causality, or deployed HTTP/2 behavior. No idle or total-delivery timeout is added.
Declared OpenAI API Responses routes support durable recovery for background: true
JSON or SSE creation when the selected transport supports creation, retrieval and
cancellation. Retrieve with GET /v1/responses/<response-id>; resume SSE with
stream=true&starting_after=<last-sequence-number>. Resumption requires an
originally streamed background create. Cancel with bodyless
POST /v1/responses/<response-id>/cancel. These controls also use the native
/v1/native/openai/v1/responses/... routes. Manifest controls use
responses_retrieve and responses_cancel: provide pathParams.response_id
and an empty body in a POST envelope, or response_id in the retrieval URL.
Repeated include[] values are preserved; manifest query arrays are supported.
Controls reauthenticate the original policy scope and credential route. They do
not select another account or reserve new budget. If creation used
OpenAI-Organization or OpenAI-Project, public controls must resupply the same
headers to reconstruct the original route pin. Each is limited to 256 printable
ASCII bytes. The collector alone temporarily retains these headers. The initial
declared create path has no caller path parameters; create options belong in
JSON, and caller-supplied create query parameters are rejected before reservation
or generation dispatch when durable recovery applies. Foreground and creation-only
transport query behavior is unchanged.
Creation keeps the ordinary policy and grant selection. A transport that supports only creation retains request-bound accounting: observed usage settles that request, and missing usage retains its conservative estimate. It receives no recovery locator, job or collection polling. Permitted creation failover keeps that accounting mode even if an alternate supports the full lifecycle. A full-capable initial transport with invalid control configuration fails before reservation; configuration failure does not downgrade it to request-bound accounting.
Verified browser sessions use the corresponding /v1/playground/v1/responses/...
or /v1/playground/proxy/openai/<endpoint> controls. Existing response ownership
must match exactly one currently authorized policy scope. Ambiguous, revoked or
replaced ownership is rejected rather than falling back to another policy.
After route preparation, controls recheck the original key, policy generation,
policy tenant and current user bindings before upstream admission. Key rotation
invalidates an already queued control. Access checks use current stored groups;
local logout is checked separately against the session store. Cloudflare Access
retains the verified assertion's expiry, without claiming live IdP revocation.
A revocation after admission does not cancel an already dispatched control or
the router's accounting collection for an admitted generation.
curl "$CLAWROUTER_BASE_URL/v1/responses" \
-H "authorization: Bearer $CLAWROUTER_KEY" \
-H "content-type: application/json" \
--data '{"model":"openai/gpt-6-astra","input":"Explain durable accounting.","background":true,"store":false}'
curl "$CLAWROUTER_BASE_URL/v1/responses/$RESPONSE_ID" \
-H "authorization: Bearer $CLAWROUTER_KEY"
curl -X POST "$CLAWROUTER_BASE_URL/v1/responses/$RESPONSE_ID/cancel" \
-H "authorization: Bearer $CLAWROUTER_KEY"Keep response.id for controls; for streaming resumption, also keep the last
observed sequence_number. The upstream background contract
permits store: false with temporary polling retention of roughly ten minutes.
The router's one-hour collection limit is not an upstream retention guarantee.
Cancellation is idempotent upstream; accepting a cancellation request is not
proof of final usage or a refund. Existing creation-only ChatGPT subscription
transport admission is preserved; this does not prove upstream acceptance or add
retrieval/cancellation capabilities to that transport. This feature does not establish
native Codex background adoption; WebSocket behavior remains separate below.
Creation responses carry x-clawrouter-background-recovery after a durable
admission is attempted, including when its acknowledgement is lost. Its value is
<64-hex-scope-digest>.bg_<32-hex-job-id>. It is a locator, not authentication,
completion proof or a settlement receipt. It is never forwarded upstream. An
absent record after an admission failure does not prove settlement.
Administrators use POST /v1/admin/usage/recovery behind the existing admin and
same-origin boundary. The JSON envelope is closed:
| Action | Exact fields | Result |
|---|---|---|
| Inspect | action: "inspect", locator |
Sanitized record and independent sink dispositions |
| List | action: "list", scope, optional after job ID |
At most 16 records from one scope, with nextAfter |
| Replay | action: "replay", locator |
Retry only an unresolved, frozen financial receipt before its original deadline |
Replay never recreates the upstream generation. The API exposes original
deadlines, event/request IDs, amount/basis and independent financial/usage
acknowledgements; it omits credentials, raw response IDs and retained route
headers. expired_by_retention closes only usage delivery after the 30-day
history window. It does not establish a financial acknowledgement or an
available history row. HTTP 404 means absent/retired, HTTP 503 means unavailable,
and neither means paid. After a durable admission, retain a recovery-aware Worker
and owner while obligations or inventory completeness are unknown. See
spend control for recovery bounds, the rollback floor
and conservative accounting.
Send authenticated upgrades to /v1/responses or
/v1/native/openai/v1/responses. Unified upgrades select their model on the first
response.create, so an HTTP 101 alone does not prove model access or upstream
readiness. Every create rechecks credential, policy, provider, grant, retention,
and budget before dispatch. The connection pins its provider route and grant
revision; changing either requires a new connection.
Response IDs and response.metadata turn state are bound to each create's fresh
authorization scope and actual upstream owner before forwarding. Codex can then
reconnect with client_metadata["x-codex-turn-state"] or fall back to HTTP with
the same turn state and account. Conflicting header and metadata tokens are
rejected; every supplied continuation identity must name the same owner.
An upgrade header alone
does not create a synthetic metadata event. Metadata may precede
response.created; it identifies the response without proving execution started.
Pending publication preserves frame order and blocks the next same-lane create
until publication and settlement finish. Closing the connection suppresses late
publication acknowledgments and queued output.
The bridge forwards native response IDs, errors, metadata, tool results,
previous_response_id, and stream_options. Prewarm generate: false requests
receive normal admission and accounting. It never replays requests or switches
grants after dispatch. A terminal response with usable usage settles once;
sent requests interrupted by disconnects or deadlines without final usage retain
the reservation. Unsent admitted requests release it; queued requests have no
receipt. The first terminal outcome or close cause owns settlement, including
when admission finishes after cancellation. A client disconnect records
client_error with status_code: null; only the response whose deadline expired
records timeout/504. Other active lanes closed with that connection do not
inherit its timeout. If budget
settlement and its durable recovery both fail, or usage publication fails, the
socket reports accounting_unavailable and closes before accepting more work.
Limits per connection are 16 active responses, 32 named lanes plus the default lane, 48 buffered creates, 4 MiB per incoming frame, 8 MiB total buffered create bytes, at most 48 output frames/8 MiB awaiting publication, and 16 MiB cumulative downstream output. The output limit bounds a slow reader because Workers' WebSocket API has no supported drain/queue metric. Connections last at most 60 minutes; each response uses its endpoint deadline, capped at 600 seconds. Clients must reconnect after a limit or deadline closes the connection. Binary frames, steering events, and background execution are rejected visibly. This contract runs on the Worker deployment; other hosts must qualify native upgrade forwarding before advertising it.
See the upstream Responses WebSocket contract. An authenticated catalog route advertises websocket: "openai.responses" only when its endpoint and grant transport are eligible.
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/session |
Verified Access identity, entitlements, and readiness |
GET |
/v1/session/avatar |
Proxied user avatar |
GET |
/v1/session/usage |
Session quota and usage summary |
GET |
/v1/entitlements |
Compatibility entitlement response |
GET |
/v1/session/credentials |
Credentials owned by the signed-in user |
POST |
/v1/session/credentials |
Create a caller-owned credential; reject an existing ID |
PUT |
/v1/session/credentials/<credential-id> |
Create or rotate a caller-owned credential |
POST |
/v1/session/credentials/<credential-id>/rotate |
Replace only an active credential's secret hash |
POST |
/v1/session/credentials/<credential-id>/revoke |
Revoke a caller-owned credential |
POST |
/v1/playground/<route> |
Run a console playground request through an allowed route |
GET |
/v1/playground/v1/responses/<response-id> |
Retrieve/resume the session's original response |
GET |
/v1/oauth/callback |
Complete a provider-approved browser OAuth flow |
The Worker redirects / to /dashboard, and /dashboard to /dashboard/home. A production Cloudflare Access application protects /dashboard/*, /v1/session*, /v1/playground/*, /v1/admin/*, and /v1/oauth/callback before the request reaches the Worker.
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/admin/bootstrap |
Coherent authority, configuration, and readiness snapshot |
GET |
/v1/admin/overview |
Policy, credential, tenant, provider, and budget totals |
GET |
/v1/admin/tenants |
Tenant summaries |
GET |
/v1/admin/usage |
Budget rows, aggregate usage, and recent request audit |
GET |
/v1/admin/content?tenant=<id>&ref=<ref> |
Retained request content from the separate archive |
GET |
/v1/admin/policies |
Access policies |
GET |
/v1/admin/credentials |
Issued proxy credentials without raw secrets |
GET |
/v1/admin/connections |
Global provider connections and kill switches |
GET |
/v1/admin/access-users |
Materialized Access users |
GET |
/v1/admin/policy-bindings |
User and group policy bindings |
GET |
/v1/admin/provider-status |
Policy-aware provider readiness |
GET |
/v1/admin/provider-health |
Persisted provider smoke status |
GET |
/v1/admin/upstream-grants |
Sanitized policy- and tenant-scoped upstream grants |
GET |
/v1/admin/upstream-grants/usage?scope=policies&scopeId=<policy-id>&days=<1..31> |
Per-grant/lane totals and daily model usage |
GET |
/v1/admin/assignment-rules |
Access identity assignment rules |
GET |
/v1/admin/fusion |
Fusion configuration |
| Method | Path | Purpose |
|---|---|---|
PUT |
/v1/admin/access-users/<email> |
Update a materialized user's tenant, status, groups, or retention exemption |
PUT |
/v1/admin/access-user-grants/<email> |
Update a user and atomically replace direct policy grants |
PUT |
/v1/admin/policy-bindings |
Create or update a user or group binding |
PUT |
/v1/admin/policies/<policy-id> |
Create or update a policy |
POST |
/v1/admin/policies/<policy-id>/revoke |
Disable a policy and every credential bound to it |
POST |
/v1/admin/credentials |
Create an issued credential; reject an existing ID |
PUT |
/v1/admin/credentials/<credential-id> |
Create or update an issued credential |
POST |
/v1/admin/credentials/<credential-id>/rotate |
Replace only an active credential's secret hash |
POST |
/v1/admin/credentials/<credential-id>/revoke |
Revoke one issued credential |
PUT |
/v1/admin/connections/<provider-id> |
Update a global provider connection |
PATCH |
/v1/admin/connections/<provider-id> |
Update only supplied connection fields |
PUT |
/v1/admin/upstream-grants/<policies|tenants>/<scope-id>/<token-ref> |
Create or update a scoped upstream grant |
POST |
/v1/admin/upstream-grants/<policies|tenants>/<scope-id>/<token-ref>/revoke |
Revoke a scoped upstream grant and remove its secrets |
POST |
/v1/admin/upstream-grants/<policies|tenants>/<scope-id>/<token-ref>/refresh |
Refresh an OAuth grant |
POST |
/v1/admin/upstream-grants/<policies|tenants>/<scope-id>/<token-ref>/quota-refresh |
Refresh provider-reported grant quota state |
POST |
/v1/admin/upstream-grants/<policies|tenants>/<scope-id>/<token-ref>/authorize |
Begin a browser OAuth authorization |
POST |
/v1/admin/pool-submission-tickets |
Issue a short-lived, one-time credential contribution ticket for one pool slot |
PUT |
/v1/admin/assignment-rules/<rule-id> |
Create or update an identity assignment rule |
POST |
/v1/admin/assignment-rules/reconcile |
Reconcile materialized users against assignment rules |
PUT |
/v1/admin/fusion |
Update Fusion routing configuration |
POST |
/v1/admin/fusion/preview |
Evaluate Fusion readiness and estimated reservations for a policy |
POST |
/v1/admin/usage/recovery |
Inspect/list one recovery scope or replay one immutable financial receipt |
The legacy GET|PUT /v1/admin/keys..., POST /v1/admin/keys/<kid>/revoke, and GET /v1/admin/users routes remain compatibility aliases. New control-plane clients use policies, credentials, and tenants directly. Legacy top-level console and /api/* aliases redirect or normalize to their /dashboard/* and /v1/* equivalents.
Upstream grant PUT preserves unspecified credentials by default. Add
?mode=replace to replace the complete grant, clearing omitted credentials and
account or refresh metadata. Replacement requires a fresh primary credential;
credential-presence flags are insufficient. Other mode values return HTTP 400.
The cf:oauth:put CLI uses replacement mode.
Grant revocation accepts an optional JSON object with kind, provider, and
label hints for legacy grants that have no credential owner. Existing owners
ignore these hints and retain their canonical identity. Revocation stores a
secretless, disabled tombstone and cancels maintenance; an unknown grant returns
HTTP 404. Retrying revocation preserves the same tombstone generation.
Explicit replacement and revocation can recover an existing legacy KV grant with
invalid JSON or metadata larger than the 3 MiB migration limit. Corrupt or oversized
bytes are discarded, never imported into the credential owner. Supply fresh credentials
through cf:oauth:put or PUT ...?mode=replace, or use cf:oauth:revoke to remove
the account. Ordinary edits, OAuth callbacks, contributions, refresh and automatic
migration remain strict. A failed KV read is unavailable state, not proof of an
absent account. Recovery preserves retained pool generations; a newer index than
an existing owner requires operator recovery instead of resetting ownership.
When both the owner and KV record are missing, either PUT mode creates an account
only after confirming an empty attachment index. Retained legacy membership or
generation history returns HTTP 409, including generation-zero legacy rows.
Policies optionally set models to an array of full catalog IDs, for example
["anthropic/claude-opus-5-5"]. Omitted or null preserves access to every
model of the allowed providers; [] denies every model. Provider restrictions
still apply. Writes accept at most 1,024 entries, reject unknown IDs, and
normalize duplicates. Omitting either models or poolStatus from an update
preserves the existing value.
/v1/catalog, /v1/models (including Anthropic format), session entitlements,
and Fusion offers honor these scopes. Access sessions can use models allowed by
any held policy, with the normal ordered policy choice among matching policies.
Every concrete inference call checks the resolved catalog ID before grant
selection, budget reservation or upstream contact. A denied model returns HTTP
403 model_not_allowed on native Messages and count_tokens, manifest routes,
OpenAI chat/embeddings/speech, HTTP Responses, and each WebSocket response.create.
Fusion checks its synthesizer and each adviser separately; denied advisers fail
open like other unavailable advisers. Its virtual name does not grant access to
underlying models. Restricted policies reject opaque universal gateway requests
because their downstream model selections cannot be verified.
GET /v1/pool accepts a proxy key or verified Access session. Policy
poolStatus: "visible" opts in; omitted or "hidden" returns HTTP 404
pool_status_hidden. Responses use Cache-Control: private, no-store.
A proxy key always selects its own policy, regardless of query parameters.
An Access session may select one of its held policies with ?policyId=<id>;
without it, the first active policy is used. An unheld policy returns HTTP 403.
The response contains version: "clawrouter.pool.v1", ISO observedAt,
policyId, routing (strategy, includedQuotaReservePercent,
environmentFallback), allowed catalog models, grants, and usage.
Each grant includes only its token-reference key, label, provider, kind,
enabled and credential status, plan, account (email, rateLimitTier,
subscriptionStatus), quota windows (id, window, models,
remainingPercent, resetAt, observedAt), extra usage (enabled,
usedCredits, monthlyLimit, currency, spendLimitReached), per-model
eligibility, lastSelectedAt, and selectedCount. Unknown facts are null.
Account emails are intentionally visible to holders when the administrator opts
in. No access/refresh tokens, credential fields, raw provider payloads, or full
storage keys are returned.
Inventory includes the selected policy's grants and shared tenant grants,
filtered by its provider and eligible-grant restrictions. It is bounded to
1,024 scanned grant records (HTTP 422 pool_too_large beyond that limit).
usage contains days: 7 and the policy's aggregate lanes and grants from
the existing grant usage ledger, with token references replacing storage keys;
it contains no individual request events. These are policy-wide totals, including
other credentials on the same policy, and retain historical attribution.
Eligibility shares the Subscriptions page and dispatch quota rules: scoped
windows match upstream model family names, reserve applies to subscriptions,
active cooldown blocks selection, and stale observations block only under
staleState: "deny". Status is eligible, exhausted, cooldown, stale, or
reauth_required. Models belonging to another provider use exhausted (no
usable capacity); disabled grants are never eligible, and missing or invalid
credentials use reauth_required. Eligibility reports quota/credential availability, not priority
selection, transport readiness or a budget reservation. Observations and usage
are advisory snapshots, not an atomic upstream quota guarantee.
Policy grantRouting accepts two opt-in controls in addition to the existing
strategy, stickiness, failover, priority and stale-state settings:
| Field | Default | Contract |
|---|---|---|
includedQuotaReservePercent |
0 |
Integer 0–50. A subscription is ineligible when any applicable, unexpired window has this percentage or less remaining. |
environmentFallback |
false |
If no scoped grant is eligible, allow the provider's configured environment credential with normal transport, pricing and budget enforcement. Missing environment credentials retain HTTP 503. |
Scoped quota windows in GET /v1/admin/upstream-grants optionally carry
models: ["fable"] (lowercase family tokens) and per-window observedAt timestamps. A scope applies when its token
occurs in the requested upstream model ID; an absent scope applies to all
models. Exhausted unscoped windows cool down the whole grant. Scoped exhaustion
excludes only matching models. most_remaining and threshold compare only
applicable live windows. Freshness uses each applicable window's own timestamp, so partial header updates cannot refresh an older model window. Concurrent feedback merges by window observation time and preserves active inference cooldowns. Reset-passed readings are unknown. A reserve protects
against extra-usage billing but cannot prevent one in-flight request crossing the
boundary. It does not turn polling into an atomic upstream quota reservation.
Grant responses also expose optional sanitized account fields (email,
displayName, rateLimitTier, subscriptionStatus, extraUsageEnabled) and
extraUsage fields (enabled, usedCredits, monthlyLimit, utilization,
currency, spendLimitReached). Credits are minor units, e.g. USD cents.
Only manifest-declared scalar fields are retained. Tokens and raw provider
payloads are excluded. A probe HTTP 429 keeps the last reading and honors
Retry-After independently per endpoint; HTTP 401 marks reauthorization required.
Proxied HTTP responses carry x-clawrouter-grant-lane: subscription for
subscription/OAuth grants, api_key for scoped API-key grants, and environment
for provider environment credentials. One-shot 401/403/429 failover can use the
environment lane when explicitly enabled and no alternate grant remains.
Continuation pins, provider authorization and budget limits remain authoritative.
An existing environment pin can reuse that environment credential while fallback remains enabled; a scoped pin never switches lanes.
Usage events carry nullable grant_key and grant_lane for the final attempt;
events created before this change retain unknown attribution.
GET /v1/admin/upstream-grants/usage?scope=policies&scopeId=<policy-id>&days=7
requires the same admin authorization as the grant inventory. days is an
integer 1–31 (default 7). The policy must exist; tenant-only scopes are not
accepted. The Worker resolves the policy's canonical tenant before addressing
its usage shard, independent of the administrator's tenant.
The response contains days, since/until epoch milliseconds, retentionDays
(currently 30), grants, lanes, and daily. Each metrics row contains
requests, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
and costMicros. grants groups by provider, nullable grantKey, and nullable
grantLane; lanes groups by provider and nullable grantLane. daily adds
UTC day (YYYY-MM-DD) and nullable model, retaining the same grant/lane keys.
Cost follows existing accounting prices (API-equivalent manifest value, or the
policy's fixed tariff), not subscription invoice spend. Failed recorded requests
also count; usage remains a single final-attempt event per request.
Only retained events within the rolling requested range are read. A 31-day request cannot restore data beyond 30-day retention. Legacy null attribution remains a separate group. The response is bounded to 8,192 daily groups and returns HTTP 422 if exceeded; request a shorter range rather than relying on partial totals. Invalid scope/range returns HTTP 400; an unknown policy returns HTTP 404. The additive SQLite migration preserves every existing event.
Use POST /v1/admin/credentials or POST /v1/session/credentials with
{ "credentialId": "my_key", "policyId": "my_policy", "secretSha256": "<64 hex characters>" }
to create a key. IDs must contain 4–128 letters, digits, or underscores so the key
can be authenticated. Success returns 201; an existing ID returns 409 credential_exists
without replacing the key or pruning retained records. Admin creation also accepts
enabled (default true) and principalId (an email or null, default null).
Personal creation always enables the key and assigns the signed-in user as owner.
Use POST .../credentials/<credential-id>/rotate with only
{ "secretSha256": "<64 hex characters>" } to replace a key's hash. Rotation preserves
its owner, policy, generation, and enabled state. It requires an enabled key, an
enabled policy with the same generation, and an owner who is not disabled. An
inactive key returns 409 credential_inactive; a missing key returns 404.
Rotation cannot reactivate a revoked key or renew a revoked policy generation.
The existing PUT .../credentials/<credential-id> remains an upsert: it can replace
the owner or policy on an admin key, reenable a key, and bind the current policy
generation. Personal PUT keeps its existing forced-enabled, caller-owned behavior.
Use create and rotate for operations that must not overwrite an existing key or
change its authorization. Secret hashes are lowercase SHA-256 hex; responses never
include a raw secret or hash and describe the record committed by that operation.
Personal create, PUT, and rotate recheck the current enabled user, groups, bindings, and policy at the serialized write boundary. Personal revoke requires ownership but remains available after group, binding, or policy access is removed. Revoke disables the latest record, preserves any intervening hash rotation, and is idempotent for an existing key. Local administrator disable or demotion is also rechecked before credential writes; Cloudflare administrator status comes from the verified Access configuration. Personal keys remain limited to 10 enabled and 100 retained records; creating another key can prune revoked records in ID order at the retention limit. Rotation consumes no additional slot.
POST /v1/pool-submissions/<ticket-id>/consume accepts a ticket bearer secret
and one credential bundle. The ticket, not the request body, supplies the pool
scope, token reference, provider, grant kind, priority, and weight. Accepted
body fields are credential, credentials, accessToken, refreshToken,
tokenType, expiresAt, scopes, accountId, and bounded subscription
metadata. Contributor-defined refresh endpoints and OAuth client settings are
not accepted. Keep-warm follows the provider manifest default; Claude subscription
tickets default to enabled. The administrator may set keepWarm: false on the
ticket to opt out. Contributors cannot alter maintenance behavior in their
submission.
The route consumes the ticket once, but an identical retry can recover an interrupted submission and returns the same receipt after completion. The response exposes only the grant key and submission timestamp. Raw credential material is stored in the grant's Durable Object; the KV grant record contains only routing metadata and credential-presence flags.
Before forwarding a request, the Worker authenticates the credential, resolves its policy, checks the provider connection and scoped grant readiness, and reserves a conservative budget. Policies can select grants by priority, round robin, least used, reported remaining quota, consume-until-threshold, or weighted random choice. Threshold routing owns pool affinity; other modes may use identity or session stickiness.
Provider grants can be scoped to a policy or tenant. When several token references are eligible, ClawRouter applies the policy strategy within the active priority tier. An upstream 401, 403, or 429 records sanitized grant state and can trigger one same-provider alternate when policy allows failover.
Disable one credential to revoke one key, disable a policy to revoke every credential bound to it, or disable a provider connection to stop that provider globally. Policy and credential generations must match, so incomplete rotations and stale migration records fail closed.
Budgeted requests reserve an upper-bound token cost before the upstream call when the selected model has versioned pricing. A policy requestCostMicros value is a fixed-cost override; budgeted routes without versioned pricing or an override fail closed.
Successful responses settle to reported usage, including cached input where available, or the explicit fixed policy tariff. Known-unsent work and received non-2xx responses settle at zero with cost_basis: none. Transport failures after dispatch, and missing or interrupted usage, retain the qualified estimate or fixed tariff because upstream work may have occurred. Streaming responses are metered without buffering the client stream.
Declared Responses JSON and SSE bodies are inspected incrementally for usage and terminal status, including fields after large output. The observer retains only bounded metadata; excessive nesting or oversized selected fields leave usage unknown and preserve the applicable estimate or fixed tariff. These inspection limits do not impose a response-size limit or change delivered bytes.
Usage events are delivered through USAGE_QUEUE to tenant- and policy-sharded USAGE_LEDGER Durable Objects. Settlement and audit delivery retry independently, and exhausted messages move to the configured usage dead-letter queue. Ledgers keep bounded identity, route, timing, outcome, token, cost, request ID, and trace metadata; they do not store prompts or completions.
Policies can retain LLM request bodies in the separate CONTENT_ARCHIVE R2 binding. Retention failure is fail-closed before upstream traffic. See Request content retention for the policy and disclosure contract.
Every Worker-owned response returns the canonical X-Request-ID, and CORS exposes it to clients. Valid W3C trace and span IDs are preserved in usage metadata. Agent attribution headers, pricing fields, and reservation semantics are documented in Agent spend control.