Control plane API
Health, token minting and the avatar provider catalogue, on the Node service.
The only process holding the media server's signing key. Everything else that needs a token asks it.
Base: $LIVEGRID_CONTROL_URL. Implemented in src/server.ts.
Authentication. Authorization: Bearer $UDK_SECRET, compared in constant
time — === on a secret leaks its length and prefix through timing, and doing it
properly costs three lines.
UDK_SECRETunset authorises every request. That is intended for local development and the process warns about it on boot. Nothing stops it running that way in production, where it is a public token-minting API.
GET /health#
Unauthenticated on purpose — a platform health check carries no bearer token.
{ "status": "ok", "version": "0.1.0",
"mediaPlane": "reachable", "checkedAt": "2026-09-10T06:12:44.101Z" }
status is degraded, never 503, when the media plane cannot be reached.
Health and status
POST /token#
{ "room": "support-8f3a21", "identity": "daniel", "role": "human", "ttlSeconds": 3600 }
room and identity are required. role is one of human, agent,
observer and defaults to human; anything else is a 400 that names the ones it
knows.
{ "token": "eyJhbGciOi...", "url": "wss://sg.example.com",
"room": "support-8f3a21", "identity": "daniel",
"expiresAt": "2026-09-10T07:12:44.000Z" }
A caller names a role; it never supplies a grant. A caller that could
assemble its own could give a browser participant canPublishData and drive the
agent. Access tokens and grants
| Status | Means |
|---|---|
| 400 | missing room/identity, or an unknown role |
| 401 | wrong or missing bearer |
| 503 | LIVEKIT_API_KEY / LIVEKIT_API_SECRET unset here |
GET /providers/avatar#
| Query | Effect |
|---|---|
available=true | only vendors whose key is set in this environment |
{ "providers": [ { "id": "bithuman", "label": "bitHuman",
"usdPerMinute": 0.04, "apiKeyEnv": "BITHUMAN_API_KEY",
"nodeSupport": false, "caveat": "..." } ],
"count": 1 }
The catalogue reports which providers are available by checking whether a key is set. It never returns the value.
GET /providers/avatar/<id>#
One provider, or 404 with a message naming the id. Validate a choice here before a call is attempted rather than during one — a 404 you can handle, a 401 mid-conversation you cannot.
POST /mcp#
The JSON-RPC surface. MCP tools
Everything else#
404 {"error": "No route for <METHOD> <path>"}. Request bodies are capped at
64 KB.