Control API
JSON over HTTP for the offer/answer exchange and group updates. A call makes a handful of requests, not one per packet. Put soli-proxy in front of it for TLS.
Routes
| Route | Body | Response |
|---|---|---|
POST/v1/sessions | {token, sdp_offer, peers?} | {session_id, sdp_answer} |
PATCH/v1/sessions/:id | {token, peers} | 204 |
DELETE/v1/sessions/:id | {token} | 204 |
POST/v1/sessions/:id/slots | {token} | {slot_mid: user_id} |
GET/v1/stats | Authorization: Bearer | {sessions, rooms} |
GET/healthz | ok or 503 |
Every session route checks that the token's user and room own the session: a token for one room cannot touch the same user's session in another.
Create a session
POST /v1/sessions
{
"token": "sfu1.dXNlci00Mg==:c3BhdGlhbDphY21l:1781234567.ab12…",
"sdp_offer": "v=0\r\no=- 46117…",
"peers": ["user-7", "user-12"]
}
200 OK
{ "session_id": 3, "sdp_answer": "v=0\r\no=- 89221…" }- The room comes from the token, not the body. A browser cannot join a room it was not given a token for.
peerslists who this person hears, at most 256 ids. Leave it out, or sendnull, to hear the whole room.- Posting again for the same user and room replaces the earlier session.
Change the group
Send the new set whenever the conversation changes shape. It applies to the next packet. Slots held by people who drop out of the set are freed straight away.
PATCH /v1/sessions/3
{ "token": "sfu1.…", "peers": ["user-7", "user-12", "user-31"] }
204 No ContentWho is on which slot
Slots carry no identity in the media, so a client that labels video
tiles asks for its own bindings. The mids match
RTCRtpTransceiver.mid on its receive-only transceivers.
POST /v1/sessions/3/slots
{ "token": "sfu1.…" }
200 OK
{ "2": "user-7", "9": "user-12" }Stats
Requires a valid token, and reports only that token's room.
sessions is the server-wide total; no other room names are
returned, so one tenant cannot list another's offices.
GET /v1/stats
Authorization: Bearer sfu1.…
200 OK
{ "sessions": 12, "rooms": { "spatial:acme": 4 } }Health
/healthz needs no token. It answers ok while
the media thread is alive and 503 once it is gone, so a
load balancer stops sending people to a broken instance.
Errors
| Status | Meaning |
|---|---|
400 | The offer did not parse or could not be accepted, or peers has more than 256 entries. The body says which. |
401 | The token is missing, expired, or signed with another secret. |
404 | No session with that id belongs to this token's user and room. |
413 | The body is larger than 256 KiB. A normal offer is about 30 KiB. |
503 | The media engine is down, or did not answer within 5 seconds. |
CORS accepts any origin, because the browser may call the API from the app's own domain; the token is the authentication.