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(withRetry-After),503 unavailable. - Idempotency: mutating POSTs accept
Idempotency-Keyheader (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)
| Concern | Behavior |
|---|---|
| Rate limits | per-user 60 rpm default; POST /rooms/*/join 10 rpm; POST /auth/login 5 rpm/IP; tenant API keys per-plan |
| Validation | OpenAPI schema validation before routing; 422 with field errors |
| Tracing | traceparent propagated; request_id returned in every response header |
| CORS | Tenant-configurable allowed origins |