Skip to main content

04 — REST API Definitions

Conventions:

  • Base URL: https://api.<domain>/v1 (versioned path; breaking changes → /v2).
  • Auth: Authorization: Bearer <access_jwt> for users; Authorization: ApiKey <key_id>.<secret> for tenant servers.
  • All bodies JSON. Errors use one envelope:
{ "error": { "code": "room_full", "message": "Room has reached max participants", "request_id": "req_01H..." } }
  • Standard failure codes: 401 unauthenticated, 403 forbidden, 404 not_found, 409 conflict, 422 validation_failed, 429 rate_limited (with Retry-After), 503 unavailable.
  • Idempotency: mutating POSTs accept Idempotency-Key header (stored 24 h).
  • Pagination: ?cursor=...&limit=50{ "items": [...], "next_cursor": "..." }.

Authentication Service

POST /auth/register

Direct-user mode (tenant may disable when it federates its own users).

// request
{ "email": "a@b.com", "password": "...", "display_name": "Alice" }
// 201
{ "user": { "id": "u_...", "email": "a@b.com", "display_name": "Alice" } }

POST /auth/login

// request
{ "email": "a@b.com", "password": "..." }
// 200
{
"access_token": "eyJ...", // JWT, 15 min
"refresh_token": "rt_...", // opaque, 30 d, single-use
"expires_in": 900,
"token_type": "Bearer"
}

POST /auth/refresh

{ "refresh_token": "rt_..." } → same shape as login. Rotates the refresh token; reuse of a consumed token revokes the whole family (see 10 — Security).

POST /auth/logout

Revokes the refresh-token family. 204.

GET /auth/me

Returns the authenticated user profile + platform role.

GET /.well-known/jwks.json

Public signing keys (unauthenticated; served at the gateway edge, cached).

Tenant server-to-server

  • POST /auth/token — API-key-authenticated; mints a user access token for a tenant's own (external) user: { "external_id": "cust-123", "display_name": "Alice" } → access token. This is the primary BaaS integration path.
  • POST /tenants/{id}/api-keys, GET .../api-keys, DELETE .../api-keys/{key_id} — key management (tenant_admin).

Room Service

POST /rooms

// request
{
"name": "Friday Standup",
"slug": "friday-standup", // optional, generated if absent
"visibility": "private",
"max_participants": 50,
"settings": { "listeners_can_request_speak": true },
"media_region": "eu-west" // optional pin; defaults to caller's region at first join
}
// 201
{ "room": { "id": "r_...", "slug": "friday-standup", "status": "created", ... } }

GET /rooms/{id} · GET /rooms?status=active&cursor=... · PATCH /rooms/{id} · DELETE /rooms/{id}

CRUD; DELETE closes the room (disconnecting participants via SFU + event) then soft-deletes.

POST /rooms/{id}/join

The critical call — validates ACL/capacity/ban, registers the session, returns everything the client needs to connect:

// 200
{
"session_id": "s_...",
"role": "speaker",
"signal_url": "wss://signal.eu-west.<domain>/v1/ws?ticket=st_...", // one-time WS auth ticket, 30 s TTL
"media_token": "eyJ...", // SFU access token, room+identity grants, 10 min TTL
"ice_servers": [
{ "urls": ["stun:stun.eu-west.<domain>:3478"] },
{ "urls": ["turn:turn1.eu-west.<domain>:3478?transport=udp",
"turns:turn1.eu-west.<domain>:5349?transport=tcp"],
"username": "1789300800:u_abc",
"credential": "b64hmac..." }
],
"room_state": { "participants": [...], "active_speakers": [] }
}

Failure codes: 403 banned, 409 room_full, 410 room_closed.

POST /rooms/{id}/leave

{ "session_id": "s_..." }204. (Also triggered implicitly by signaling disconnect timeout.)

Participants & moderation (moderator or tenant server only)

  • GET /rooms/{id}/participants — live list (served from Redis).
  • POST /rooms/{id}/participants/{user_id}/role{ "role": "speaker" } promote/demote.
  • POST /rooms/{id}/participants/{user_id}/mute · /unmute — server-enforced at SFU.
  • POST /rooms/{id}/participants/{user_id}/kick{ "reason": "..." }, disconnects at SFU + signaling.
  • POST /rooms/{id}/participants/{user_id}/ban · /unban.
  • GET /rooms/{id}/moderation-log?cursor=... — audit trail.

TURN Management Service

POST /ice/credentials

// request (session-scoped; called at join and on ICE restart)
{ "session_id": "s_...", "room_id": "r_..." }
// 200
{
"ice_servers": [ ...same shape as join response... ],
"ttl": 600,
"region": "eu-west"
}

Normally invoked server-side by Room Service during join; exposed directly for ICE restarts mid-call when credentials near expiry.

Internal/admin

  • GET /ice/clusters — cluster health + capacity (admin).
  • GET /ice/metrics — usage per tenant/cluster (feeds billing later).

Presence Service

  • GET /presence/rooms/{id}{ "count": 12, "participants": [{ "user_id": "...", "state": "speaking|muted|idle|listening" }] }
  • GET /presence/users/{id}{ "online": true, "region": "eu-west", "room_id": "r_..." | null }
  • GET /presence/tenant/summary → aggregate occupancy for tenant dashboards.

Analytics Service

  • POST /analytics/qos — client SDK batch ingest (array of samples; fire-and-forget, 202).
  • GET /analytics/rooms/{id}/quality?from=&to= — per-room QoS time series.
  • GET /analytics/sessions/{session_id} — session summary incl. MOS.
  • GET /analytics/tenant/overview?from=&to= — active users, minutes, quality distribution.

Gateway-level behaviors (apply to all routes)

ConcernBehavior
Rate limitsper-user 60 rpm default; POST /rooms/*/join 10 rpm; POST /auth/login 5 rpm/IP; tenant API keys per-plan
ValidationOpenAPI schema validation before routing; 422 with field errors
Tracingtraceparent propagated; request_id returned in every response header
CORSTenant-configurable allowed origins