Errors
Every error UnifAPI returns, what it means, and how to recover.
Errors always come back in the same envelope, regardless of which upstream provider was called:
{
"error": {
"type": "rate_limited",
"message": "Workspace rate limit exceeded.",
"request_id": "tikhub-5471f8cf-a381-4204-a280-b73df8c93167"
}
}error.typeis from a closed vocabulary (see table below) — stable, machine-readable, safe to switch on.error.messageis for humans and may change without notice.error.request_idis the upstream-prefixed trace id. Include it in support requests.error.issuesis present only whentype = validation_error— it's the raw Zod issue list.
The HTTP status mirrors the type — clients can pick whichever signal they prefer.
Reference
| Status | type | When it happens | Recovery |
|---|---|---|---|
| 400 | validation_error | Request failed gateway-side validation (bad query, missing body field). | Inspect error.issues. Fix the request. |
| 400 | invalid_id | The provided id was syntactically valid but the upstream rejected its format. | Use a real id. |
| 401 | unauthorized | Authorization header missing, malformed, or rejected by the upstream. | Rotate or recreate the key. |
| 404 | not_found | The resource (video, user, room, ...) does not exist, is deleted, or is private. | Confirm the id; some resources are private and 404 by design. |
| 429 | rate_limited | Workspace or upstream rate limit exceeded. | Honor Retry-After. See Rate limits. |
| 500 | internal_error | UnifAPI itself failed (uncaught exception). | Safe to retry. Report request_id if it persists. |
| 500 | response_shape_error | Upstream returned a payload that no longer matches our schema (upstream drift). | Report request_id — drift is a bug. |
| 502 | upstream_error | Upstream returned an error UnifAPI cannot recover from. | Inspect error.message. |
| 503 | upstream_unavailable | Upstream timed out or is unreachable. | Retry with exponential backoff. |
The full vocabulary is captured in ADR 0003 — Error model.
Anatomy of a validation error
For validation_error, the issues array carries the raw Zod issue list so a caller can map errors back to fields:
{
"error": {
"type": "validation_error",
"message": "Request validation failed.",
"issues": [
{
"code": "too_big",
"maximum": 50,
"type": "number",
"inclusive": true,
"path": ["limit"],
"message": "Number must be less than or equal to 50"
}
]
}
}Always log request_id
Every error response carries request_id (when the failure originated from or transited an upstream call). Log it. Support requests without a request_id take days; with one, minutes.