Authentication
Every call to the Cortex API is authenticated. As an integrator you’ll use one of two mechanisms:
- API key — for backends, game servers, CI and any machine-to-machine calls. This is the one you’ll use most.
- JWT bearer token — for browser/dashboard sessions where a human has logged in.
Pick the right one and send it with every request.
API keys
An API key belongs to your account, can be bound to a single project, and carries
scopes that decide which resources it can touch. Create and manage keys in the dashboard under your project’s
Cortex → API Keys. A key looks like ots_live_... and is shown only once on creation —
store it securely.
Send it as a header — either form works:
With the SDK:
An API key is a secret. Use it from your backend only — never embed it in a browser app,
mobile app or shipped game client. Keys are accepted only in request headers: a key in a
URL (?apiKey=...) is ignored and the request is rejected as unauthenticated, because URLs end
up in logs, proxies and browser histories. If a key leaks, revoke it in the dashboard and
create a new one.
Project binding
A key can only act on projects that your account owns and has configured for Cortex
(activating Cortex on a project does that). On every /api/projects/{projectId}/... route:
- a key bound to a project can only use that project (an unbound key can use any project of your account), and
- the project must be configured for Cortex by the same account as the key.
Anything else is answered with 403 — the same response for a project that doesn’t exist
and one that belongs to someone else, so a key can’t be used to probe for project ids. Use one
key per environment (Dev, Staging, Production) so a credential for one can never reach
another. For production integrations, always bind the key to its project.
Scopes
Every key carries a list of scopes. A request to a route outside the key’s scopes is
rejected with 403. Pick only what the integration needs:
| Scope | Grants |
|---|---|
sessions | Create, start, stop, list and read sessions; run session actions such as regenerating a summary; generate ODIN Voice tokens for the project |
messages | Read session transcripts (including debug audio) |
participants | Create, read, update and erase participants |
gatherings | Gatherings, their members and invitations, join codes |
sanctions | Read and issue, update or revoke sanctions |
sanctions.read | Read sanctions only — for a backend that enforces sanctions but must not issue them |
rooms.push ⚠️ | Push custom messages into the ODIN room of a live session (Room Push) |
plugins | The plugin catalog, plugin instances and annotations |
plugin-endpoints | Custom inbound plugin endpoints |
webhooks | Webhook subscriptions and the delivery log |
functions | Serverless functions, their settings and runtime; invoking functions that require an API key |
settings ⚠️ | Project settings (transcription provider, announcements, debug audio, encryption, room push) |
⚠️ marks sensitive scopes: they act on your players or on how the project is configured, so the dashboard never includes them in a preset and asks for them explicitly.
The scope names follow fixed rules, so you can predict what a scope grants:
<family>(e.g.sessions) — read and write of one resource family.<family>.read(e.g.sanctions.read) — only the read routes of that family.<family>.<capability>(e.g.rooms.push) — one narrow capability that the family scope does not include.
There are no wildcards, and scope names are never renamed — when a family is split, the
new scope is added and granted to existing keys, so a key never silently loses (or gains)
access. The dashboard offers presets for common setups. The full, current catalog — with
labels and descriptions — is also available to signed-in users at GET /api/api-keys/scopes
(client.apiKeys.getScopes() in the SDK). That catalog is authoritative and may list additional scopes for features in early access.
On the Server-Sent Events stream, a key only
receives events for the resource families its scopes cover — for example, sessions delivers
session.* and bot.* events, but transcript events (message.*) need messages. A key needs
at least one such scope to open a stream at all.
For example, a backend that starts sessions and reads transcripts needs sessions and
messages; a game server that checks whether a player is muted or banned needs only
sanctions.read.
Rate limits
Each API key has its own request budget. Responses carry RateLimit-* headers, and an exhausted
budget returns 429 with a Retry-After header. See Rate limits.
JWT bearer tokens
JWTs are for dashboard / user sessions — when a human signs in, the dashboard exchanges their 4Players login for a short-lived Cortex JWT and sends it as a bearer token:
A signed-in user has full access to the projects they can see; scopes only apply to API keys. Use JWTs when you’re building UI on behalf of a logged-in user; use API keys for everything server-side.
Inside serverless functions
Code running in a Cortex serverless function doesn’t
need a key at all: ctx.cortex is a client that the platform has already authenticated for the
function’s project. The underlying credential is never exposed to your code.
Choosing the right mechanism
Related
- Getting Started — create your first API key
- REST API — the authentication section of the reference
- Serverless functions —
ctx.cortex