soli-sfu

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

RouteBodyResponse
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/statsAuthorization: Bearer{sessions, rooms}
GET/healthzok 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.
  • peers lists who this person hears, at most 256 ids. Leave it out, or send null, 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 Content

Who 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

StatusMeaning
400The offer did not parse or could not be accepted, or peers has more than 256 entries. The body says which.
401The token is missing, expired, or signed with another secret.
404No session with that id belongs to this token's user and room.
413The body is larger than 256 KiB. A normal offer is about 30 KiB.
503The 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.