Skip to main content

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 reference

Base URL​

https://cortex.odin.4players.io/api

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:

X-API-Key: ots_live_xxxxxxxxxxxxxxxxxxxx
Authorization: ApiKey ots_live_xxxxxxxxxxxxxxxxxxxx

JWT bearer token (dashboard/user sessions):

Authorization: Bearer eyJhbGciOi...

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; GET endpoints use query parameters for optional filtering and pagination.
  • Pagination — large collections accept cursor parameters on GET. Sessions take status, limit and cursor and return { sessions, nextCursor }; a transcript takes after, limit, order and include and returns { messages, nextCursor, latestSeq }. Without any query parameters both return the plain list, as before.
  • Errors use standard HTTP status codes — 400 for an invalid request body, 401/403 for auth (including a missing scope), 404 for missing resources, 409 for conflicts (e.g. a session that isn’t running), 429 for rate limits. The SDK maps these to typed errors.

Endpoint groups​

The reference is organized by resource. The main groups:

GroupWhat it covers
SessionsCreate, start, stop and read transcription sessions and their messages
MessagesTranscripts for a session (and debug audio)
Room MessagesPush custom messages into the ODIN room of a live session (Room Push)
ParticipantsPer-project speaker identities
GatheringsLobbies, appointments and open spaces; members and invitations
SanctionsModeration actions and their audit trail
Plugins / Plugin InstancesThe plugin catalog and per-project activations
FunctionsServerless function settings, versions and deployment
WebhooksEvent subscriptions and the delivery log
EventsThe real-time event stream (SSE)
API Keys / App Settings / AuthAccount, 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:

GET /api/projects/{projectId}/sessions/{sessionId}/messages?after=180&limit=200
X-API-Key: ots_live_...
If-None-Match: "180"

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:

RateLimit-Limit: 200
RateLimit-Remaining: 137
RateLimit-Reset: 4
RateLimit-Policy: 200;w=10

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.