REST API
Everything in Cortex is a REST API call. The TypeScript SDK is a convenience layer over this exact API — so if you work in a language without an official SDK, you can build your own client against it with no loss of capability.
Open the interactive REST referenceBase URL
All endpoints are served under the /api prefix.
Authentication
Send credentials on every request. Two mechanisms are relevant to integrators:
API key (server-to-server) — either header form works:
JWT bearer token (dashboard/user sessions):
API keys are accepted in headers only — never as a query parameter. A key reaches only
the project it is bound to (and only projects your account has configured for Cortex), and
only the routes its scopes allow; anything else is
answered with 403.
Create and scope API keys in the dashboard. See Authentication for the full picture.
Conventions
- Project scoping — most resources live under a project:
/api/projects/{projectId}/sessions,/api/projects/{projectId}/gatherings, and so on. See Multi-Tenancy & Projects. - JSON — request and response bodies are
application/json. - Create / update / delete carry identifiers in the path and payloads in the
body;
GETendpoints use query parameters for optional filtering and pagination. - Pagination — large collections accept cursor parameters on
GET. Sessions takestatus,limitandcursorand return{ sessions, nextCursor }; a transcript takesafter,limit,orderandincludeand returns{ messages, nextCursor, latestSeq }. Without any query parameters both return the plain list, as before. - Errors use standard HTTP status codes —
400for an invalid request body,401/403for auth (including a missing scope),404for missing resources,409for conflicts (e.g. a session that isn’t running),429for rate limits. The SDK maps these to typed errors.
Endpoint groups
The reference is organized by resource. The main groups:
| Group | What it covers |
|---|---|
| Sessions | Create, start, stop and read transcription sessions and their messages |
| Messages | Transcripts for a session (and debug audio) |
| Room Messages | Push custom messages into the ODIN room of a live session (Room Push) |
| Participants | Per-project speaker identities |
| Gatherings | Lobbies, appointments and open spaces; members and invitations |
| Sanctions | Moderation actions and their audit trail |
| Plugins / Plugin Instances | The plugin catalog and per-project activations |
| Functions | Serverless function settings, versions and deployment |
| Webhooks | Event subscriptions and the delivery log |
| Events | The real-time event stream (SSE) |
| API Keys / App Settings / Auth | Account, project configuration (including the transcription provider) and tokens |
Building your own client
The reference is generated directly from the Cortex OpenAPI specification, so it’s an exact, machine-readable contract. You can:
- Point a generator (OpenAPI Generator,
openapi-typescript, etc.) at the spec to scaffold a client in your language. - Use the “Try it” console on each endpoint page to experiment with live requests.
The spec is available at https://cortex.odin.4players.io/api/docs-json.
Conditional requests (ETag)
The transcript endpoint is built to be polled. Every message has a per-session sequence
number (seq); a paged response carries the session’s newest one as latestSeq and as its
ETag. Send it back as If-None-Match and Cortex answers 304 Not Modified while nothing new
has arrived:
The ETag tracks new messages only, so it isn’t sent when you ask for include=annotations
(annotations arrive asynchronously). See
Polling instead of streaming.
Rate limits
Requests made with an API key draw from a per-key token bucket. By default a key can burst up to 200 requests and sustain 20 requests per second. Every limited response tells you where you stand:
When the bucket is empty you get 429 Too Many Requests with a Retry-After header (in
seconds) — wait at least that long before retrying. A 304 from a conditional request counts
like any other request; it is cheap for Cortex, not free for the caller. Some endpoints have
an additional budget of their own (for example, pushing messages into a room is limited per
room). Dashboard sessions (JWT) are not rate limited this way.
Real-time
For live updates, the API exposes a Server-Sent Events stream so you don’t have to poll.
See Events & Real-time — and note the SDK’s
watch() builds on it for you. Long-lived streams opened with an
API key are supported; opening a stream counts against the key’s rate limit once.