Errors
Every status this kit returns, what causes it, and what to do about it.
Every status this kit returns, what causes it, and what to do. The rule behind the table: a failure that is somebody else's to fix says so in its status.
By status#
| Status | Where | Means |
|---|---|---|
| 400 | control plane /token | missing room or identity, or an unknown role |
| 400 | POST /public/avatars | no display_name, no consent, or an unsupported content_type |
| 400 | webhook | the body is not JSON |
| 401 | control plane, MCP | wrong or missing UDK_SECRET |
| 401 | /public/* | the customer JWT is missing, expired or wrongly signed |
| 401 | webhook | signature failed, or the digest does not match the body |
| 401 | ingest, internal avatar routes | the shared secret does not match |
| 404 | /public/calls/<id>, /public/avatars/<id> | no such thing or not yours — deliberately identical |
| 404 | /providers/avatar/<id> | unknown provider id |
| 409 | POST /public/avatars/<id>/uploaded | the object is not in storage yet. Recoverable |
| 500 | webhook | the handler threw. Retry it — the upsert is idempotent |
| 503 | control plane /token | LIVEKIT_API_KEY / LIVEKIT_API_SECRET unset |
| 503 | ingest | LIVEGRID_INGEST_SECRET unset |
| 503 | internal avatar routes | LIVEGRID_RENDER_SECRET unset |
| 503 | POST /public/avatars | storage would not sign an upload URL |
503 rather than 500 for an unset key is deliberate throughout: the service is fine, it is missing a dependency it cannot supply itself, and the two need different responses from whoever is paged.
404 for "not yours" is also deliberate. Distinguishing it from "no such id" tells an unauthorised caller which ids are real.
Failures with no status at all#
The expensive ones. Every item here returns 200 somewhere and is wrong.
| Symptom | Cause |
|---|---|
| The agent joins and never speaks | Its token was minted with the human role. Agent control rides the data channel; without canPublishData the messages are dropped silently |
| A call connects, no agent ever appears | No worker registered under that agent_name, or none is running |
| The token mints, the browser never connects | The client was given the control plane's URL instead of url from the mint response |
| A region silently does not exist | Its named key or secret is missing from the environment, so it was dropped from REGIONS_JSON without a warning. Check describe_call_config |
| A call exists with no transcript | Normal. The transcript is best-effort; the lifecycle row is authoritative |
| The console shows no live calls during an outage | You rendered reachable: false as count: 0 |
| Every request to the control plane is authorised | UDK_SECRET is unset |
An avatar is stuck on pending | The browser took an upload ticket and never confirmed, or never PUT the file |
| Enrolment never starts | LIVEGRID_RENDER_SECRET unset, so the GPU box cannot claim work |
| Renders are shipping unreviewed | GOOGLE_API_KEY unset. Nothing is checking the motion |
When something is wrong, in order#
udk health. Is the media plane up at all.GET /healthon the control plane.degradednames the media plane;okclears it.describe_call_config. What the control plane actually loaded, rather than what you think you configured.get_call_status <room>. Who is in the room, and whether the agent is one of them.GET /public/calls/<id>. What was recorded, after the fact.
The order is by cost. Step 1 answers in under a second and rules out the cause of most early failures; step 5 needs the call to have finished.