Skip to main content

Endpoint reference

Every route on the gateway: /v1/, /mcp, /scim/v2/, /admin/, /connect/.

Auth lanes​

SurfaceGated by
/v1/*An enrolled client key (minted by anyray-connect) or a non-human service key. Attribution rides x-anyray-metadata.
/mcp/adminAn AuthKit bearer for exactly this gateway's /mcp/admin URL, resolved to the live console grant.
/mcp/orgThe caller's client key, or a WorkOS AuthKit token whose audience is this gateway's exact /mcp/org URL.
/admin/*The admin token (ANYRAY_ADMIN_TOKEN), a role-scoped console SSO session, or a scoped admin API key (aak_…, see the API reference).
/scim/v2/*Its own static bearer, set through PUT /admin/scim/settings. Not an admin-console session.

Authenticated is not privileged. Every /admin/* route also needs the capability in its row below, per the RBAC lattice (viewer → auditor → operator → security_admin → owner).

In fleet mode (ANYRAY_MULTI_TENANT=true) most configuration routes also need the deployment owner (a tenant SSO principal gets 403 whatever its role). Tenant-scoped reads and client test publishing stay role-gated only: /admin/client-tests, /admin/spend*, /admin/health, /admin/me, /admin/client-keys, /admin/service-keys, and the optimizer and observability routes. Rows below mark the deployment-owner routes where the distinction most often bites.

Inference requests and the VS Code pre-call hook carry content under the deployment's content mode. SCIM identities, admin spend records, and request logs never expose prompt or response bodies.

Capabilities from the OpenAPI spec​

Every /admin/* operation in the OpenAPI spec carries x-required-capability, the one capability that route enforces (null means authenticated but ungated, like /admin/me). It is generated from the gateway's route table, and CI fails on drift. Use it to script an access matrix, or to pick the smallest grant for a scoped admin API key:

# Which capabilities does a script touching these routes need?
curl -s https://docs.anyray.ai/openapi.json \
| jq -r '.paths | to_entries[] | .key as $p | .value | to_entries[]
| select(.value["x-required-capability"])
| "\(.key | ascii_upcase) \($p) -> \(.value["x-required-capability"])"'

Management API v1: /admin/v1/*​

Stable The /admin/* surface below, re-served under resource paths with a scope you derive from the URL: GET needs <resource>:read, anything else <resource>:write. The named exceptions are in the API reference.

Each v1 operation is the same handler as its legacy twin, so the request and response bodies documented in the legacy rows apply unchanged. Only the path, the gate, and the error envelope differ. The spec carries x-required-scope, x-scope (tenant or deployment), and x-legacy-operation on every v1 operation.

ResourcePathsScopesLegacy twins
meGET /admin/v1/menone/admin/me
me/logoutPOST /admin/v1/me/logoutnone; console session requirednative v1; bearer logout
keys/admin/v1/keys (?type=service; ?limit=&cursor= for {data,next_cursor} pages), …/{id} (GET/PATCH/DELETE), …/{id}/rotate, …/{id}/secretkeys:read, keys:write, keys:secret/admin/client-keys, /admin/service-keys
admin-keys/admin/v1/admin-keys, …/{id}admin-keys:read, admin-keys:write/admin/api-keys
users/admin/v1/users/limits, …/{user}/limits, …/auth-events, …/revoked, …/revoked/{email}, …/{user}/export, …/{user}/datausers:read, users:write, users:revoke, users:gdpr/admin/user-caps, /admin/auth-events, /admin/revoked-users, /admin/gdpr/users/…
spend/admin/v1/spend/summary, …/dashboard, …/overview, …/active-users, …/activity, …/usage, …/usage/properties, …/sessions/{sessionId}, …/quality-parity, …/externalspend:read/admin/spend, /admin/spend/*, /admin/spend-connectors/usage
client-tests/admin/v1/client-tests (GET/POST)client-tests:read, client-tests:write/admin/client-tests
provider-resources/admin/v1/provider-resources (GET), …/{provider}/{id} (GET), …/{provider}/{id}/owner (PUT/DELETE)provider-resources:read, provider-resources:write (providerresources:manage; security_admin and owner)/admin/provider-resources
providers/admin/v1/providers, …/capabilities, …/{slug}/key (PUT/DELETE), …/connectors, …/connectors/sync, …/web-search (GET/PUT/DELETE)providers:read, providers:write/admin/provider-keys, /admin/spend-connectors, /admin/claude-desktop-policy/web-search
routing/admin/v1/routing, …/references, …/aliases, …/agent-models, …/modelsrouting:read, routing:write/admin/routing-config, /admin/model-aliases, /admin/agent-models, /admin/pricing
optimizer/admin/v1/optimizer, GET /admin/v1/optimizer/log-read, …/purge, …/erase-tenant, GET /admin/v1/optimizer/session-gate, PUT /admin/v1/optimizer/session-gate/{kind}, GET /admin/v1/optimizer/tenant-policy, PUT /admin/v1/optimizer/tenant-policy/{tool}/{version}optimizer:read, optimizer:write, optimizer:erase/admin/optimizer/settings, GET /admin/log-read/status, /admin/optimizer/purge, /admin/optimizer/erase-tenant, GET /admin/optimizer/session-gate, PUT /admin/optimizer/session-gate/{kind}, GET /admin/optimizer/tenant-policy, PUT /admin/optimizer/tenant-policy/{tool}/{version}
policies/admin/v1/policies/teams, …/claude-desktop, …/claude-desktop/mobileconfig, …/claude-desktop/connectors/{probe,readiness,authorize,timing}policies:read, policies:write/admin/team-skills, /admin/claude-desktop-policy, …/connectors/*
settings/admin/v1/settings, …/content, …/updates, …/updates/run, …/setup, …/setup/status, …/prompt-cache, …/health, …/health/series, …/health/problems, …/support-bundle, …/support-bundle/sharesettings:read, settings:write, settings:content, settings:update/admin/settings, /admin/settings/prompt-cache, /admin/update-status, /admin/update/settings, /admin/update/run, /admin/wizard-*, /admin/health, /admin/support/bundle
sso/admin/v1/sso, …/scim, …/portal-link, …/members, …/members/{email}sso:read, sso:write/admin/idp-config, /admin/scim/settings, /admin/workos-portal-link, /admin/console-members
enrollment/admin/v1/enrollment/links, …/links/{id} (?hard=true)enrollment:read, enrollment:write/admin/enrollment-links
provisioning/admin/v1/provisioning/tokens, …/{id}, …/{id}/rotateprovisioning:read, provisioning:write/admin/provisioning-tokens
fleet/admin/v1/fleet/seats, …/directivesfleet:read, fleet:direct/admin/connect-health, /admin/connect/directives. …/seats is the per-seat connect-health rollup. appliedIntegrationLevel is present only when Connect reports a recognized level. residue is three-state: omitted = not reported, [] = probed clean, non-empty closed list = reported leftovers; only an actionable member (any except the informational osquery_log_retained) derives state: "needs_pkg_install". The /admin/v1/fleet/seats twin returns the identical body.
traces/admin/v1/traces (next_cursor pages), …/exists, …/facets, …/sessions, …/{id}, …/{id}/content, …/export, …/connector-credential-scrubtraces:read, traces:content, traces:scrub/admin/observability/*, /admin/traces/connector-credential-scrub. traces:read lets a caller call these; observability:read decides whether they answer for the whole organization or only the caller's own traffic
audit/admin/v1/audit/events ({data,next_cursor} pages), …/meteringaudit:read/admin/metering/last-report
playgroundPOST /admin/v1/playgroundplayground:run/admin/test-request

Two reads are sharper than their verb suggests. GET /admin/v1/providers needs providers:write, because provider keys come back in full; …/capabilities is the credential-free view on providers:read. GET /admin/v1/traces/{id} is metadata-only for every caller. …/{id}/content and …/export (a time window as NDJSON, no legacy twin) are the only trace paths that return bodies, and only to a principal holding traces:content (never a key).

Not re-served: the console session routes (/admin/auth, /admin/auth/update, /admin/sso/*) and the legacy /admin/spend/connectors alias.

GET /admin/v1/settings/setup/status reports setup progress in a fixed step order: provider_keys, routing, email_domain, team_link, first_request. The console's setup guide and Get started card read it too.

  • done, optional and passthrough settle a step. todo, pending, missing_keys, no_target and waiting need action. missing_keys names routed providers with no key, or pinned to a deleted key. no_target means a routing group has no target a request can be sent to.
  • unreadable, not_connected and not_permitted mean the gateway could not check the step. They never count as settled.
  • complete is true when every step is settled. next names the first step that needs action, or is null.
  • Every unsettled step carries action: an operationId to call (or null) and a short hint.
  • first_request looks back 90 days, the same window the console's Home uses to decide whether to show setup. It ignores the console playground's test requests.

The response holds no credentials, DNS challenges or developer identities.

Inference API: /v1/*​

OpenAI-compatible, plus the Anthropic-native Messages API. Point any OpenAI or Anthropic SDK's base URL at the gateway. Full schemas: the OpenAPI spec and the API reference.

MethodPathWhat it does
post/v1/chat/completionsChat completions, streaming and non-streaming.
post/v1/messagesAnthropic-native Messages API.
post/v1/messages/count_tokensToken counting. No inference, not metered.
post/v1/messages/batchesAnthropic Message Batches (GET list; GET /v1/messages/batches/{id}, POST …/{id}/cancel, GET …/{id}/results). Requests ride inline, so no /v1/files upload step. Metered content-free, one spend row per result line.
post/v1/responsesResponses API create (GET/DELETE /v1/responses/{id}, GET …/{id}/input_items). A GET with Upgrade: websocket is Codex's Responses WebSocket on a ChatGPT seat, relayed unchanged; anything else it cannot serve answers 426, including a cap, budget or entitlement refusal, which reaches Codex as 402 or 403 on the HTTPS request it sends instead.
post/v1/completionsLegacy text completions.
post/v1/embeddingsEmbeddings.
get/v1/modelsList available models. A route to Bedrock, which has no list to forward to, is answered from the gateway's own catalog.
get/v1/me · /v1/me/usage · /v1/me/limits · /v1/me/modelsSelf-service for the verified key holder: identity and key metadata, month-to-date tokens and savings, the caps that would 402 this identity, and the selectable models. Answered by the gateway, metadata only.
post/v1/audio/speechText to speech.
post/v1/audio/transcriptions · /v1/audio/translationsAudio transcription and translation.
post/v1/images/generations · /v1/images/editsImage generation and edits.
post/v1/filesUpload a file (GET list; GET/DELETE /v1/files/{id}, GET …/{id}/content).
post/v1/batchesCreate a batch (GET list; GET /v1/batches/{id}, POST …/{id}/cancel).
get/v1/realtimeRealtime WebSocket passthrough.
post/v1/extensions/vscode/optimizeOptimize a VS Code extension request before the extension calls its provider. Fails open; the optimizer's savings are metered as a zero-token spend row.
post/v1/copilot/:plan/:seat/*GitHub Copilot seat carrier lane (GET too). The gateway forwards the seat token, so GitHub still bills the Copilot plan.
post/v1/seat/cursor/chat/completionsCursor OpenAI-BYOK lane. Configured by anyray-connect, not called by hand.

A /v1/ path the gateway has no route for is forwarded to the provider for GET, DELETE, file and audio uploads, bodyless POSTs, and requests that send x-anyray-auth-mode: passthrough. Any other JSON POST to one answers 404 with a hint naming the main inference routes, so a wrong base URL fails at once instead of being retried.

On a provider key the gateway holds, some of those paths answer 400 instead:

ProviderNot forwarded on the org's key
vertex-ai, bedrockEvery unrouted path, including /v1/proxy/*. GET /v1/models still answers, unless the request carries an x-anyray-config or x-anyray-custom-host.
Every other provider except googleEvery unrouted path except GET model listing (anything under /models) and the provider's inference paths that have no route here. The routes in the table above, such as /v1/files and /v1/batches, are not affected.
openaiThe admin API, /v1/organization/*, on any route
anthropicThe admin API, /v1/organizations/*, on any route

Of those inference paths, only a body that is not application/json reaches the provider: a JSON POST to an unrouted path is a 404 before any of this, so moderations, rerank and OCR, which take JSON, do not forward. Image variations, a multipart upload, does.

A caller's own credential (pass-through, or a key sent with no key stored) is forwarded as before. The admin API check judges the path past the base URL. An admin's custom host is that base, so its own path may name an organization. A custom host the caller names is judged whole.

/v1/me/models lists aliases, the models routing lanes name, and the priced ids a $regex lane matches. When nothing names the default provider, its priced family is listed where routing reaches that provider. anyray-default is the last row. Rows carry id and known token limits. A person with a route or a service key gets its own menu instead, the one the admin route below returns for that key. An agent-binding or agent-lanes menu has no anyray-default.

The provider and a per-key menu come from GET /admin/v1/routing/agent-models?keyId=<key record id> (routing:read; legacy GET /admin/agent-models on config:read). Its scope says which menu you got: agent-binding (the key has its own route, so its providers' priced models), agent-lanes (only the models of that key's own lanes) or org (the menu above). Without keyId it is always org. With keyId, primary names the key's default model: the menu id it sent the most successful requests for in the last 30 days (callers with spend:read), else the first model its own lanes name exactly. It is absent when neither applies. An unknown keyId returns 404 with error.code: key_not_found, and a key-store failure returns 503.

max_tokens clamping on the Anthropic lanes

On /v1/messages, and on /v1/chat/completions with an Anthropic provider, a max_tokens above the model's hard output ceiling is clamped down to that ceiling instead of being rejected upstream with a 400. A tool that sets one provider-wide value for every model (OpenClaw's per-provider maxTokens, say) still succeeds at the largest output the model supports. Values at or under the ceiling, unknown model ids, and subscription passthrough traffic pass through unchanged. The value is never raised.

What the Cursor seat lane rewrites

/v1/seat/cursor/chat/completions is served by the /v1/chat/completions pipeline but classified as an Anyray subscription seat, because Cursor cannot send a seat header. Before validation it folds Cursor's wire shape into a chat body: a Responses-API envelope (input, instructions, flat function tools, max_output_tokens, with store, previous_response_id and truncation dropped), flat tools on a chat body, and a missing model filled with anyray-default. The reply is the provider's chat-completions response. This is API-key-upstream traffic, not the native Cursor Team entitlement.

Files, batches and stored responses on the org's key​

On a provider key the gateway holds, a file, batch, fine-tuning job or stored response belongs to the person who created it. Only they can read, download, cancel or delete it. Anyone else gets 404, the same answer as an id that does not exist. A batch's output and error files, and a fine-tuning job's result files, belong to the owner of the batch or job.

List calls return only your own, so a page can hold fewer items than you asked for. Page on with after set to the page's last_id. A page that holds none of yours has no last_id, so paging stops there; narrow the list instead (for files, by purpose). A Responses request with store: false keeps nothing at the provider, so it has no owner.

A request that names a file by id is checked the same way: a file_id in a chat, Messages or Responses request (tool results included), a batch's input_file_id, a fine-tuning job's training and validation files, and a Responses previous_response_id. Naming one you don't own answers 404.

A batch's input file is checked too. When you upload a file with purpose=batch on the org's key, the gateway reads each line as it passes through. The upload fails before the file is created if a line names a file or stored response you don't own (404), or isn't a JSON object, or is longer than 50 MB (400). One input file can name up to 1 MB of distinct ids; more answers 400. A batch on the org's key takes only an input file uploaded this way. Any other file, including one uploaded before this check, one an admin assigned, or one with another purpose, answers 400: upload it again with purpose=batch. So does an input file an admin transferred to you, since its lines were checked against its previous owner's files. The gateway checks the ids an input names again when you create the batch, so a batch whose input names a file that has since moved to someone else answers 404.

A method these routes don't serve, such as DELETE on a fine-tuning job, answers 405 on the org's key, and nothing reaches the provider.

Creating a file, batch or fine-tuning job on the org's key needs a verified identity: a client key or SSO identity. Without one, the create answers 403 and nothing reaches the provider.

Who sends the requestWho owns what it creates
A client keyThe person the key is enrolled to.
A service keyThe key itself. The user it names in x-anyray-metadata is attributed spend, not ownership, because the gateway can't verify that header.
A request with no key, under identityMode: network-trustedNothing: its file, batch and fine-tuning creates answer 403, because the user it names is not verified. A stored Response it creates has no owner and stays admin-only.

While the gateway can't reach its ownership store, these routes answer 503, and a create is not sent to the provider. The store is the spend database, so a gateway without one, including one on Workers, answers 503 here on the org's key. Your own provider credential (pass-through) is not checked; the provider's own account rules apply.

If something you just created answers 404, the gateway could not save its owner. An admin can assign it to you with the transfer below.

Erasing a user releases what they owned, so it becomes admin-only, and a GDPR export lists the files, batches, jobs and stored responses a user owns (ids and dates only).

Created before this change

Files, batches, jobs and stored responses created before the gateway checked ownership have no owner, so only an admin can reach them. That includes a batch still running. An admin can hand one back with a transfer. Transfer each one to the person who created it, not to yourself: a batch's output spend is booked to whoever downloads it.

To transfer a resource, an admin with provider-resources:write sets its owner. From a terminal with the admin CLI:

Terminal
anyray-connect admin api GET provider-resources --query provider=openai --query kind=batch --json
printf '%s\n' '{"owner":"jane@example.com","kind":"batch"}' |
anyray-connect admin api PUT provider-resources/openai/batch_abc123/owner --body - --json

From the admin MCP, call anyray_admin_write with v1_put_provider_resources_provider_id_owner. DELETE …/owner releases a resource, leaving it admin-only. A batch's or job's output files move with it on a transfer or release, unless they were assigned to someone else. A file an admin assigns is never marked as a checked batch input, so to use it as one, upload it again with purpose=batch. Every transfer and release is audited. These routes read and write ownership records only. They never call the provider and never return a file's or response's content.

Inbound SCIM v2: /scim/v2/*​

Okta, Microsoft Entra ID, and other SCIM clients push verified users and groups here. Every request sends the SCIM bearer as Authorization: Bearer <token>. Responses use application/scim+json, and errors use the SCIM 2.0 error envelope.

MethodPathWhat it does
get/scim/v2/ServiceProviderConfigAdvertise PATCH, filtering, paging, and bearer authentication.
get/scim/v2/ResourceTypesList the User and Group resource types.
get/scim/v2/SchemasReturn the supported core User and Group schemas.
post/scim/v2/UsersCreate a user from the verified IdP payload.
get/scim/v2/UsersList users. Supports filter=userName eq "dev@example.com", startIndex, count.
get/scim/v2/Users/:idRead one user.
put/scim/v2/Users/:idReplace a user.
patch/scim/v2/Users/:idPatch activation, display name, or identity type. active:false blocks the user's client keys.
delete/scim/v2/Users/:idSoft-delete and deactivate a user, so existing client keys stay blocked.
post/scim/v2/GroupsCreate a group and resolve or create its members.
get/scim/v2/GroupsList groups with startIndex and count paging.
get/scim/v2/Groups/:idRead one group and its members.
put/scim/v2/Groups/:idReplace a group and recompute member team and role mappings.
patch/scim/v2/Groups/:idAdd, remove, or replace members; update the display name.
delete/scim/v2/Groups/:idDelete a group and recompute affected identities.

Billing App machine API​

Served by the Billing App (app.anyray.ai), not the gateway. The gateway calls it with its adt_ deployment token. Usage rollups, lease state, release metadata, or an explicitly submitted redacted support bundle. Never prompt or response content.

MethodPathWhat it does
post/v1/meterIngest a usage rollup plus deployment heartbeat. Returns a freshly signed entitlement lease.
get/v1/entitlementLease heartbeat: refresh the signed lease without ingesting usage. ?deploymentId= binds it like a meter report. Throttled on its own clock; 429 carries Retry-After.
post/v1/tenant-offboard/ackAcknowledge a signed offboarding notice delivered on a deleted deployment's metering 401.
get/v1/tenant-contextFleet mode only: resolve an ark_ key hash to its tenant context.
get/v1/releases/latestThe current release-line version and its bounded requirements manifest. manifest may be null.
post/v1/support-bundlesAccept a reviewed, redacted support bundle, analyze it, and return { receiptId }. Deployment bearer, 1,000,000-byte cap, one per deployment per minute.
post/v1/connect-eventsGateway-down fallback for connect-health. Authenticated by a DevCert and proof of possession, not a deployment bearer. Best-effort, throttled, never on the billing path.
post/v1/cert/renewRoll a live DevCert forward, so a machine in use never has to re-enroll. Authenticated by a signed challenge. Refused for a revoked developer or deployment, and for an SSO-issued certificate whose tenant SSO policy is absent or unreadable.
post/v1/cli-sso/startStart the identity half of a CLI login. Takes the gateway session id and user code, returns the browser activation URL.
get/v1/cli-sso/pollReturn pending, or the verified identity, governed team list and default, resolved role, and configured key lifetime. Never returns the gateway key.
put/v1/enrollment-identitiesRecord a deployment-verified roster identity with allowlisted source and type. Used by gateway SCIM reflection.

CLI self-service login​

Pre-key gateway routes used only by anyray-connect login. Start is rate-limited by source IP. Poll is authorized by the separate high-entropy secret start returns once; the secret travels in a header, not the URL, and the gateway stores only a salted hash.

MethodPathWhat it does
post/sso/cli/startCreate a durable ten-minute login session, start the Billing App SSO grant, and return { id, browser_url, user_code, poll_secret, interval, expires_in }.
get/sso/cli/poll/:idWith x-anyray-poll-secret, return pending, the eligible team list, or the one-time minted key. Send the selected team in x-anyray-team.

The Billing App also serves the browser-only /sso/cli/activate/:id, /sso/cli/authorize, and /sso/cli/callback pages. They are HTML and redirect surfaces, not integration APIs.

Desktop sign-in​

Public Billing App routes used by the Connect desktop app before it belongs to a deployment. Start returns a human code separately from the browser URL and registers an exact, randomized http://127.0.0.1:<port>/<43-character-base64url-token> callback with no query or fragment. The Billing App stores hashes, not the completion or poll secrets.

MethodPathWhat it does
post/sso/desktop/startAccept { devPublicKey, callback_uri }, bind the Ed25519 key and loopback callback, and return { id, browser_url, user_code, poll_secret, interval, expires_in }. The user_code is not part of browser_url.
get/sso/desktop/poll/:idWith the poll secret, callback-delivered completion secret, and start-key Ed25519 proof in x-anyray-poll-secret, x-anyray-completion-secret, and x-anyray-key-proof, return the signed DevCert with its deployment and gateway URL. Replayable until the ten-minute grant expires.

/sso/desktop/activate/:id only shows a code form. The browser posts the code to /sso/desktop/authorize, and one correct live code redirects to AuthKit. The callback maps the verified email domain to one live deployment and redirects to the registered listener with grant_id plus completion_secret, error=access_denied, error=access_removed (the email is on the tenant's revoked list), or error=no_company. Missing or invalid poll key proof gets the same 404 as an unknown grant. A poll for a person revoked after approval returns 200 { status: "rejected", reason: "access_removed" } and no certificate, even on a retry; the canonical signed message is in billing/PROTOCOL.md. Transient poll 503 responses carry retryable: true; configuration failures do not.

Health & settings​

MethodPathCapabilityWhat it does
get/NoneReadiness. 200 normally, 503 once graceful shutdown starts, and 503 while a metered gateway holds no entitlement lease at all (an expired or suspended lease stays 200). Point your readiness check here.
get/livezNoneLiveness. 200 for as long as the process can serve, including while draining, and checks no dependency. Point your liveness check here, never at /.
get/docsNoneThe interactive API explorer over this gateway's own spec. Shows only the API shape.
get/openapi.jsonNoneThe OpenAPI 3.1 spec of /v1/*, /admin/*, and /connect/*. Load it into Postman or a generator.
get/admin/healthconfig:readLiveness, spend-store ping, schema version (applied vs expected) with jsonbChecks column-constraint counts, optimizer reachability, pools connection budget and built-pool counters, the portal leg with leaseSource, leaseTrust, leaseAcquire, and clientIpShadow: this replica's counts for the client-IP rule that is measured but not enforced (Gateway TLS). It carries branch (trust_proxy_off, rightmost_public, railway_first_entry), railwayVariable, per-limiter seen and differs, and collapse: over the last 10 minutes, the most of today's keys that one new key would merge (maxLegacyKeysPerKey) and how many new keys merge more than 1, 5 and 20. No addresses. egressProxy says whether a corporate forward proxy is configured: configured, the proxy host:port per scheme (https, http), and noProxyEntries, never credentials (Behind a forward proxy). clientKeySnapshot describes this replica's in-memory copy of live (not revoked, not expired) client keys and the SCIM roster that /v1 verifies against: state (off, loading, fresh, or stale once the last successful load is over 30 s old, when verification reads the database per request), ageMs, keyRows, rosterRows, and lastRefreshMs, the last load's query time. Each replicas[] entry carries the same block as of that replica's last publish.
get/admin/health/seriesconfig:read + deployment ownerRead health slots for the last 24 hours, open problems and the newest 50 history rows. Optional from and to (epoch milliseconds) pick another range within the last 7 days; a range over 24 hours returns hourly points. Cached for 60 seconds per range. No live probes run on this route.
get/admin/health/problemsconfig:read + deployment ownerRead 50 problem log rows. For the next page, pass the last row's since, service, id and replicaId as query parameters.
get/admin/meNoneThe caller's identity plus deploymentOwner, false for tenant SSO principals in fleet mode.
get/admin/settingsconfig:readRead Postgres-backed runtime settings, including mcpConnectTool (default false) and mcpConnectorSignInTools (default true).
put/admin/settingscontent:manageUpdate runtime settings: contentMode, heartbeatTier, storeCapacityBytes, mcpConnectTool, mcpConnectorSignInTools, promptCacheTtl, promptCacheKeepWarm, promptCacheKeepWarmIdleMaxMinutes, promptCacheKeepWarmSubscription, promptCacheRecordedExpiry, hookStash, hookAmortizationTurns, codexSeatAppsDefer, claudeCodeRemoteMcp, personalUpstream, claudeDesktopRetrievalMcp, toolDefer, ccBudgetNotice, artifactsEnabled, artifactsOrgShare, harnessRetrieval (and, deprecated for one release, promptCache1h and promptCacheKeepWarmSubscriptionIdle). See the notes below.
get/admin/settings/prompt-cacheconfig:readThe prompt cache settings (ttl, keepWarm) and the 1 hour recommendation's state (on, recommended, off, not_enough_data, unavailable), with the last 7 days of session evidence behind it, the keep-warm returnHazard in use (the built-in prior and this deployment's observed pauses, per stop reason), and what auto picks and why. Deployment owner only.
put/admin/settings/identityidentity:manageSet how /v1/* establishes who is calling. See the warning below. Audited.
get/admin/metering/last-reportconfig:readThe exact final payload from this process's last successful verified usage report, plus heartbeat tier and metering state.
get/admin/update-statusconfig:readSelf-updater status: available vs running version, the local preflight verdict (missing environment-variable names plus breaking notes), the computed updateClass (soft or hard), the autoSoftUpdates and autoFallback policies, hasApplier, and the last manual or automatic attempt (updateAttempt, including attemptId; autoUpdate remains a compatibility alias), shared by every gateway replica (autoUpdateSource is shared, replica without a spend database, or unavailable when it cannot be read). autoUpdateLoop is this replica's last tick: its outcome (triggered, up-to-date, rejected, store-unavailable, ...) and the store it ran under. store-unavailable is one skipped tick; the next one falls back to the replica's own last attempt, so an unreachable spend database delays an update by one tick instead of stopping it. rejected and unreachable mean the applier itself failed and are reported until the retry window passes.
put/admin/update/settingsupdate:run{"autoSoftUpdates": true|false, "autoFallback": true|false}, at least one: whether image-only updates apply unattended, and whether Connect switches to direct when the gateway is down. Both default on. Platforms without an applier are notify-only.
get/admin/authNoneAuth-mode probe (pre-session; reports whether SSO is required).
post/admin/update/runupdate:run + deployment ownerTwin of POST /admin/v1/settings/updates/run (settings:update, unscopeable). Dispatch a newer manifest-confirmed soft target through the existing HTTP applier, even with auto-update off. No overrides. 202 returns target, attemptId and attempt metadata, never installation confirmation. Shared pending claim before dispatch; six-hour duplicate window; ten-second bound. 409 for hard/unknown preflight, 501 update_applier_unavailable with platform guidance, 503 for unavailable shared storage, 502 for a refused dispatch. Cross-site requests are refused. Poll the GET's running version and check fleet convergence.
get/admin/auth/updateupdate:run + deployment ownerCompatibility capability probe: 200 only when the pending update is soft, else 403. The gateway POST enforces its own checks.
Point liveness at /livez, not at /

/ answers "should traffic be routed here?" and turns 503 the moment graceful shutdown begins. A liveness probe aimed at it restarts the container mid-drain and severs every in-flight streaming response, which client tools report as "Connection closed mid-response" against the gateway.

/livez answers "should this process be killed?" and checks no dependency on purpose: a liveness probe that tested Postgres would turn a brief database blip into a fleet-wide crashloop. For dependency health use /admin/health, a diagnostic rather than a probe. Both exist from v1.10.224, and the Helm chart wires them automatically.

identityMode: network-trusted removes the key requirement

PUT /admin/settings/identity takes {"identityMode": "enrolled"|"network-trusted"}. enrolled is the default and requires every request to present a minted client key. network-trusted lifts that requirement for a deployment that is already network-isolated, attributing spend from the request instead. It also turns off SCIM offboarding enforcement and leaves the server-held provider keys reachable by anything on that network. A key-less request counts toward no person's cap or budget or subscription allowance.

A key that is presented is still verified and still binds an authoritative identity in either mode; only the absence of one is forgiven. Single-tenant self-hosted only: a fleet-mode deployment resolves identity through the control plane, which never consults this setting, so /v1/* there still returns 401 missing_key without a valid tenant key. The separate identity:manage capability is one no scoped admin API key may ever hold.

PUT /admin/settings and the console are the only ways to set the content mode. There is no environment variable for it. storeCapacityBytes declares the durable store's real volume capacity in bytes (minimum 1 GiB; null clears it), so storage-runway alerting measures against that ceiling. Set it on any deployment whose Postgres runs in its own container or service. Without a ceiling there is nothing to measure the store against, so the runway reads as unknown instead of being guessed at. Give it the size the volume can grow to, such as an RDS storage-autoscaling maximum. It needs settings:content on /admin/v1, and it is a declaration, not a measurement. The console sets it on the Privacy page (Database storage, in GiB). Set {"mcpConnectTool": true} to replace the per-connector tools mcpConnectorSignInTools serves by default with one anyray_connect covering every waiting connector, including a row the gateway has not classified.

seatOverflow and seatOverflowHourlyMaxRequests are retired: PUT /admin/settings answers 400 retired_setting for either. Set seat overflow with the subscription fallback policy, fallback.org on PUT /admin/team-skills.

Spend & governance​

The spend surface and per-user governance: token caps and USD budgets.

MethodPathCapabilityWhat it does
get/admin/spendconfig:readIn-memory spend summary: requests and tokens per attributed user.
get/admin/spend/dashboardconfig:readWindowed dashboard: money by billing class, trend, per-model/team/user/client splits, top strategies, latency, success rate, prompt-cache economics. Includes kpis, the four headline tiles with their daily series. ?window=.
get/admin/spend/overviewconfig:readMoney by billing class, daily trend, and model/team/user/client splits, including attributed savings-only models and saved tokens by model for each user. Omits latency, cache, retrieval, and strategy diagnostics. Includes kpis and holdout (the audited holdout's cost-per-session reading for the window). ?window=; optional ?from=/?to= (inclusive UTC days, YYYY-MM-DD) narrow every aggregate except holdout and echo back as range.
get/admin/spend/active-usersconfig:readAll attributed users with billed tokens or saved tokens in the window, as users: string[]. ?window=.
get/admin/spend/activityconfig:readAI activity per person, or per agent with ?by=agent: activity spans (requests 5 minutes apart or closer join one span), request counts, and each person's reported time zone. ?window= (7d, 30d or 90d, with the same UTC-day bounds as /admin/spend/dashboard), or ?from=/?to= (ISO instants, a range of up to 91 days starting no more than 91 days ago); a range of 6 hours or less adds requests per minute. ?user= returns one person. Agent requests, keep-warm pings and service accounts never count toward a person.
get/admin/spend/usageconfig:readPer-user usage detail: per-model and per-day breakdown, org per-model rollup, cap gauges. ?window=, ?limit=; optional ?from=/?to= (inclusive UTC days, YYYY-MM-DD) narrow every aggregate and echo back as range.
get/admin/spend/quality-parityconfig:readHoldout vs treated quality parity, with tenant and pairedSession fields and measured gross-cost reduction against the 30% objective, with 95% CIs. One anyray-bench request excludes its whole tenant-scoped session from both arms and the paired-session gate. benchTraffic counts every request in those sessions, plus kit requests without a session id, and reports requests, sessions and cost. ?window=, ?cohortKind= (a strategy id selects that strategy's arm, log_read the test-log Read lane's; omitted reads the whole-optimizer arm).
post/admin/client-testsclienttests:writePublish a content-free client test result. Returns 201 {result} with a server id and receive time. Strictly accepts hashes, versions, token-shaped ids, counts, ratios and verdict enums.
get/admin/client-testsconfig:readList results newest first as {results}, ties by id descending. ?limit= is an integer from 1 to 200, default 50. ?latest=plan returns each plan's newest result, with the limit counting plans. Other latest values return 400. Tenant-bound callers see their tenant only. The v1 twin accepts the same queries.
get/admin/spend/usage/propertiesconfig:readSpend grouped by one bounded custom attribution property (?name=, ?window=): values, counts, tokens, and USD only. The raw attribution map is never returned.
get/admin/spend/sessions/:sessionIdconfig:readOne client session's spend (the Claude Code session id), split by requestKind: null for requests the client sent, cache_warm for keep-warm pings the gateway sent for it. Requests, cost, savings, and prompt, completion, and cache tokens. hookTrims counts the session's Connect hook trims (events, tokensSaved, grossSavingsUsd); batchReads.avoidedTurns counts the model turns batch reads saved. ?includeSubagents=true adds the session's subagent rows. Unknown ids read as zeros.
get/admin/auth-eventsconfig:readReason-coded auth and cap rejections with remediation hints. ?user=, ?limit=.
get/admin/user-capsconfig:readPer-user token caps and USD budgets, current-month token usage, per-user budgetUsage, and cap-alert posture. The webhook URL is never returned.
put/admin/user-capsusercaps:writeReplace the whole caps map, plus alert thresholds and webhook. Returns the GET shape.
patch/admin/user-caps/:userusercaps:writeMerge a partial edit into one user's entry (omit = keep, null = clear). Never touches alert settings. Returns the GET shape.
get/admin/spend-connectorsconfig:read + deployment ownerRead redacted Cursor, Devin, and GitHub Copilot connector state and poll status. Credentials are never returned.
put/admin/spend-connectorsproviderkeys:manage + deployment ownerPartially set, rotate, or remove write-only connector credentials. Omitted connectors stay unchanged; null or {} removes one. {connector, token} replaces only the credential, keeps enabled as stored, and keeps Copilot scope and slug unless sent. A 409 names its cause in error.code: connector_credentials_unreadable (stored credentials no longer decrypt, as after an admin-token rotation) or secret_sealing_unavailable (no private admin token).
post/admin/spend-connectors/syncproviderkeys:manage + deployment ownerSync vendor usage. Returns safe per-connector counts, or skipped/failed.
get/admin/spend-connectors/usageconfig:read + deployment ownerAggregate vendor-native usage. ?days= accepts 1 to 90, default 30.

clienttests:write is an operator capability that scoped admin keys may carry. On v1, publishing needs client-tests:write; listing needs client-tests:read (implied by config:read). Neither route needs deployment-owner access. An invalid body returns 400 with issue paths and codes only; an unavailable result store returns 503. An empty store returns {results: []}.

A scenario may include twinOf, the client plan hash its public twin represents: 64 lowercase hex characters, excluding all zeros. Omit it when there is no client plan. The gateway stores and returns it verbatim; invalid values return 400 at scenarios[i].twinOf.

Each scenario may include predictedHitRate: a number from 0 to 1 for its band's hit rate in earlier tests, or null when there are fewer than 8 calibration rounds. Omit it for an unrated band.

Each scenario may include parity, the paired check outcomes: n graded pairs, passDirect, passAnyray, anyrayWorse (pairs only the direct arm passed) and anyrayBetter (pairs only the Anyray arm passed). All are integers from 0 to 10000. The counts must reconcile, so passAnyray equals passDirect - anyrayWorse + anyrayBetter, and neither pass count nor the two pair counts together may exceed n. A mismatch returns 400 at scenarios[i].parity. Omit it from older kits.

A result may include evaluatedKinds, the optimizer kinds its test sessions applied: on (on for the tenant) and shadow (still in shadow, so live traffic does not get them yet). Both are integers from 0 to 10000, and no other key is accepted. An invalid value returns 400 at evaluatedKinds. Omit it from older kits; a missing field means not reported, not zero.

Spend connector rules​

Connector credentials (cursor.adminApiKey, devin.serviceUserToken, githubCopilot.token) are write-only. Responses show configuration and poll status, safe counts, and GitHub's non-secret scope and slug. They never return credentials, vendor payloads, signed URLs, or vendor errors.

POST /admin/spend-connectors/sync returns {connectors: [...]}. Each connector reports fetched, inserted, and duplicate counts, {skipped: true} when another replica is syncing, or {failed: true} without the vendor error. Usage keeps each vendor's units separate, and Copilot's public list value is an estimate, not an invoice total.

These routes manage the whole deployment, so in fleet mode tenant SSO users cannot use them even with the listed capability. Use the deployment owner credential or an unbound local owner session. The deprecated /admin/spend/connectors path uses the same encrypted store for older clients; new clients should use /admin/spend-connectors.

Per-user caps and budgets​

Each entry may carry a monthly token cap (monthlyTokens), a hard USD ceiling (maxUsd), an alert-only USD threshold (softUsd), and the window the dollar counters roll on (reset: never, daily, weekly, or monthly, all UTC, weeks starting Monday). Every field is optional.

put /admin/user-caps replaces the whole map, so read the current config first and send it back with your edit. Send the revision you got from GET back as expectedRevision and the write becomes conditional: if anything changed in between, the gateway answers 409 and applies nothing.

PUT /admin/user-caps
Request
{
"caps": {
"dev-synthetic-1": { "monthlyTokens": 5000000 },
"dev-synthetic-2": { "maxUsd": 200, "softUsd": 150, "reset": "monthly" }
},
"alerts": { "thresholds": [0.8, 1] },
"expectedRevision": "9f2c1ab47e05d3purelysynthetic0000"
}
Response
{
"config": {
"caps": {
"dev-synthetic-1": { "monthlyTokens": 5000000 },
"dev-synthetic-2": { "maxUsd": 200, "softUsd": 150, "reset": "monthly" }
},
"alerts": { "thresholds": [0.8, 1], "webhookConfigured": false },
"revision": "9f2c1ab47e05d3purelysynthetic0000"
},
"usage": { "dev-synthetic-1": 1284000 },
"period": "2030-06",
"budgetUsage": {
"dev-synthetic-2": { "spentUsd": 164.2, "period": "2030-06" }
}
}

For a single user prefer patch /admin/user-caps/:user. It edits one entry without re-sending the map, retries a lost race internally when expectedRevision is omitted, and cannot touch the alert settings. It is the lane built for scoped admin API keys; the minimal grant for a budget bot is ["config:read", "usercaps:write"].

Both verbs answer with the same shape, and never the alert webhook URL (only webhookConfigured). Users with no dollar budget are absent from budgetUsage.

Both limits are enforced on the inference endpoints and reject with 402 (user_token_cap_exceeded / user_budget_exceeded). Three things to know about the dollar figure:

  • It is real out-of-pocket spend, not list price. An included subscription seat accrues only its over-allowance overage, and spend equal to maxUsd counts as over.
  • maxUsd only blocks API-key traffic. Subscription seat lanes (Claude Code, Codex, Copilot, Cursor) pass even over budget and notify instead.
  • Enforcement is soft and fails open. Unattributed requests, unset limits, an unwarmed config, and a spend-store outage all pass through, and because the counter moves after a response completes, in-flight requests can overshoot.

Crossing softUsd or maxUsd posts one payload to the configured cap-alert webhook and emails your workspace's billing contact, once per user, threshold, and window across the whole fleet.

Optimizer, pricing & routing​

MethodPathCapabilityWhat it does
get/admin/optimizer/settingsconfig:readRead the optimizer pipeline config.
put/admin/optimizer/settingsoptimizer:writeUpdate the optimizer pipeline config. clientStrategies.log_read is not stored in it: it reads and writes the team policy's org-level hooks.logRead, and a change needs teamskills:write and the deployment owner.
get/admin/log-read/statusconfig:readThe test-log Read lane (log_read) in production: fleet (seats seen in 14 days: active, off by policy by level, held back by the regret guard, not available, not reporting), guard (the lane's regret guard: state, window re-read rate, trip point) and holdout (whether the Measurement card is measuring it, the paired-session reading of cost per session for its own arm, and replica-local shadow counters). Counts only.
get/admin/optimizer/session-gateconfig:readPer-tenant strategy states and last paired-session evaluation.
put/admin/optimizer/session-gate/{kind}optimizer:writeSet or clear an operator gate override. tenant_policy__<tool> is one client tool's learned-policy unit and tenant_policy the tenant-wide one: force_off prices that tool's (or every) session on the defaults, and force_on is refused.
get/admin/optimizer/session-gate/{kind}/sessionsconfig:readOne gate unit's sessions over the last days (1 to 30, default 7), at most limit (default 2000, cap 20000). Each session is its arm and arm-blind facts: tool set, entry size, cost, turns, output tokens, error and worst flags. No user, no session id. Also returns the gate's own cost statistics over every session read, and the unit's last 20 recorded looks. policy selects a policy key (default: the unit's current one).
get/admin/optimizer/tenant-policyconfig:readLearned policy versions per client tool with their state (shadow, measured, active, rejected, retired), and the gate units judging them: tenant_policy__<tool> per tool, and the tenant-wide tenant_policy. learner gives the learner's latest outcome per step: each tool's fit verdict (published, no_gain, no_holdout, thin, no_sessions, no_trace_store, already_fitted, error) with its sample sizes, and the fleet-wide tick and lock_busy.
put/admin/optimizer/tenant-policy/{tool}/{version}optimizer:writeMove a version of one client tool's line: measured starts its trial (one per tool at a time), rejected stops it, retired turns the active version off, active rolls a retired version back. Only the session gate makes a measured version active. A heavy user's own line (<tool>.u-<digest>) is trialled and judged on its own, under its own gate unit.
post/admin/optimizer/purgeoptimizer:purgePurge optimizer caches and session state.
post/admin/optimizer/erase-tenantoptimizer:purgeOffboarding: erase one tenant's context stash now. erased: true only after the delete commits; 503 otherwise. Data boundary.
get/admin/pricingconfig:readThe read-only official price table.
get/admin/agent-modelsconfig:readThe model menu with each row's provider and token limits, never the upstream model. ?keyId=<key record id> returns that key's menu and its scope.
get/admin/model-aliasesconfig:readRead model-alias mappings.
put/admin/model-aliasesmodelaliases:writeReplace model-alias mappings. Send { aliases, revision } or use If-Match to refuse a stale save with 409. A bare map contains only aliases, including an entry named revision.
get/admin/routing-configconfig:readRead routing (single / loadbalance / fallback / conditional).
put/admin/routing-configrouting:writeUpdate routing config.
get/admin/v1/routing/served?user={id} or ?agentId={id}routing:readLast 20 inference attempts, newest first; person reads cover the last 30 days. Returns { served: [{ at, model, provider?, providerKeyId?, billingMode?, status }] }. Exactly one id; malformed ids return 400 invalid_request. Org-wide reads also need traces:read (scope-minted keys) or observability:read. Other readers see only their own identities and assigned agent keys. A person's list excludes requests signed by agent keys and asserted identities.
get/admin/v1/routing/usersrouting:readList person routes with user, revision, and readable. Readable rows include route.
get/admin/v1/routing/users/{user}routing:readRead a person route. Unreadable rows return readable: false without route. 404 not_found when absent.
put/admin/v1/routing/users/{user}routing:writeSend { route, expectedRevision? }. A stale revision returns 409 conflict.
delete/admin/v1/routing/users/{user}routing:writeDelete a person route. 404 not_found when absent.
post/admin/test-requestplayground:runConsole playground: send a test request through the gateway.

Person routes hold a required default, optional rules (at most 16), and optional providerKeys. Destinations are subscription, org, or an agent routing document. Each rule matches one exact model or a known model family. User ids fold ASCII case. Changing or clearing providerKeys also needs providerkeys:manage (providers:write for API keys). A target's providerKeyId needs only routing:write. API-key requests apply these routes, including model rules and provider-key maps. Routing precedence and subscription fallback.

Routing reads (GET /admin/routing-config and GET /admin/v1/routing) mask inline provider credentials as {"$anyrayCredentialRef":"<64 lowercase hex HMAC>"}. The response also includes inlineCredentials: {present, targets}, a presence flag and the number of nodes holding inline credentials. Stored config and request dispatch stay unchanged. Guardrail check credentials (fields under a check's credentials, a sink's headers, every URL a check sends data to such as webhookURL, a top-level apiKey, and any address that embeds userinfo, a query or a fragment) are masked the same way. When the store cannot be read, a read serves the gateway's cached copy and says so with fromCache: true; reload before acting on it.

Keep these objects unchanged when saving. A successful PUT replies with the config as stored, read like a GET, so that reply saves back unchanged. If that read fails, the save still happened and the reply is 503 routing_config_saved_unreadable; reload before saving again. A reference resolves against the authoritative stored config, in the same credential field and with the same provider, host/base URL, custom endpoints, region and resource addressing. This includes targets inheriting the credential. An absent or empty root api_key can also be represented by a reference; saving restores its original state. That root object makes previous releases reject a projected config rather than store references as credentials.

Add new credentials in Provider keys, then select provider_key_id. Routing saves return 400 inline_credentials_refused for new literal credentials, 400 routing_sealed_value_refused for a sealed value (a string starting rsec:v<n>:, or an anyray_sealed object; only the gateway writes those), 400 routing_credential_reference_invalid for unknown or stale references, and 400 routing_credential_address_changed when a reference would send credentials somewhere new. A guardrail reference stays on its unchanged check, in the same guardrail field and spelling (same plugin, same parameters in the same key order, except is_enabled); otherwise the save returns 400 guardrail_credential_check_changed. A reference where the read put none returns 400 routing_credential_reference_invalid. Schema and depth checks run first. Revisions are opaque keyed tokens: send expectedRevision unchanged. A 409 routing_config_conflict, including across mixed releases, means reload and reapply the change.

Claude Desktop organization policy​

MethodPathCapabilityWhat it does
get/admin/claude-desktop-policyconfig:read + deployment ownerRead the secret-free organization policy and resolved deployment fields. Personal OAuth grants are not enumerated. An absent row reads as a disabled empty policy. builtinConnectors lists each github, microsoft365 and websearch row of an array-form managedMcpServers by its index, with status served or withheld and, when withheld, a reason: missing_client_id, missing_tenant_id, web_search_not_configured or web_search_unreadable.
put/admin/claude-desktop-policydesktopconfig:write + deployment ownerReplace {enabled, settings}. Accepts recognized Claude managed keys for models, connectors, plugins and skills, workspace and tool policy, egress, and application restrictions. Rejects server-owned inference and bootstrap fields, credential-shaped string values at every depth, headers and env, nested secret keys, unknown keys, and documents over 128 KiB. A refusal answers 400 with error.code device_profile_only_key, server_owned_key or credential_key (each with error.key), or credential_value or unknown_key.
get/admin/claude-desktop-policy/mobileconfigconfig:read + deployment ownerDownload the stable macOS trust and helper profile. Needs an enabled policy and a public HTTPS ANYRAY_GATEWAY_PUBLIC_URL. The artifact pins the bootstrap URL (?os=macos) and the bootstrap-headers helper path, and contains no credential.
get/admin/claude-desktop-pluginsconfig:read + deployment ownerList the organization's Skills, Guidance and plugins. v1 twin GET /admin/v1/desktop-plugins (desktop-plugins:read).
put/admin/claude-desktop-pluginsdesktopconfig:write + deployment ownerSave one item, kind skill, guidance or plugin. A plugin, or a save that replaces one, also needs orgplugins:manage (403 without it, and always 403 for an API key). 400 names the broken rule, 409 a skill id another item already holds. v1 twin PUT /admin/v1/desktop-plugins (desktop-plugins:write). See Plugins.
delete/admin/claude-desktop-plugins/:iddesktopconfig:write + deployment ownerRemove one item. Removing a plugin also needs orgplugins:manage. v1 twin DELETE /admin/v1/desktop-plugins/:id.
get/admin/org-plugins/install-statesorgplugins:manage + deployment ownerCounts of reporting seats by tool, plugin id, version and install state over the last 72 hours. v1 twin GET /admin/v1/desktop-plugins/install-states. Admin API keys cannot read it. Response.
post/admin/claude-desktop-plugins/git/inspect, /git/preview, /git/importorgplugins:manage + deployment ownerInspect a public GitHub repository, preview one plugin, and import it with its source recorded. Public repositories only, no credentials sent. v1 twins under /admin/v1/desktop-plugins/git/. Admin API keys cannot call them. See Plugins.
get/admin/claude-desktop-plugins/:id/sourceorgplugins:manage + deployment ownerCheck whether an imported plugin has a newer commit (one request, no download). v1 twin GET /admin/v1/desktop-plugins/:id/source.
post/admin/claude-desktop-plugins/:id/source/previeworgplugins:manage + deployment ownerPreview what an update would change. Nothing applies until the import endpoint is called with the previewed commit. v1 twin POST /admin/v1/desktop-plugins/:id/source/preview.
get/admin/claude-desktop-policy/marketplacesconfig:read + deployment ownerList the marketplaces seats can add, anyray-org-skills first and read-only. v1 twin GET /admin/v1/desktop-plugins/marketplaces.
post/admin/claude-desktop-policy/marketplaces (also DELETE)orgplugins:manage + deployment ownerAdd a marketplace by public GitHub repository (optional ref) or by bare HTTPS URL of its marketplace.json, or remove it. Writes only allowedPluginMarketplaces. Admin API keys cannot change marketplaces. v1 twins on /admin/v1/desktop-plugins/marketplaces.
post/admin/claude-desktop-policy/marketplaces/queryconfig:read + deployment ownerFetch one configured marketplace again and return its plugins, or its unreachable, malformed or unusable state with the reason and plugins: null. v1 twin under /admin/v1/desktop-plugins/marketplaces/query.
post/admin/claude-desktop-policy/connectors/probedesktopconfig:write + deployment ownerAsk the MCP server at {url} for its name and tool list, so a connector can be added from its address. Answers ok (with name, version, tools, truncated), needs-auth, or unreachable with a reason. HTTPS only, nothing stored.
get/admin/claude-desktop-policy/connectors/readinessconfig:read + deployment ownerThe most-blocking organization sign-in state, /mcp/org endpoint URL, static served and dropped counts, cached access classification, and privacy-floored adoption. Never probes connector vendors.
post/admin/claude-desktop-policy/connectors/readiness/refreshdesktopconfig:write + deployment ownerRefresh the process-local connector access cache with at most four probes in flight, then return readiness.
get/admin/claude-desktop-policy/connectors/timingconfig:read + deployment ownerRelay latency per leg (oauthFetchTools, oauthAuthorize, refreshTools, relay): sample count, p50, p95, and the last outcome. Samples live in memory on the replica that answers, so the response names it and a fleet's other replicas hold their own; capped marks a leg whose window is full. No connector URL or vendor body.
post/admin/claude-desktop-policy/connectors/authorizedesktopconfig:write + deployment ownerStart a personal OAuth flow. The result holds only the vendor authorizationUrl; after consent the grant belongs to the verified client-key user who redeems the short claim code.
get/admin/claude-desktop-policy/web-searchconfig:read + deployment owner{configured, provider, updatedAt, sealingAvailable} for the Web search key. Never the key. v1 twin /admin/v1/providers/web-search (providers:read).
put/admin/claude-desktop-policy/web-searchproviderkeys:manage + deployment ownerStore {provider, apiKey}: provider is brave, tavily or exa, apiKey 8 to 512 characters. Send both every time. The key is encrypted at rest and never returned or audit-logged. 409 when ANYRAY_ADMIN_TOKEN is unset or the public placeholder. v1 twin providers:write.
delete/admin/claude-desktop-policy/web-searchproviderkeys:manage + deployment ownerRemove the Web search key. The policy then leaves gateway-served Web search rows out. v1 twin providers:write.
get/connect/claude-desktop/bootstrapOptional client keySecret-free effective policy fetched by managed Claude Desktop clients. ?os=macos|windows|linux (default macos) selects the inferenceCredentialHelper path the response names; an unknown value is 400. A presented Authorization: Bearer ark_… is verified (401 when rejected), and no bearer is still served. 404 while disabled.
post/connect/claude-desktop/websearchClient keyThe address the policy's Web search row names. Takes {"q": "..."} (1 to 400 characters), searches with the stored key, and answers {"results": [{"title", "url", "snippet"}]}, at most ten. 401 without a live key, 429 past 30 searches a minute per key, 503 web_search_not_configured or web_search_unavailable, 502 web_search_upstream_failed. Queries and results are never logged or stored.

Current Claude Desktop (1.34493.1 and later) authenticates the bootstrap fetch through the MDM-pinned bootstrapHeadersHelper, which Anyray's wrapper fills with the enrolled user's client key, and requires the response to name inferenceCredentialHelper. The route still serves an unauthenticated fetch so same-origin managed clients work without a customer OIDC issuer, so treat connector URLs and restrictions as publicly readable. Credentials and OAuth grants are never accepted into the stored document. The authenticated admin routes stay available under --headless, so operators can always inspect, disable, replace, or export a policy that the bootstrap route serves.

Field detail and worked examples: API reference.

Providers​

MethodPathCapabilityWhat it does
get/admin/provider-keysproviderkeys:manageList configured providers and named-key metadata (capabilities.keyIds). unreadable: true means this gateway cannot decrypt the stored keys, so config is empty (provider keys).
get/admin/provider-capabilitiesconfig:readThe same list without credentials, so a role that may route but not read a key can still fill its pickers. capabilities.personalUpstream is true on a gateway with the personal upstream setting.
put/admin/provider-keysproviderkeys:manageSet server-held provider API keys (per-slug patch; per-key form for named keys; fields patches for changed fields). Returns {status, capabilities} with provider slugs and key ids only, never credential values. On a store this gateway cannot decrypt, a full re-entry answers 409 provider_store_unreadable unless sent with ?replaceUnreadableStore=true.
get/admin/provider-resourcesproviderresources:manageWho owns the files, batches, fine-tuning jobs and stored responses created on the org's provider keys, oldest first: {data, next_cursor}. Each row has provider, kind, resourceId, owner, parentId and createdAt. Filter with ?provider=, ?kind= (file, batch, fine_tune, response) and ?owner=; page with ?limit= (1 to 500, default 50) and ?cursor=. Ownership records only: never a provider call or content.
get/admin/provider-resources/:provider/:idproviderresources:manageOne resource's owner as {resource}, with owner: null once released, or 404 when the gateway has never recorded it.
put/admin/provider-resources/:provider/:id/ownerproviderresources:manageSet the owner, {"owner": "<user>", "kind": "<kind>"}: transfer a resource, or give one created before ownership checks its creator back. Audited. 400 for an owner that is not a specific user, such as unknown.
delete/admin/provider-resources/:provider/:id/ownerproviderresources:manageRelease the resource, {released}. It stays admin-only until it is assigned again. Audited.

Access, SSO & enrollment​

MethodPathCapabilityWhat it does
get/admin/idp-configconfig:readRead the SSO / IdP configuration.
put/admin/idp-configidp:manageConfigure SSO / IdP.
delete/admin/idp-configidp:manageRemove the SSO / IdP configuration.
get/admin/revoked-usersconfig:read + deployment ownerList revoked user emails.
post/admin/revoked-usersidp:manage + deployment ownerRevoke a user: kills their client keys and blocks re-enrollment. The offboarding step.
delete/admin/revoked-users/:emailidp:manage + deployment ownerUn-revoke, allowing re-enrollment.
get/admin/console-membersconfig:read + deployment ownerList who may sign in to the console by email, and each one's role.
put/admin/console-membersidp:manage + deployment ownerAdd an email or change its role, up to operator. You can only grant a role whose capabilities you hold.
delete/admin/console-members/:emailidp:manage + deployment ownerRemove an email. Their current session ends within 12 hours.
get/admin/connect/directivesobservability:read + deployment ownerDirective history, every status including acked, newest first. ?user=<userHash>, ?limit=.
post/admin/connect/directivesidp:manage + deployment ownerQueue a per-user connect remediation directive. See the kinds below.
get/admin/scim/settingsconfig:read + deployment ownerRead redacted SCIM state, admin group, and group-to-team mappings. The bearer and its hash are never returned.
put/admin/scim/settingsidp:manage + deployment ownerSet or rotate the SCIM bearer and update group mappings. Omitting bearerToken preserves it.
post/admin/workos-portal-linkidp:manageMint a WorkOS admin-portal link.
get/admin/sso/start · /admin/sso/callbackNoneBegin the console sign-in handshake, and its redirect callback (pre-session). Uses the org's SSO when set up, and an emailed code otherwise. /admin/sso/start is limited per IP at the unauthenticated rate.
post/admin/sso/logoutNoneEnd the console SSO session.
get/admin/client-keysconfig:readList minted client keys (enrolled users). A row whose user had a Claude subscription token refused carries seatRejections.
post/admin/client-keysclientkeys:manageMint a client key.
delete/admin/client-keys/:idclientkeys:manageRevoke a client key.
get/admin/service-keysconfig:readList non-human service keys. Tokens and hashes are never returned.
post/admin/service-keysclientkeys:manageMint an ark_svc_… key for an AI agent, CI job, or SDK script.
delete/admin/service-keys/:idclientkeys:manageRevoke a service key.
get/admin/api-keysconfig:read + deployment ownerList scoped admin API keys (aak_…). Metadata only.
post/admin/api-keysadminkeys:manage + deployment ownerMint a scoped admin API key with exactly one grant shape. Scope-minted keys are v1-only. A key cannot mint keys.
delete/admin/api-keys/:idadminkeys:manage + deployment ownerRevoke a scoped admin API key. A key cannot revoke keys.
get/admin/enrollment-linksconfig:readList enrollment / setup links.
post/admin/enrollment-linksenrollment:manageCreate an enrollment link with install.sh and install.powershell commands for the default tool choices.
delete/admin/enrollment-links/:idenrollment:manageDisable an enrollment link (…/:id/hard hard-deletes it).
get/admin/provisioning-tokensconfig:readList deployment provisioning tokens.
post/admin/provisioning-tokensprovisioning:manageMint a provisioning token. kind: "mdm", identityMode: "email", and proofMode: "macos-bootstrap" return the macOS certificate profile once in mobileconfig; list responses never return it.
delete/admin/provisioning-tokens/:idprovisioning:manageRevoke a provisioning token.
post/admin/provisioning-tokens/:id/rotateprovisioning:manageRotate a provisioning token. A macOS bootstrap rotation returns a new one-time profile and certificate fingerprint.

Directive kind is reapply_config, remint, revert, revert_tool, or update. The machine pulls it through /connect/directive, within about two minutes on the desktop app and within 30 minutes on CLI-only machines. Delivery is pull-only and never pushed. Only revert_tool takes a tool (a lowercase adapter slug): it un-routes that tool and leaves the seat enrolled for the rest. update runs the verified Connect self-updater on supported CLI runtimes only, and the desktop poll excludes it. See update limitations.

Service key request and response​

post /admin/service-keys accepts name and team, each 1 to 64 characters of letters, digits, dot, colon, hyphen, or underscore (the name feeds the ANYRAY_SEAT_EXCLUDE glob matcher, so spaces and * are rejected), plus an optional non-negative monthlyBudgetUsd, an optional delegatedAttribution boolean (see AI agent enrollment), and an optional telemetryOnly boolean (an ingest-only key for the Claude telemetry receiver: /otel/v1/* accepts it and every inference route answers 401 key_telemetry_only). name becomes the key label and attributed service identity.

POST /admin/service-keys
Request
{
"name": "synthetic-release-agent",
"team": "platform",
"monthlyBudgetUsd": 50
}
Response
{
"key": "ark_svc_synthetic-once-only",
"record": {
"id": "synthetic-key-id",
"user": "synthetic-release-agent",
"team": "platform",
"label": "synthetic-release-agent",
"tenant": "default",
"createdAt": "2030-06-01T00:00:00.000Z",
"keyType": "service",
"monthlyBudgetUsd": 50,
"source": "service",
"type": "service"
}
}

The 201 returns the raw ark_svc_… value exactly once. The raw key cannot be recovered after minting; revoke it and mint a replacement. DELETE /admin/service-keys/:id returns { "ok": true }.

Once the key has signed a verified request, the record also carries firstSeenAt, lastSeenAt, and lastSeenTool: an inferred client id (codex, claude-code, shell-env, unknown) or the tool the client sent in x-anyray-metadata (openclaw), never the raw User-Agent. A key with none of the three has not signed a request since these fields shipped, which is how the console's Agents card tells "never connected" from "active". The same fields appear on GET /admin/v1/keys.

When the last request came from Claude Code, the record also carries lastSeenEntrypoint: cli (a terminal session or a headless claude -p), sdk (the Claude Agent SDK), ide, or other. It is overwritten by every request, so a key that moved to another tool has none, and a gateway that predates it omits it.

The budget covers the key's successful request cost in the current UTC calendar month. At or above it, inference returns 402 with error.code: "service_key_budget_exceeded". Enforcement is a soft cap: counters update after the response, so concurrent requests can overshoot, and counter failures pass traffic through. Omitting the budget, or setting it to 0, leaves the key unlimited.

Privacy, support & observability​

MethodPathCapabilityWhat it does
get/admin/gdpr/users/:user/exportgdpr:manageExport a user's spend records, and the provider files, batches, jobs and stored responses they own (providerResources: provider, kind, id and creation time).
delete/admin/gdpr/users/:usergdpr:manageErase a user's spend rows and cap counters, client keys, context-stash rows, traces and ownership records. Files, batches, jobs and stored responses they created on the org's keys become admin-only. An owner-level request also erases their connector observations and team-policy overrides; agents the user owns are unassigned and keep running.
get/admin/support/bundleconfig:readGenerate a support bundle for remote debugging.
post/admin/support/bundle/sharecontent:manageRebuild, redact, and send a support bundle to the Billing App. Returns its receipt and audit metadata.
get/admin/observability/traces/:idobservability:readFetch one trace. Content is redacted unless the caller holds observability:read-content. A caller holding only observability:read-own sees their own traces and their agents'; anything else answers 404.
get/admin/observability/*observability:readProxy to the observability read API: lists (traces pages as /admin/v1/traces does), sessions, traces/exists (whether any trace is visible to the caller), and traces/facets (the distinct model, provider and client ids behind the Sessions filters; pass fromTimestamp, toTimestamp or sessionId to read only that slice).
get/admin/artifactsobservability:read-ownPublished pages shared with the signed-in person (by name, through their IdP team, or with the whole organization while artifactsOrgShare is on), newest first, with titles and no bodies: { data, next_cursor, skipped } (?limit=, ?cursor=). skipped counts pages in the window that could not be read; next_cursor still continues after them. Only a person's console session reads it: the admin token, a console session signed in with it, and API keys get 403 artifacts_person_session_required. A person the IdP roster deactivated gets 403 artifacts_person_deactivated. Works with publishing turned off. 409 artifacts_unavailable (no_store, no_content_key) when pages cannot be read, and 503 artifacts_read_failed while the store or the IdP roster cannot be read. A malformed cursor answers 400. On every artifact route, an SSO session another organization minted gets 403 artifacts_foreign_tenant, and SSO sessions get 503 artifacts_lease_unavailable while the gateway holds no lease.
get/admin/artifacts/allobservability:readEvery published page in the organization as metadata only (owner, who it is shared with, size, created), never a title or body. Pages like /admin/artifacts. A scope-minted key needs artifacts:metadata on /admin/v1/artifacts/all.
get/admin/artifacts/:idobservability:read-ownOpen one page shared with the signed-in person: { data } with its title and body, sent Cache-Control: no-store. A page that does not exist or is not shared with them answers 404. An open by anyone but the author is audited. Refusals match /admin/artifacts.
delete/admin/artifacts/:idcontent:manageRemove anyone's published page. 204, or 404 when there is none. An author removes their own with DELETE /connect/artifacts/{id}. On a multi-tenant gateway the admin token, which names no tenant, gets 409 artifacts_tenant_required here and on /admin/artifacts/all.
post/admin/traces/connector-credential-scrubcontent:manage + deployment ownerFind connector credentials that older gateways stored in trace content and list the exposed client keys by id. A dry run unless the body is {"apply": true}, which rewrites those rows redacted. Stops after 20 seconds; send back the returned cursor to continue. 409 while another apply runs. Never rotates a key. Data boundary.
get/admin/connect-healthobservability:read + deployment ownerPer-seat connect-health rollup over the last 24h. ?limit= caps seats (1 to 1000, default 200); ?user= narrows to one userHash; ?shape= (repeatable, up to 32 command shapes such as grep or git show) adds those families to hookArrivalShadow. See below.
get/admin/team-skillsconfig:readRead shared skills, tool and update policy, eight resolved org hooks.* switches, and stored hooks.teams/hooks.users overrides. An unreadable store reports all switches off and no maps.
put/admin/team-skillsteamskills:writeReplace shared skills and edit hook switches. hooks.digest and hooks.logRead default on; readTrim, lifecycle, prompt, digestEconomics, rangedArrival, and fleetReadBack default off. Omitted hook switches keep their stored values. hooks.ghRunLog and hooks.shellRewrite are retired: a write may still send them, and they are ignored. An explicit hooks.teams or hooks.users map replaces that map; {} clears it. claudeCodeTools.denyFirstPartyOnly and claudeCodeTools.unpinToolSearch (on or off, default off) opt Bedrock-routed Claude Code seats into the two claude-code-*-v1 capabilities, org-wide or per claudeCodeTools.teams and claudeCodeTools.users, with the same merge rules as hooks. tools (enabled, tiers, followsDefault), updates.mode (auto, directed, off) and lanes.teams / lanes.users (auto, subscription, org; a user beats their team) keep their stored values when omitted; a sent tools or lanes object, {} included, replaces it. Answers 503 when the stored policy cannot be read.
get/admin/wizard-statusconfig:readOnboarding wizard progress.
put/admin/wizard-confirmwizard:writeConfirm an onboarding wizard step.

Team-policy GET and PUT responses include revision; send it as optional expectedRevision on PUT to get 409 if another admin saved first.

integrationLevel stores org, teams, users, and agents assignments. Values are gateway, gateway_hooks, or gateway_hooks_mcp. Agent map keys are service-key ids. An omitted field keeps its stored value; an empty map clears that scope. Seats resolve user, team, org, then the gateway_hooks_mcp default; agent keys resolve agent, team, org, then default. The gateway enforces the resolved level per session, and Connect reports the level it applied on its daily health check.

/admin/connect-health is content-free: hashes, enum tokens, counts, and timestamps only. Every request with a verified developer key can refresh durable presence, with at most one write per developer every five minutes on each gateway replica. A Connect version header creates or updates the seat and its reported version. Traffic without one only refreshes an existing seat and never erases a known version. Connect sends the header only from the calls that reflect the installed build: the refresh tick's verify and heartbeat, its daily health report, doctor, and endpoint enrollment. Hooks and a running MCP server never send it, so a server started days ago cannot roll the version back. A seat on a key with no enrollment cert still reports through the tick's health report. Traffic keeps an existing credential lane unchanged. A seat first discovered from versioned traffic starts in the heartbeat lane. Each row carries the pseudonymous userHash, last-seen instant, credential lane (verify or heartbeat), connect version (null identifies a client that has not reported one), per-check statuses when reported, and a 0 to 100 compliance score against the tools the fleet policy requires (Required, stored as locked; every other tool is Available, the person's choice). When Connect reports claude-code/applied_integration_level, its passing check carries the level in errorClass, and the seat row exposes the latest recognized value as appliedIntegrationLevel (gateway, gateway_hooks, or gateway_hooks_mcp). The field is absent for older Connect builds and unrecognized values. The check does not affect the compliance score or failing check count. Connect applies policy changes on its next tick and for new sessions. hookArrivalShadow reads back the ranged-trim verdicts Connect reports on its bets over the last 30 days: pooled, and one entry per ?shape= family, each with rangedBets, withVerdicts, unpriced (bets by the reason arrival was null) and cells, one per verdict pair (today, arrival) with bets, readBacks, readRounds, horizonServed, todayNetMean and arrivalNetMean. With the posture off every bet is one the shipped check placed, so the cells to compare are today: true, arrival: true (both) and today: true, arrival: false (shipped check only). The trims only the arrival check would add place no bet, so they come as arrivalOnly, apart from the cells: precheck and final, each with candidates, netMean, horizonServed and buckets (candidates by floor(log2(characters removed))). precheck is an upper bound, priced as a trim to nothing; final is priced at the gateway's real trim. The whole answer is absent when the bet store cannot answer. When no tool policy has ever been saved, GET /admin/team-skills omits tools and returns read-only toolsDefault instead.

Organization MCP OAuth​

URL-only MCP clients authenticate to /mcp/org through the WorkOS SSO connection already attached to the organization. WorkOS AuthKit is the authorization server, and the gateway is the protected resource.

MethodPathAuthWhat it does
get/.well-known/oauth-protected-resource/mcp/adminpublicAdmin resource metadata with its own audience and AuthKit issuer. No tool list or identity.
post/mcp/adminAuthKit bearerRemote admin MCP. Stateless JSON-RPC; live console role checked per request; 2 MiB body bound.
get · delete/mcp/adminAuthKit bearerAuthenticate, then return 405 with Allow: POST. No stream or session.
get/.well-known/oauth-protected-resourcepublicReturn the exact https://<gateway>/mcp/org resource, the managed AuthKit issuer, and bearer-header support. Cache-Control: no-store; bursts return 429 with Retry-After.
get/.well-known/oauth-authorization-serverpublicCompatibility proxy for clients that skip protected-resource discovery. Fetches AuthKit's metadata through the outbound SSRF guard and returns it only when the issuer matches and S256 is supported.
post · delete/mcp/orgclient key or AuthKit bearerServes the organization's remote connectors under the verified caller's identity. DELETE acknowledges session closure with 204.
get/mcp/orgclient key or AuthKit bearerAuthenticate, then return 405 with Allow: POST, DELETE.

Authless tools are shared. When an OAuth connector needs this caller to sign in, a gateway-owned anyray_signin__<connector> tool appears for it, one per waiting connector with no ceiling. Once a grant exists, the connector's <connector>__<tool> tools replace it. The retired anyray_mcp__ and anyray_oauth__ prefixes still route for one release.

anyray_connect accepts three call shapes:

CallResult
no argumentsLists the connectors waiting for the caller. Starts nothing.
{ "connector": "Atlassian" }Returns an authorization URL. The vendor callback shows a single-use, five-minute claim code.
{ "connector": "Atlassian", "claim": "ABCDEFGHJKLM" }Binds the grant to the verified caller who redeems it.

A usable grant blocks both start and redemption, so the tool cannot replace a working account connection.

GET /admin/settings returns healthDetailShared: true by default. Change only that field with PUT /admin/settings and {"healthDetailShared":false}. Writes require content:manage and deployment ownership, and changes are audited. Reads require config:read and deployment ownership. An unreadable store reads false. The /admin/v1/settings twins use settings:read and settings:content respectively; the write also works through /admin/v1/settings/content. The floor tier suppresses sharing without changing the stored choice. See Data boundary.

Two settings govern which sign-in tools appear, both through PUT /admin/settings or PUT /admin/v1/settings:

SettingDefaultEffect
mcpConnectorSignInToolstrueServes one anyray_signin__<connector> per waiting connector, no ceiling. Set false to serve no sign-in tool at all.
mcpConnectToolfalseReplaces the named tools with one anyray_connect covering every waiting connector, including rows the gateway has not yet classified.

Each named tool adds roughly 80 prompt tokens to every request against a flat 49 for the shared one, so an organization with many connectors waiting at once can turn on mcpConnectTool to trade findability for a shorter prefix. A remembered OAuth tool called without a usable grant returns 200 with result.isError: true and names the connector; it points to a sign-in tool only when one is actually being advertised, and it never starts authorization.

On a deployment whose SSO sign-in is live, a missing credential returns 401 with:

WWW-Authenticate: Bearer resource_metadata="https://<gateway>/.well-known/oauth-protected-resource"

A deployment with no SSO sign-in advertises no authorization server at all. /mcp/org returns 401 naming the client key and omits WWW-Authenticate, and both discovery documents return 404, so a URL-only client stops rather than completing a login whose token this gateway can never accept.

Token requirements and the full verification chain: API reference. A presented x-anyray-api-key, or Authorization: Bearer ark_..., takes precedence over the AuthKit lane.

/mcp/org accepts one request per POST and returns 405 for GET, so it has no server-push channel. Its initialize response does not advertise tools.listChanged, so clients must issue tools/list again after consent, and clients that cache the list may need to reconnect.

anyray-connect contract: /connect/*​

Called by the anyray-connect CLI and its hooks. The managed workflow SDK uses the strategy policy and report routes. Carries savings beacons, policy, and CCR retrieve/recall. No admin capability; authenticated as the connected client.

MethodPathWhat it does
post/connect/verifyVerify a client key or enrollment.
get/connect/policyFetch the client's effective policy: shared skills, routing origin, bound team, and the fleet tool set. See below.
get/connect/strategiesRead { revision, strategies: { symbol_context, read_only_workflows } } for the verified key's user, team and tenant. Both switches default off. 404 means the optimizer predates support; 503 means policy could not be read. Read once per new client/app session.
post/connect/strategies/reportReport { clientStrategies: { symbol_context?: { state, revision? }, read_only_workflows?: { state, revision? } } }. Only known runtime states and hash revisions are accepted; the server supplies observation times. Stores per-key metadata, never tool content, names or savings. Does not establish MCP retrieval capability. The key's admin readback exposes the last clientStrategies reports.
post/connect/artifactsPublish a page to share inside the organization: { title, body, contentType, share? }, where contentType is text/html, text/markdown, image/svg+xml or upload-only text/jsx and share is { org, users, teams } (default: only you). The owner is always the key's own user, so an agent (service) key gets 403 artifacts_person_key_required. 201 returns metadata only, plus url, the page's console link (<console>/console/artifacts/<id>), when the gateway knows its console URL (ANYRAY_CONSOLE_PUBLIC_URL, else its own host on port 3000). 409 artifacts_unavailable names why publishing is closed: turned_off, content_off, no_content_key or no_store. 400 for a malformed page or a share entry that is not a person or team id, and 400 artifact_refused (org_share_off) for share.org while artifactsOrgShare is off; 400 artifact_refused also names unsupported_import, no_default_export, compile_error or too_large for React. JSX needs a default export and imports only from react, react/jsx-runtime, react-dom or react-dom/client; it is compiled to stored text/html (200 KiB source, 2 MiB output, five-second build limit). SVG needs an <svg element. 413 over 5 MiB; 503 artifact_busy when eight builds are already waiting for the two compiler slots, or a wait reaches five seconds. Publishing is limited to 10 requests per minute per key across all content types (429 with retry-after), checked before reading the body. Storage quotas also return 429 once the owner keeps 500 pages or 250 MB. 503 key_store_unavailable when the gateway cannot read its key store, so it cannot verify the key: retry. Needs artifactsEnabled. A team share opens for the people the IdP puts on that team (SCIM, or Directory Sync and SSO groups).
get/connect/artifactsList the pages shared with the key's own person, newest first: { data, next_cursor, skipped, console_url }, metadata only (no body). skipped counts pages left out because their title would not decrypt; 0 is a complete list. The answer has an ETag over the ids in the window you asked for (and whether older pages follow it), so a page added far behind it leaves the tag alone: send it back as If-None-Match and an unchanged list is 304 with no body. A key may list 20 times a minute (429 with retry-after), and open 120. console_url is the console's artifacts base (<console>/console/artifacts/), left out when the gateway does not know its console URL; append an id to get a page's link. Both reads carry it. Takes limit (default 100, max 500) and cursor; 400 for a cursor that did not come from next_cursor. Uses the console's visibility rule: the owner, named people, a team the IdP puts them on, or the whole organization while artifactsOrgShare is on. 403 artifacts_person_key_required for an agent (service or machine) key, 403 artifacts_person_deactivated for a deactivated person, 409 artifacts_unavailable when pages cannot be read. Works with publishing turned off. Cache-Control: no-store.
get/connect/artifacts/{id}Open one page: { data } with the body. 404 artifact_not_found for a page that is missing or not shared with the key's person, so the two cannot be told apart. Same refusals as the list. Each open by someone other than the owner is audited with ids only.
delete/connect/artifacts/{id}Remove a page you published. Works with publishing turned off. 204, or 404 for a page that is not yours.
get/connect/client-assets/{kind}/{sha256}Download a client build this gateway serves, by its sha256: vscode-extension (the VSIX) or openclaw-plugin (a JSON document of the plugin's files). Key-gated. 404 for a kind or sha this image does not carry.
get/connect/self-checkWhat the calling key can ask about its own wiring, for an agent host that cannot run Connect. Content-free and scoped to the presented key: gateway.reachable, identity.keyKind (service or personal), retrieval (per-runtime verdicts over the last day, or unavailable when the read failed, or unmeasured_personal_key), key (this key's own traffic evidence), runbookVersion, skillVersion, skill (whether each runtime listed the anyray skill to its model over the last day), and problems as a count. Answers from a 15 second cache, so poll no faster. See below.
post/connect/test-keyMint a seven-day test key with your personal Connect key. Returns { key, keyId, expiresAt } once. Replaces your previous test key in the same tenant; cannot renew.
get/connect/test/contextTest key only. Returns its tenant, gateway version, evaluation-cohort marker, session-gate counts, and running replicas.
post/connect/test/resultsTest key only. Store a content-free benchmark result using the strict /admin/v1/client-tests body.
get/connect/test/resultsTest key only. Read its tenant's results; latest=plan selects the newest per plan. limit is 1 to 200 (default 50).
get/connect/test/sessions/:sessionIdTest key only. Read this key's request count and list-price cost for the session, with keep-warm pings counted separately.
get/connect/savingsReport realized savings for the client.
get/connect/usageThe key holder's own dashboard: the four headline tiles in kpis, a daily trend, and tokens kept and saved per model and per tool. At most 6 models and 8 tools are named, fewer when ids are very long; the rest are reported together as (other). The user is always the one the client key belongs to. ?window= (default 30d). A user with no usage gets zeros. 429 when a key polls more than 20 times a minute. 503 when the spend store cannot be read.
post/connect/hook-savingsRecord hook-trim savings (PostToolUse) under the verified client key's user, credited to the strategies the beacon names (known strategy ids only), else hook_local_trim. A beacon with no client key, or a rejected one, records nothing, and so does a claim that rounds to 0 tokens. Each user has two hourly caps in any 60 minutes, shared by the three hook lanes and counted per gateway replica: 5M claimed tokens, and 6.25M billed units (5M x 1.25). The units cap admits fewer claimed tokens the more later turns a trim is credited. A beacon that would pass either cap records nothing. A paste beacon (source: "prompt") counts only from Cursor (tool: "cursor"), as advisory potentialPaste that is never billed and not held to those caps. Any other paste beacon records nothing: the optimizer's paste_gap measures those pastes. A beacon that names its tee (tee: { id, shape, lines }) also records the trim as a bet for the ranged lane. A ranged bet may add tee.verdicts, the two verdicts Connect priced it with (today and arrival, true or false, arrival null when it could not be priced), each rule's predicted net (todayNet, arrivalNet, token-equivalents), horizon (served or estimator) and unpriced (why arrival is null); the bet keeps them, and a field outside that shape is dropped. event: "arrival-candidate" with candidate (shape, stage, net, sizeBucket, horizon) reports a ranged trim only the arrival check would admit: a verified key counts it per UTC day under the family's pseudonym, with no saving, spend row, bet or seat. event: "tee-read" with tee.id (plus range.lines for a sized read) marks that bet read back. Both take a verified key and store pseudonyms and counts only. event: "digest-stood-aside" with digest (router, version, kept, total) and originalChars reports a digest the digestEconomics gate held back: a verified key records it on that call's trace as a command_digest decision with standAside: true and no saving, never a spend row (a log_read decision when digest.strategy is log_read, the test-log Read lane's cost gate). An older gateway answers it recorded: false. A saving whose digest.strategy is log_read (with the test-run router) is booked and shown as the log_read strategy; an older gateway ignores the field and records it as a test-run command digest. event: "log-read-declined" with reason (ranged, reread, raw_latched, no_failures, changed, not_whole_file, not_test_output, failures_not_kept, failure_block_cut, page_unbounded, no_saving, would_misnumber_lines or digest_stood_aside), originalChars and tool reports a Read of a test log that lane left whole: a verified key records it on that call's trace as a log_read decision with standAside: true and the declineReason, counts it on the trace's strategyDeclines, and books no saving and no spend row. A reason outside the list records nothing, and at most 600 are recorded per user per hour. A recorded bet gets readBack back when the bet store can answer: that shape across every seat, in the same form as on the ranged reply below, which Connect keeps per shape. A saving may name the request of the session it fired on (requestsSoFar, a non-negative integer; anything else is ignored): the gateway reads the deployment's survival curve at that request to credit the trim's later cache reads. A saving and a stand-aside may name the client whose hook sent them (clientTool, such as claude-code or codex): the spend row, the bet, its readBack and the trace take that id when it is a client tool id the gateway knows, else unknown. Without it they take the request's own client tool (the tool in x-anyray-metadata, else the User-Agent), held to the same ids. A tee-read may carry it too, but its bet keeps the id that placed it.
post/connect/optimize-outputOptimize a tool output at the source: a reversible trim, or a ranged trim for a tenant that turned rangedHookTrim on. Anything else returns a no-op. The saving shares the hook-savings caps (5M claimed tokens and 6.25M billed units per user in any 60 minutes, per replica); past either, the trim still comes back but is not recorded. The body may carry requestsSoFar, the session's request count, read the same way as on /connect/hook-savings. See below.
post/mcpThe gateway as a remote MCP server (streamable HTTP): serves anyray_retrieve and anyray_recall, client-key gated. To Claude Desktop (by its User-Agent) on a person's key, tools/list also lists anyray_list_shared and anyray_open_shared while artifacts.mcpRead on /connect/policy is true. A call to either takes any person's key and follows GET /connect/artifacts: the same visibility rule, tenancy, paging, and audit of an open by someone other than the owner. Every valid authenticated JSON-RPC message renews the key's 6-hour retrieval lease. Each served tools/call counts toward the per-tool adoption bins, the same ones the stdio server's heartbeat fills. GET /mcp and DELETE /mcp answer 405: there is no MCP session to stream or end.
post/connect/recall · /connect/retrieveCCR recall and retrieve of externalized context. A human or CLI caller sends x-anyray-retrieve-probe: 1 so the call does not latch retrieve capability.
post/connect/mcp-heartbeatMCP-server liveness heartbeat, which authorizes the deferral_latched evidence class when the tool is hidden behind deferred loading. Optional body { "client": "<name>", "retrieveApproved": <bool> } authorizes approval_latched on clients that never name the tool in the request at all; sending no body leaves a stored approval as it was.
post/connect/healthRecord anyray-connect doctor results or the authenticated daily selfcheck, plus the client's own version. The ambient report updates the verified seat and its per-user check bins. Best-effort, 200; the deployment-wide rolling 24h rollup travels on the next meter round. A report without a verified client key (before enrollment, or a rejected key) is recorded only within the per-IP unauthenticated rate (ANYRAY_RATE_LIMIT_UNAUTH_RPM, default 10 a minute); past it a keyless report gets 429.
post/connect/key/heartbeatExtend the existing enrollment key without replacing it, including recovery after bearer expiry. Needs the current ark_ key, the signed DevCert, and a fresh purpose: "key-heartbeat" proof. The bearer alone is insufficient, and no replacement key is ever returned.
post/connect/machineReport the machine-to-seat join, which fleet Connect updates read. Gated like /connect/key/heartbeat, with machineUuid as a sibling of the proof. Only sha256 hashes are stored, never the raw UUID, hostname, or username. Off-Postgres nothing is stored (recorded: false).
get/connect/directivePull the seat's next remediation directive ({directive:{id,kind,tool?}} or {}). Key-gated and scoped to the caller's own seat; a directive re-delivers until acked. See below.
post/connect/directive/ackAcknowledge an applied directive ({id, result:"ok"|"failed"}). Scoped to the caller's own seat, so a leaked id cannot close another developer's directive. A revert reporting ok also marks the seat offboarded; the kind is read from the stored directive, never the payload.
post/connect/offboardAnnounce that this seat has been reverted off Anyray, sent while the credential it is about to delete still authenticates. Always 200 with {status:"ok", recorded:<bool>}.
post/connect/mcp-oauth/authorizeStart an OAuth browser flow. See below.
post/connect/mcp-oauth/completeRedeem the code shown in the consenting browser: { "name": "connector name", "claimCode": "12-character code" } → { "status": "ok", "user": "verified account" }. The grant binds to the account redeeming the single-use, five-minute code.
GET /connect/policy fields

Returns shared skills (the console's Skills and Guidance plus any legacy team skills), routing origin, bound team, and the fleet tool set (tools.enabled plus a tools.tiers entry for each).

The built-in anyray skill carries variants["claude-code"], Claude Code's version, beside the shared body. A service key, or ?tool=sdk-agent (also langchain or langgraph), gets the agent version as body and no variants. Any other tool is ignored. See which version each machine gets.

A deployment that never saved a tool policy is served the default set, every tool recommended, with tools.defaulted: true, so a tool installed after enrollment still gets configured. A saved list with tools.followsDefault: true (what the console's Users → Add User → Required tools writes) is served with every default tool it lacks added as recommended, so a tool Connect adds later reaches the fleet. A seat whose developer named their own tools with --tools ignores a defaulted list, and an admin's saved list always wins. Every enabled tool is served with a tier: locked is Required, anything else is Available. A stored required or missing tier is served as recommended, and a stored optional is served unchanged. A seat enrolled from the desktop app takes only locked tools from either list; the person turns on the rest. skillsIncomplete: true means a skills store could not be read this call, so clients write what is listed and remove nothing.

With ?upstream=<origin> the response adds upstream.authorized: whether that host is allowed as a custom host here. Connect asks before it keeps a developer's own LiteLLM key. The allowlist is never returned.

personalUpstream: { "url": "https://litellm.example.com" } is the saved personal upstream. It is served to a personal developer key only, never to a service or agent key, and left out when the setting is off.

seat.anthropic.rejected lists the caller's own Claude subscription tokens Anthropic refused, newest first, at most 5: fingerprint, rejectedAt and reason. Connect sets aside the stored token whose fingerprint matches. Omitted when there are none, and by an older gateway. See rejected subscription tokens.

artifacts: { "publish": true } means this key's POST /connect/artifacts would be accepted now: a person's key while artifactsEnabled is on and content can be stored. An agent (service) key always reads false. A gateway that predates sharing omits the field; read that as off. It does not cover share.org, which artifactsOrgShare can refuse.

artifacts.read is true when GET /connect/artifacts would answer for this key: a person's key, with the content store and key present. It stays true while publishing is off. A gateway that predates reading omits it; read that as off.

artifacts.mcpRead is true when POST /mcp lists anyray_list_shared and anyray_open_shared to Claude Desktop on this key. It equals read. A gateway that predates it omits it, and its /mcp serves neither tool.

hooks.digest and hooks.logRead default to on. hooks.readTrim, hooks.lifecycle, hooks.prompt, hooks.digestEconomics, hooks.rangedArrival and hooks.fleetReadBack default to off. Each takes on or off. Digest shortens command output; Read trim requires anyray_retrieve; lifecycle records session receipts; logRead shortens a Read of a failing test log the same session's test command wrote; digestEconomics holds back a Claude Code command digest, or a logRead digest, whose expected read-backs cost more than it saves, and needs digest or logRead on; rangedArrival lets the ranged-trim check priced on the hookArrival rates below decide which ranged trims go out; fleetReadBack passes a command's output through undigested when the fleet keeps reading that kind of digest back, priced per output size. prompt is retired: Connect ignores it and removes its old paste hook. Optional hooks.teams and hooks.users assign switches per identity. Each switch resolves user, then team, then org, then default. Seats receive only their eight resolved values, never the maps. An unreadable store serves all eight as off. When an older gateway omits hooks, Connect keeps its cached posture.

hooks.sessionGate carries the session gate's connect_hook posture: mode (on or off) and, while the lane is measured, draw (seed, 32 hex characters, and fraction, at most 0.5). Connect draws one arm per Claude Code session from them, keyed on the hook's session_id. Codex and Cursor hooks cannot name that session, so they are in neither arm and follow mode. A Connect that sends x-anyray-connect-capabilities: session-gate-v1 honors it; an older one ignores it. Absent while the gate's state is loading, and Connect then keeps the gate it had.

mcp.org.url is the organization MCP endpoint when the gateway has a valid public HTTPS origin. Optional ?model=<id> also returns modelContext.contextTokens, that model's real context window, omitted when the model is unknown. It also returns hookArrival, the model it was asked for and its rates: read, write5m, write1h and output, the prompt-cache read, five-minute and one-hour write, and output rates, each relative to one fresh input token and derived from the model's price entry. A model the gateway prices only by fallback gets no hookArrival. Connect prices the ranged-trim check with them. With ?clientTool=<id> (a client tool id such as claude-code), hookArrival.horizon.main is the measured turns left on the main thread: for each count of requests so far, the most further requests three in four of your tenant's sessions of that client made before their first compaction. It is served only where mintSessionSurvival is true or "shadow" and the curve is built; otherwise it is absent and Connect estimates the horizon itself. At most 1,000 points; past the last, the last holds.

integrationLevel reports {level, source} for this caller. source is user, agent, team, org, or default. An unreadable policy store reports gateway_hooks_mcp from default. Existing clients ignore the field.

capabilities lists gateway features Connect may turn on in a tool's own config. responses-websocket-v1 means the gateway relays Codex's Responses WebSocket. request-zstd-v1 means it accepts zstd-compressed request bodies. An older gateway omits the field, and Connect leaves those features off.

Two more are opt-in and off by default. claude-code-deny-first-party-tools-v1 has Connect add WebSearch and DesignSync to Claude Code's permissions.deny. claude-code-unpinned-tool-search-v1 has Connect leave env.ENABLE_TOOL_SEARCH unset. Each is served only when its claudeCodeTools switch is on for the caller and the default routing config reaches only Bedrock, with no custom host. Subscription-lane seats and agent service keys never get them.

clientAssets names the VS Code extension and OpenClaw plugin builds this gateway image serves: version, sha256, and bytes for each, plus distSha256 (extension) or sourceSha256 (plugin), and on a release image Anyray's Ed25519 signature over those fields. Connect installs a served build only when the signature verifies against a key built into Connect and the build is newer than its bundled copy and any served build it holds. It fetches the bytes from /connect/client-assets/{kind}/{sha256}, checks the sha256 first, and only over HTTPS (or plain HTTP on loopback). On any failure it keeps the last verified build, else the bundled copy. An older gateway omits the field, and an unsigned entry is never installed.

States GET /connect/self-check can report

retrieval.state is measured, unavailable, or unmeasured_personal_key. measured carries windowHours and a runtimes array, one entry per client runtime the key has used, each with a clientTool, a verdict, a request count, and counts by evidence class. An empty runtimes is its own answer: this key has sent no request the gateway could classify yet. unavailable means the evidence read failed, timed out, or was capped, so nothing is proven either way. unmeasured_personal_key means the key is a developer key, which stamps no agent binding on its rows, so there is no per-runtime evidence to read. A measured report also carries readBack: handles minted and reads that resolved or missed per tool (Retrieval read-back). It does not change problems.

A verdict is wired, unlatched, unwired, toolless_only, parity, or unknown, with the meanings the Agents card shows. A parity runtime also carries readPath, which never changes problems. problems counts the runtimes that are not wired, toolless_only, or parity (forwarded unchanged by the deployment's default), and scores 1 for each of the three shapes that prove nothing: an empty runtimes, unavailable, and unmeasured_personal_key. So problems: 0 means the store answered, at least one runtime was seen, and every runtime seen is wired or has nothing to wire.

key.state is reported or unavailable (the key record could not be read). reported carries the metadata the console's Health pill reads for this key, never a key id, owner or hash: expiresAt, renewMs, firstSeenAt, lastSeenAt, lastSeenTool, retrieveCapableAt, sourceHook, the four sourceTrim* fields, clientTools, and connectRefresh. Stamps a key has not earned yet are left out. An older gateway omits key.

Pass ?tools=claude-code,codex (comma-separated, up to 16 tool ids) to add toolTraffic for the calling key's user over the last 7 days. toolTraffic.state is reported or unavailable (the read failed). reported carries windowDays: 7 and tools, keyed by tool id, each with hookRows, inferenceRows and optimizeRows. optimizeRows counts the optimize-only calls a Copilot seat makes, which the other two never include. An older gateway omits optimizeRows. deviceRequests says whether the gateway can answer for the calling key's own device. reported means the key is bound to an enrolled device: each tool then carries deviceLastRequestAt, the ISO 8601 UTC time of the newest inference request of any outcome that this device sent, and a tool without it sent none in the window. Hook and optimize rows never set it, and neither does the same person's other machine. unsupported means the key is bound to no device, so no time is given. An older gateway omits deviceRequests. An older gateway omits toolTraffic, which Connect shows as Ready with "Your gateway can't report this yet" under Traffic.

runbookVersion and skillVersion are what this gateway release ships with. An agent records both at setup and re-reads the agent-host runbook or re-pulls GET /connect/policy when either goes up.

Directive kinds a client can run

The client declares what it can run in x-anyray-directive-kinds, a comma list. Absent means the three original kinds. A kind it did not advertise is skipped, not delivered: an unrecognized directive is never acked and would park at the head of the seat's queue, swallowing every later one. It stays queued and lands once the client updates. Supported kinds are reapply_config, remint, revert, revert_tool, and update.

Loopback fields on /connect/mcp-oauth/authorize

Body: { "name": "connector name", "loopbackPort": 49152, "loopbackNonce": "<43 base64url chars>" }. The loopback fields are optional but go together: a port from 1024 to 65535 that Connect is listening on at 127.0.0.1, and 32 random bytes Connect minted for this run. One without the other answers 400.

Returns { "authorizationUrl": "https://..." }, plus loopbackPort echoed back when accepted, so Connect can tell a gateway that predates the field. Starting the flow binds no user. After consent the callback shows the claim code as text/plain when no port was given, and otherwise answers 302 to http://127.0.0.1:<port>/callback?claim=<code>&nonce=<SHA-256 of loopbackNonce, base64url> with cache-control: no-store. Connect redeems the code only under its own nonce digest, so a code from another run pushed at that port is ignored.

Trimming lanes on /connect/optimize-output

Two lanes trim. Everything else returns the confirmed byte-identical no-op {"status": "ok", "optimized": false, "output": …, "handled": true} and records no savings.

RequestResult
reversible: true with retrievalContext: "available"A trim with retrieve ctx_… handles. The saving is recorded server-side. An anyray_search result is trimmed only when the key has called anyray_retrieve within the last 6 hours. The answer lists strategies (kind, tokensSaved), which Connect sends back when it reports a replay of this trim.
ranged: true (not reversible, not a managed source hook)A trim whose omitted runs read … [N line(s) omitted by anyray · lines A-B], only for a tenant with relevance_filter rangedHookTrim on. The response adds rangedHandled: true, plus rangedDisabled: true when the tenant has not turned it on, and readBack when the bet store can answer: for a request that names its command shape, that shape's ranged bets across every seat (emits, rereads, readRounds, sliceSum, slices) and its digest bets (emits, rereads), plus the pooled read-back rate. Every figure is narrowed to the caller's client tool. Connect's trim rules still decide from its own ledger. No saving is recorded: Connect reports it through /connect/hook-savings only if it emits the trim.

Connect sends retrievalContext from the current hook consumer, not from a long-lived client identity. A reversible: true request whose value is unavailable, unknown, invalid, or absent gets the no-op. The probe answer (no output) adds rangedLane: true on a gateway that serves the ranged lane. Connect sends ranged: true only to such a gateway, and skips the lane for an hour after rangedDisabled. A trim with neither a handle nor line ranges is gone: a lossy trim of arbitrary output was often read back in full right away, which cost more than the original.

The anyray_connect tool inside /mcp/org uses these same authorization and claim operations. OAuth state stays userless: the vendor callback stages encrypted tokens behind the short claim code, and only redemption by a verified client-key or AuthKit identity creates the grant.

/connect/health also accepts x-anyray-connect-login-observation, a JSON header bounded to 256 characters: { "version": 1, "state": "enabled", "observedAt": "2026-09-08T10:00:00Z" }. States are enabled, disabled, approval-required, unknown, and error. It needs an existing valid client key, and identity comes from that key. The response adds loginObservationRecorded, and /admin/connect-health exposes the latest per-user observation as loginRegistration: { state, observedAt }, independently of seat presence and compliance. Older observations and heartbeats cannot replace a newer observation. See desktop reporting cadence for freshness handling.

/connect/health accepts x-anyray-org-plugin-states, a JSON snapshot shaped as [{tool, plugins: [{id, version, state}]}] for Claude Code and Claude Desktop. Each report holds at most 32 plugins total (8 KB maximum); ids and versions are at most 64 characters each, and an unknown version is null. Records contain tool, id, version and an allowlisted state, never paths or file contents. Only a verified human client key updates the seat's shared Postgres snapshot. The admin install-state read keeps each machine's latest report from the last 72 hours, so a laptop that reports daily is not dropped for a weekend, and combines a seat's machines into one.

The daily selfcheck sends its existing client key and x-anyray-connect-health-kind: ambient. A verified ambient report records the same events in the seat's per-user bins. A doctor aggregate omits that discriminator because its separate verified doctor lane already records those bins. Missing or rejected keys never create or update a seat.

Authenticated /connect/verify health reports may also send x-anyray-connect-residue, a JSON array of the closed installer-residue enum. The header is omitted when Connect did not probe, [] is an explicit clean result, and any member except osquery_log_retained is actionable residue (Connect reports that one for information only). The latest explicit value is preserved across heartbeats that omit the header. The admin seat endpoints expose it as residue and derive state: "needs_pkg_install" only when an actionable member is present.

The same check-ins may send x-anyray-connect-time-zone, the machine's IANA time zone (such as Asia/Jerusalem). The gateway keeps it only when it is a zone the runtime knows, stores it on the seat, and keeps the last value when a check-in omits it. /admin/spend/activity returns it per person as timeZone; a person with no report reads not_reported.

/public/* (admin-token sign-in: /public/auth, /public/auth/session, /public/logs) and /log/stream back the gateway's built-in log page, not the console, and are not a public API. A wrong admin token at /public/auth counts toward the failed-auth limit. For automation use /admin/* with a scoped admin API key, which is capability-limited, or the admin token.

GET /v1/me also exposes key-scoped source-trim timestamps and a request count, without content or key material. Source evidence fields.