API reference
The gateway's inference, organization MCP, admin, and inbound SCIM v2 surfaces.
Browse and try every endpoint, inference (/v1/*) and admin (/admin/*), in the interactive
API explorer your gateway serves itself at GET /docs, backed by the
OpenAPI 3.1 spec at GET /openapi.json (load that into Postman or codegen
too). Both routes are unauthenticated and expose only the API shape; "try it out" calls run against
the same gateway, so real requests still need your client key or admin token. A copy of the spec is
published at docs.anyray.ai/openapi.json if you don't have a deployment
in front of you. The capability-annotated index of every route is the
Endpoint reference.
open http://localhost:8787/docs # or https://<your-anyray-gateway>/docs
Base URL
The gateway is self-hosted: it runs on your own infrastructure (default port 8787), not on
docs.anyray.ai. Put it behind TLS for any networked deployment.
https://<your-anyray-gateway> # e.g. http://localhost:8787 in-network
Deploying and configuring the gateway (provider keys, content mode, the optimizer URL): Configure.
Authentication
Send your Anyray client key as a bearer token, in x-anyray-api-key, or, for Anthropic-native
clients on /v1/messages, in x-api-key. The real provider key stays server-side.
curl https://<your-anyray-gateway>/v1/chat/completions \
-H "Authorization: Bearer $ANYRAY_API_KEY" \
-H "Content-Type: application/json" \
-H 'x-anyray-provider: openai' \
-d '{
"model": "gpt-4o",
"messages": [{ "role": "user", "content": "Summarize the release notes." }]
}'
Six credentials:
| Credential | Used on | Grants |
|---|---|---|
Client key (Authorization: Bearer, x-anyray-api-key, or ark_ in x-api-key) | /v1/* inference | The holder's user/team attribution. Minted by an admin enrollment flow or self-service SSO with anyray-connect --sso. |
Service key (ark_svc_…, sent through the same headers) | /v1/* inference | A revocable non-human service identity for an AI agent, CI job, or SDK script, with an optional per-key monthly dollar budget. No interactive SSO. |
AuthKit access token (Authorization: Bearer) | /mcp/org | A WorkOS user in the customer organization. The audience must equal this gateway's exact /mcp/org URL. The gateway verifies it and Billing checks live membership. |
Admin token (ANYRAY_ADMIN_TOKEN) | The console + /admin/* config API | Break-glass owner access, kept server-side. Each /admin/* route requires a capability; enterprises can add console SSO + RBAC. |
Admin API key (aak_…, Authorization: Bearer) | /admin/* config API | A revocable automation credential carrying either an explicit capability subset or native Management API v1 scopes. Never inference; never trace content. See Scoped admin API keys. |
SCIM bearer (Authorization: Bearer) | /scim/v2/* | Lets the configured IdP provision users and groups for one tenant. Only its SHA-256 hash is stored. |
Every /v1/* request needs a valid minted key. The key's user and team control attribution;
x-anyray-metadata cannot override them. Once the organization configures SSO, a developer mints
their own key with the enrollment command from Users → SSO enrollment; an org admin can also
mint and rotate keys. OAuth/OIDC never runs on /v1/* itself.
Missing, unknown, expired, revoked, and SCIM-deactivated identities return HTTP 401 with
missing_key, key_unknown, key_expired, key_revoked, or user_deactivated and a safe fix
hint. An unreachable key store is not a 401: verification fails closed with HTTP 503 and
key_store_unavailable, so a database outage is never mistaken for a bad credential.
Organization MCP access with SSO
An MCP client that cannot attach a static header adds one URL:
https://<your-anyray-gateway>/mcp/org
The unauthenticated request returns 401 and points the client to the protected-resource metadata:
WWW-Authenticate: Bearer resource_metadata="https://<your-anyray-gateway>/.well-known/oauth-protected-resource"
That public metadata names WorkOS AuthKit as the authorization server. AuthKit runs the authorization flow through the enterprise SSO connection already attached to the customer's WorkOS organization. The gateway does not implement authorization codes, PKCE, client registration, consent, token refresh, or revocation.
The access token must meet all of these checks:
| Claim or property | Required value |
|---|---|
| signing algorithm | RS256 |
iss | the managed AuthKit issuer |
aud | one scalar string equal to https://<your-anyray-gateway>/mcp/org |
| time and subject | valid sub, iat, and exp |
| organization | non-empty WorkOS org_id |
No custom mcp:org scope is advertised or required. The exact resource audience separates one
gateway from another.
After JWT verification, the gateway sends only sub and org_id to Billing under its deployment
bearer. Billing requires the tenant's activated SSO marker and a currently active WorkOS connection.
It fetches the current WorkOS user, requires exactly one active membership in the configured tenant
organization, resolves the current groups to an Anyray team, and applies allowed domains and
revocation. The AuthKit token never reaches Billing or storage. Keep non-SSO methods disabled in the
WorkOS domain policy and require SSO in the organization policy. Otherwise a manually enabled Google
or Magic Auth method can bypass the enterprise IdP.
A client key still works on /mcp/org and takes precedence when presented. This keeps
Connect-managed tools compatible. Pre-verification and live identity throttles return 429 with
Retry-After; concurrent config, JWKS, and metadata refreshes share one outbound request. Billing,
WorkOS, or AuthKit key-discovery outages fail closed with 503. See
Organization MCP hub for setup and the
endpoint reference for every route.
Billing also returns 503 until a vendor operator confirms the current WorkOS organization policy
and the exact Resource Indicator derived from this deployment's public gateway URL. A WorkOS
organization rebind or gateway URL change invalidates the matching confirmation.
This lane never provisions WorkOS for an ordinary tenant. The WorkOS organization is created only
after an authenticated owner explicitly chooses Set up SSO, or an operator deliberately binds
an existing organization. Non-SSO tenants continue to use client-key MCP authentication.
Service keys for agents and CI
Three admin routes manage service keys:
| Method | Path | Capability | Shape |
|---|---|---|---|
| post | /admin/service-keys | clientkeys:manage | { "name": string, "team": string, "monthlyBudgetUsd"?: number } → 201 { "key": "ark_svc_…", "record": {...} } |
| get | /admin/service-keys | config:read | { "keys": [...] }; filtered to service keys, with source and type set to service |
| delete | /admin/service-keys/:id | clientkeys:manage | Revokes the key and returns { "ok": true } |
curl https://<your-anyray-gateway>/admin/service-keys \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "synthetic-ci-release",
"team": "platform",
"monthlyBudgetUsd": 25
}'
Store the returned key immediately; the gateway stores only its SHA-256 hash and never returns the raw token again. A budget counts successful request cost per UTC calendar month. At or above the budget, covered inference routes return:
{
"error": {
"code": "service_key_budget_exceeded",
"message": "This service key has reached its monthly budget. Ask an administrator to raise the budget or wait for the next UTC month."
}
}
The cap is soft and fails open: completed requests update the shared counter after their responses,
so concurrent requests can overshoot, and a counter outage lets requests proceed. A key without
monthlyBudgetUsd, or with a value of 0, is unlimited.
Scoped admin API keys
An admin API key (aak_…) scripts the /admin/* surface without the break-glass
ANYRAY_ADMIN_TOKEN. Mint with exactly one grant shape: an explicit subset of the
admin capabilities, or native Management API v1
scopes. The grant is fixed at mint time and can never exceed the minter's own access; content,
content-mode, SSO config, self-update, and key management remain ungrantable. Keys expire after
90 days by default; pass expiresInDays for a different horizon or neverExpires: true to opt out
explicitly. Minting requires the adminkeys:manage capability (security_admin and up).
A scope-minted key is deliberately v1-only. It has no capabilities, so every legacy /admin/*
capability gate rejects it; use it with /admin/v1/* (and the scope-less GET /admin/v1/me). A v1
403 names the exact scope to mint in required_scope.
| Method | Path | Capability | Shape |
|---|---|---|---|
| post | /admin/api-keys | adminkeys:manage | Exactly one of caps: string[] or scopes: string[], plus optional label and expiry → 201 { "key": "aak_…", "record": {...} } |
| get | /admin/api-keys | config:read | { "keys": [...] }; metadata only, hashes stripped |
| delete | /admin/api-keys/:id | adminkeys:manage | Revokes the key and returns { "ok": true } |
Store the returned key immediately; only its SHA-256 hash is kept. Revocation takes effect on the
next request, on every replica. Admin writes made with a key are attributed in the audit log as
apikey:<id>.
For a CI dashboard, mint the one scope its read needs, then call the v1 dashboard:
curl -X POST https://<your-anyray-gateway>/admin/v1/admin-keys \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "ci-dashboard",
"scopes": ["spend:read"]
}'
curl https://<your-anyray-gateway>/admin/v1/spend/dashboard \
-H "Authorization: Bearer $ADMIN_API_KEY"
On the legacy surface a budget bot needs a capability grant instead: a key that can read config and write per-user caps.
curl https://<your-anyray-gateway>/admin/api-keys \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "budget-bot",
"caps": ["config:read", "usercaps:write"]
}'
curl -X PATCH https://<your-anyray-gateway>/admin/user-caps/alice \
-H "Authorization: Bearer $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "maxUsd": 250 }'
PATCH /admin/user-caps/:user (capability usercaps:write) merges a partial edit into one user's
entry: a field you omit is kept, null clears it, a value replaces it. Accepted fields are
monthlyTokens, maxUsd, softUsd, and reset (never | daily | weekly | monthly), plus
an optional expectedRevision for a conditional write (a stale revision returns 409). Unknown
fields are rejected with 400. The whole-map PUT /admin/user-caps remains for bulk edits.
The console host's proxy replaces the Authorization header with the browser login cookie, so an
aak_ key only works against the gateway itself (port 8787 by default), not through the console
hostname.
If the key store is unreachable, aak_ verification fails closed with HTTP 503 and
admin_key_store_unavailable (never a 401), and the break-glass admin token keeps working.
Management API v1
/admin/v1/* is the /admin/* surface re-cut for scripts and API keys: plural-noun resources, one
permission rule you can read off the URL, one error envelope, and a request id on every response.
The surface is stable (x-anyray-api-status: stable): response shapes are the documented
contract, and breaking changes would arrive only as /admin/v2. Legacy /admin/* data routes still
answer, now with Deprecation: true and a Link: <successor>; rel="successor-version" header
naming their v1 path; plan their retirement into your scripts. Requests are capped at 300 per
minute per principal (429 + Retry-After past it).
Beyond the re-served routes, v1 adds a key lifecycle (GET/PATCH /admin/v1/keys/{id},
POST /admin/v1/keys/{id}/rotate returning the fresh token exactly once), per-provider key writes
(PUT/DELETE /admin/v1/providers/{slug}/key), and the audit trail
(GET /admin/v1/audit/events?limit=&cursor=&category=, a { "data": [...], "next_cursor": ... }
page over the admin/GDPR trail, audit:read). Self-service for developers lives on the inference
plane instead: GET /v1/me, /v1/me/usage, and /v1/me/limits answer with the verified key
holder's identity, month-to-date usage, and the ceilings that would 402 them; no admin credential
involved.
The scope rule
A scope is <resource>:<action>, where the resource is the first path segment after /admin/v1/:
| Request | Scope it needs |
|---|---|
GET /admin/v1/<resource>/… | <resource>:read |
POST, PUT, PATCH, DELETE /admin/v1/<resource>/… | <resource>:write |
POST /admin/v1/<resource>/…/query (a read whose filter needs a body) | <resource>:read |
Resources: keys, admin-keys, users, spend, providers, routing, optimizer, policies,
settings, sso, enrollment, provisioning, fleet, traces, audit, playground.
GET /admin/v1/me needs no scope and returns the scopes the caller holds. The named exceptions,
each declared on its operation in the OpenAPI spec as
x-required-scope:
| Scope | Gates | Grantable to a key |
|---|---|---|
users:revoke | Revoking or reinstating a developer | no |
users:gdpr | A user's export and erase | yes |
providers:write on GET /admin/v1/providers | A credential-grade read: provider keys come back in full, so possession is the privilege, not the verb | yes |
fleet:enroll | Enrolling machines: the enroll secret (read and rotate), evidence-connector bearers, installer download, the MDM profile, enabling a policy | yes |
fleet:remediate | Running a remediation script, retiring a host, pushing policy | yes |
playground:run | The admin playground | yes |
fleet:direct | Queuing a directive for a developer seat (revert, re-mint, update) | no |
settings:content | Content mode and trace retention: PUT /admin/v1/settings/content, and any PUT /admin/v1/settings body naming a field outside the runtime-settings allowlist | no |
settings:update | The self-update policy | no |
sso:write | IdP and SCIM configuration | no |
admin-keys:write | Minting and revoking aak_ keys | no |
traces:content, fleet:content | Prompt/response bodies; remediation script output | no |
Existing grants keep their reach: a role or an aak_ key still carries capabilities, and each
capability translates to the scopes it gated on the legacy paths (config:read is every
<resource>:read except the audit, trace, and fleet lanes; usercaps:write is users:write;
enrollment:manage is enrollment:write plus fleet:enroll; provisioning:manage is
provisioning:write, fleet:write, and fleet:remediate; and so on). The legacy trust boundaries
between enrolling, provisioning, and directing never collapse into one another.
The deliberate differences, each pinned by a test:
- The audit lane (
/admin/v1/audit/*) needsaudit:read, whichconfig:readno longer implies. - The fleet listings (including
GET …/mdm-servers,GET …/installers, andGET …/evidence-connectors) arefleet:read. - Single-route capabilities of one tier fold into one write scope:
routing:writecovers routing config and aliases,policies:writecovers team skills and the Claude Desktop policy,optimizer:writecovers settings and purge. - The two non-content runtime settings (
seatOverflow,seatOverflowHourlyMaxRequests) aresettings:write; every other field ofPUT /admin/v1/settings(content mode, trace retention, the heartbeat tier,storeCapacityBytes, and anything added later) stays behindsettings:content.
Tenant and deployment resources
Each operation carries x-scope: tenant or x-scope: deployment. In fleet mode
(ANYRAY_MULTI_TENANT=true) a tenant-bound principal (an SSO operator, or a key minted for one
tenant) reaches tenant resources only (keys, spend, optimizer, traces, the GDPR routes,
health); a deployment resource answers 403 with deployment_scope_required. Off fleet mode the
level is informational.
Errors
Every error, whatever the status, uses one envelope:
{
"error": {
"code": "insufficient_scope",
"message": "This operation requires the 'audit:read' scope, which this API key was not granted.",
"required_scope": "audit:read",
"request_id": "8c2f0b1e-synthetic-request-id"
}
}
code is stable: unauthenticated, insufficient_scope, deployment_scope_required,
invalid_request, not_found, conflict, rate_limited, unavailable, plus the legacy reason
codes such as admin_key_store_unavailable. request_id repeats the x-request-id response header;
send your own identifier in that request header and it is echoed back. 401 means the credential is
missing or bad; 403 means it is fine but lacks the scope, and names it.
curl -X PATCH https://<your-anyray-gateway>/admin/v1/users/alice/limits \
-H "Authorization: Bearer $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "maxUsd": 250 }'
The resource-by-resource map, with the legacy twin of every v1 path, is on the
endpoint reference. Every v1 operation in the spec also
names its twin in x-legacy-operation.
Request headers
Beyond Authorization / Content-Type, the gateway reads a few x-anyray-* headers:
Provider to route to: openai, anthropic, vertex-ai, bedrock, azure-openai, … Optional
when a default provider or routing config is set server-side.
Attribution, e.g. {"user":"alice","team":"platform"}. Drives spend attribution and per-user caps.
Never carries prompt/response content.
Per-request routing: fallback list, load-balance weights, retry policy. No credentials; they resolve from the server-held key store.
Response headers
Every response carries x-anyray-provider (the provider the request was routed to) and
x-anyray-trace-id. When the provider returns a server error (any 5xx, such as an Anthropic 529 overloaded_error), the gateway also stamps the markers below. The status and the error type are
forwarded unchanged; only the human-readable error.message gains a sentence naming the provider
that produced it:
{
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded — this error was returned by Anthropic (api.anthropic.com) and relayed unchanged by the Anyray gateway; it is not an Anyray gateway failure. Check https://status.claude.ai/ for provider status."
}
}
Present and set to upstream when the error originated at the provider, not the gateway. Absent on
success and on gateway-generated errors.
The upstream provider that returned the error: anthropic, openai, vertex-ai, … (omitted when the
provider is unknown).
The provider's own HTTP status (e.g. 529, 503).
No x-anyray-error-source: upstream header on a 5xx means the gateway itself failed, so check your
deployment. With it, the fault is on the provider's side (capacity/overload); retrying shortly usually
clears it.
Endpoints
/v1/* serves an OpenAI-compatible inference API plus the Anthropic-native Messages API;
/admin/* is the admin-gated configuration and reporting API; /sso/cli/* is the pre-key
self-service login handshake used by Connect; /scim/v2/* is an inbound SCIM 2.0 server. The
Endpoint reference is the complete, capability-annotated index of every route;
full request/response schemas live in the OpenAPI spec. The optimizer's
own before/after-request hooks are the separate optimizer protocol.
Retrieval via MCP: POST /mcp
The gateway is a remote MCP server (streamable HTTP). It serves the retrieval pair,
anyray_retrieve and anyray_recall, behind the same client key as every other route, so an
MCP-capable agent runtime closes the retrieval loop with a single server entry and no code:
{
"type": "http",
"url": "https://<your-anyray-gateway>/mcp",
"headers": { "x-anyray-api-key": "<client key>" }
}
The path is /mcp, not /v1/mcp. /v1/* is the inference proxy, and it catches every path it
does not recognize, so /v1/mcp is sent upstream as an inference request and comes back 500
rather than 404.
Registering the server puts the two tools in every request the client sends. The gateway forwards
them to the model only from the first request of a session that carries a retrieve ctx_ marker,
and on every request after it. Until then the model sees the same tool list it would without the
server: the two definitions on their own changed how an agent worked a lint task, before anything
had been shortened, and a session that never shortens an output has no use for them. The lane still
counts as retrieve-capable from the first request, so the reversible strategies run as before.
Hosted connectors (Anthropic mcp_servers, OpenAI Responses type:"mcp") may point here and are
credited as retrieve evidence when they target this gateway's own /mcp. First-party provider
APIs only (not Bedrock/Vertex), and the endpoint must be reachable from the provider's cloud.
One JSON-RPC 2.0 message per POST (initialize, ping, tools/list, tools/call); notifications
answer 202 with no body; batch arrays are rejected; GET /mcp is 405 (no server-initiated
stream). There is no session id; every call authenticates independently. Every valid message renews
the presenting key's 6-hour retrieval lease, the same evidence the stdio server's heartbeat
provides, so retrieval-dependent optimization unlocks after initialization. Tool-level
failures come back in-band (isError: true with "unknown or expired handle" or
"retrieval unavailable"), never as protocol errors, so the model can read them and adapt.
Retrieval: POST /connect/retrieve
An agent you build yourself has to be given a read path, or optimization runs one-way. Declare a
tool named anyray_retrieve and implement it against this route with the same client key you
already send. These are three independent requirements:
- The declaration proves the model can select the tool on this turn. It matches on the tool
name (
anyray_retrieve, or a namespacedmcp__…__anyray_retrieve), so renaming the tool silently turns retrieval off. - The implementation is what resolves a handle once the model calls it.
- The liveness signal proves that implementation is running. Send an authenticated empty
POST /connect/mcp-heartbeatat worker start and every 5 minutes while it can execute tools.
Without both a callable path and a fresh live lease, the gateway keeps clientCanRetrieve false.
Retrieval-dependent strategies stand down, and degradable strategies keep their handle-free mode.
curl -X POST https://<your-anyray-gateway>/connect/retrieve \
-H "x-anyray-api-key: $ANYRAY_CLIENT_KEY" \
-H "Content-Type: application/json" \
-d '{"handle":"ctx_abc123"}'
The ctx_… handle from an [anyray: … retrieve ctx_…] marker in the message the model received.
Read back a slice instead of the whole original.
Return matching lines with context. Max 256 characters.
Answers {"status":"ok","content":"…","handle":"ctx_…"}. An unknown or expired handle is 404, an
unavailable retrieval store is 503, and a missing or invalid key is 401. This is the route the
/mcp endpoint's anyray_retrieve tool calls into; implement against it directly only when the
runtime cannot register an MCP server. POST /connect/recall takes a natural-language query
instead of a handle and returns ranked handle + score + preview matches, for when the marker
has scrolled out of the model's view.
A human or CLI caller that is only probing sends x-anyray-retrieve-probe: 1 so the call does not
latch capability; only the model's own calls prove it can pull spans back. Worked examples per SDK:
Retrieval reference.
Claude Desktop managed bootstrap
An organization that routes Claude Desktop through Anyray manages its third-party profile through the deployment-owner API:
curl -X PUT https://<your-anyray-gateway>/admin/claude-desktop-policy \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"settings": {
"inferenceModels": [{"name": "claude-sonnet-4-6"}],
"managedMcpServers": {},
"allowedPluginMarketplaces": [],
"chatTabEnabled": true,
"coworkEgressAllowedHosts": ["*"]
}
}'
GET /connect/claude-desktop/bootstrap?os=macos|windows|linux returns the effective secret-free
policy, with the gateway provider/base URL, helper credential mode, and that OS's
inferenceCredentialHelper path injected by the server (os defaults to macos). Current Claude
Desktop sends Authorization: Bearer <client key> on this fetch through the MDM-pinned
bootstrapHeadersHelper; the route verifies a presented bearer (401 when rejected) and still
serves an unauthenticated fetch in the no-OIDC lane. It returns 404 while disabled. The write
boundary rejects credential-shaped string values at every depth, including values placed under
otherwise allowed free-text keys. The authenticated admin routes remain available in headless
gateway deployments. GET /admin/claude-desktop-policy/mobileconfig downloads the MDM trust profile,
which pins that endpoint and /usr/local/bin/anyray-bootstrap-headers-helper without embedding a
key; the response names /usr/local/bin/anyray-credential-helper.
POST /admin/claude-desktop-policy/connectors/probe takes {"url": "https://..."} and asks that
MCP server for its own name and tool list, so a connector row can be filled in from its address:
{"status": "ok", "name", "version", "tools": [...], "truncated"} when it answers
openly, {"status": "needs-auth", ...} when it wants a sign-in first, and
{"status": "unreachable", "reason"} otherwise. HTTPS only, and nothing is stored.
POST /admin/claude-desktop-policy/connectors/sync waits for one connector-mirror round and returns
{mcpHub} with content-free status, counts, and recent timing outcomes. The tool count covers shared
authless tools only. A total round failure returns lastSyncCounts: null. Its v1 twin is
POST /admin/v1/policies/claude-desktop/connectors/sync (policies:write).
Import and fleet deployment: Desktop apps.
POST /admin/claude-desktop-policy/connectors/authorize takes {"name": "Atlassian"} and
returns a vendor authorizationUrl. After consent, the callback page shows a short claim code.
Redeem it with POST /connect/mcp-oauth/complete and a client key. The grant belongs to the
verified account that redeems the code, never the account that started the flow. Developers start
their own flow with POST /connect/mcp-oauth/authorize. Neither response contains a token.
POST /admin/claude-desktop-policy/connectors/revoke takes {"name": "Atlassian"} and requires
desktopconfig:write (policies:write on /admin/v1/policies/claude-desktop/connectors/revoke). It
returns {"state":"needs-auth","outcome":"hub-grant-removed"} only after the hub has removed
its stored grant. It does not claim the upstream provider session was revoked.
Chat completions, embeddings, and the Anthropic Messages API work across all supported providers.
Image, audio, Responses, file, and Batch endpoints are OpenAI / Azure-only passthrough (audio
also works on groq).
SCIM v2 provisioning
Point the IdP at this base URL and send the separately configured SCIM bearer on every request:
https://<your-anyray-gateway>/scim/v2
| Resource | Routes |
|---|---|
| Discovery | GET /ServiceProviderConfig, GET /ResourceTypes, GET /Schemas |
| Users | POST /Users, GET /Users, GET /Users/:id, PUT /Users/:id, PATCH /Users/:id, DELETE /Users/:id |
| Groups | POST /Groups, GET /Groups, GET /Groups/:id, PUT /Groups/:id, PATCH /Groups/:id, DELETE /Groups/:id |
User lists support filter=userName eq "dev@example.com", startIndex, and count. Group lists
support the same paging fields. User PATCH accepts active:false for immediate offboarding;
Group PATCH accepts add/remove/replace member operations. A missing group member can be created
when the member carries a verified email in display or value.
Configure the bearer, admin group, and team mappings through the deployment-owner admin lane:
curl -X PUT https://<your-anyray-gateway>/admin/scim/settings \
-H "Authorization: Bearer $ANYRAY_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bearerToken": "synthetic-scim-bearer-token-replace-me-0001",
"adminGroup": "Anyray Admins",
"groupTeamMap": { "Engineering": "platform" }
}'
GET /admin/scim/settings returns configured, adminGroup, groupTeamMap, and updatedAt. It
never returns the bearer or bearer hash. A later PUT can omit bearerToken to retain the current
credential.
SCIM errors always use application/scim+json and this envelope:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "invalid bearer token",
"status": "401"
}
Errors
Most inference errors use the OpenAI envelope ({ "error": { "message", "type", "code" } }).
Client-key authentication uses the fixed machine envelope shown below.
| Status | Meaning |
|---|---|
400 | Malformed request: failed validation, bad JSON, or an unknown provider/model. |
401 | Missing, malformed, unknown, expired, revoked, or SCIM-deactivated client identity. The body includes status: failure, a reason code, the fixed valid client key required message, and a safe hint. |
402 | A governance ceiling has been reached: deployment entitlement/spend cap, a service key's monthly dollar budget, or a per-user monthly token cap / USD budget. The body carries error.code, one of service_key_budget_exceeded, user_token_cap_exceeded, or user_budget_exceeded. Terminal, not retryable: retrying cannot clear it, only an admin raising the limit or the window rolling. |
429 | Rate limit exceeded. Honors Retry-After. |
503 | A dependency the gateway needs is temporarily unreachable. Client-key verification returns status: failure with code: key_store_unavailable when the gateway cannot reach the store holding minted keys. This is an infrastructure fault, not a credential problem, so re-running anyray-connect will not help. Verification fails closed, so the request is never forwarded upstream. Retry shortly; if it persists, an operator should check GET /admin/health for the deployment's database legs. |