Relay docs

Authentication & keys

Every gateway request authenticates with a Relay API key using the ApiKey scheme. The only exceptions carry a credential of their own: GET /health, the realtime WebSocket (a single-use session token), and workflow webhook / resume URLs (the token in the URL).

Authorization: Bearer app_live_xxxxxxxxxxxxxxxxxxxxxxxx

#Issuing keys

Create keys in the control panel under API Keys. The full key is shown once at creation — copy it then. Keys belong to the workspace you have selected, which is how usage, budgets and cost attribution are grouped; it is stamped server-side and never typed in.

Manage keys via the control-plane API too: POST /api/ApiKeys/search, POST /api/ApiKeys, PUT /api/ApiKeys/{id}, POST /api/ApiKeys/{id}/revoke, DELETE /api/ApiKeys/{id}.

#What a key controls

Everything below is settable at creation and editable afterwards:

SettingEffectEmpty / unset means
NameIdentifies the key in the panel and in cost attribution.— (required)
ScopesCapabilities the key may exercise — see the table below. A request outside the list returns 403 insufficient_scope.Every scope allowed.
Model allow-listModels the key may call. A request for anything else returns 403 model_not_allowed; GET /v1/models lists only these.Any enabled model.
Rate limitRequests per fixed 60-second window, counted per gateway instance. Over the limit returns 429 rate_limit_exceeded with Retry-After; every response from a limited key carries X-RateLimit-Limit / X-RateLimit-Remaining.No per-key limit.
ExpiryThe key stops working after this date (end of day).Never expires.
EnvironmentA default environment stamped on this key's traces (production, staging, …) when the request sends none.Unset.
Trace body capturenone / metadata / full for this key's traces, overriding the workspace default. A caller can still lower it per request. See Observability.Workspace default.

An empty scope list or allow-list means unrestricted, not nothing — so adding a new scope to the gateway never retroactively locks out existing keys.

#Scopes

ScopeGrants
chat.completionsPOST /v1/chat/completions.
embeddingsPOST /v1/embeddings.
models.readGET /v1/models and GET /v1/models/metrics.
workflows.runEverything under /v1/workflows except webhook and resume URLs.
data.queryRun a dataset agent, which queries the data app as the named end user.
connectors.useRun a connector agent, which reads the named end user's Microsoft 365 account.
telemetry.readRead this workspace's traces, sessions, users, scores and metrics (/v1/telemetry/*, GET /v1/scores, POST /v1/metrics) — metadata only.
telemetry.bodiesAlso return captured prompts and completions when reading telemetry.
scores.writeAttach scores and feedback (POST /v1/scores, /batch, DELETE).
traces.writeSend app-side spans (POST /v1/traces/ingest, /v1/otlp/v1/traces).

Scopes gate the paths they name; the rest of the API (prompts, agents, files, knowledge, batches, realtime, tools, pricing, /v1/responses) needs only a valid key. data.query and connectors.use are checked inside the agent run, because gating /v1/agents as a whole would gate every agent, dataset or not. The two are kept apart from chat.completions deliberately: both act on a caller-asserted end user that Relay cannot verify, so a key holding them can read any end user's data by varying the header. Grant them only to applications that authenticate their own users.

#Editing a key

A key's details can be changed at any time; the secret cannot. Editing changes the name, scopes, allow-list, rate limit, expiry, environment and capture level, and leaves the key its holder is using working exactly as before.

There is deliberately no way to rotate a key in place. Repointing an existing key at a new secret would either break every caller silently or, worse, leave two secrets believed to be one. Rotation is revoke + issue a new key, which leaves an audit trail that an edit would not.

Nor is the key retrievable. Only a SHA-256 hash is stored, so after creation the panel can show the identifiable prefix (app_live_xxxxxxxx…) and nothing more — not because it is being withheld, but because the rest was never kept.

#Budgets

A workspace has one spend ceiling, optionally divided into per-member allocations. When workspace spend reaches a hard-stop cap, the router rejects further requests with 402 budget_exceeded before anything is sent upstream. A separate per-end-user limit (keyed by X-Relay-User-Id) returns 402 end_user_budget_exceeded. Spend is recomputed periodically from telemetry (Gateway:BudgetRefreshMinutes, default 5), so a stop engages within that window rather than instantly. See Budgets & spend limits.

#Control-panel authentication

The control panel is a separate app that authenticates admins through the company's Flowbyte identity app (OAuth sign-in, then a cookie) — not API keys. The api/* control-plane endpoints require a signed-in admin session; the public /v1/* API requires an API key. Keep the two mental models separate:

Gateway API (/v1/*)Control panel (api/*)
WhoApplicationsAdmins / team owners
AuthAPI key (Bearer)Identity-app sign-in (cookie)

One bridge exists between them, by design: the panel's Playground runs a call as one of your stored keys without ever holding its secret. It posts to the panel host, which forwards to the gateway with a two-minute signed delegation token that names the key id; the gateway then treats the call as that key's in every respect. Only the Playground's four routes are forwarded.

#Good practice

  • One key per app or environment, so you can revoke narrowly.
  • Set a model allow-list on keys that only need a specific model.
  • Put every app's key behind the team that should own its cost.
  • Keep data.query, connectors.use and telemetry.bodies off keys that do not need them.