# Errors (/errors)



Errors always come back in the same envelope, regardless of which upstream provider was called:

```json
{
  "error": {
    "type": "rate_limited",
    "message": "Workspace rate limit exceeded.",
    "request_id": "tikhub-5471f8cf-a381-4204-a280-b73df8c93167"
  }
}
```

* `error.type` is from a **closed vocabulary** (see table below) — stable, machine-readable, safe to switch on.
* `error.message` is for humans and may change without notice.
* `error.request_id` is the upstream-prefixed trace id. Include it in support requests.
* `error.issues` is present only when `type = validation_error` — it's the raw Zod issue list.

The HTTP status mirrors the type — clients can pick whichever signal they prefer.

## Reference [#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](/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](https://github.com/winsloop/unifapi/blob/main/docs/adr/0003-error-model.md).

## Anatomy of a validation error [#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:

```json
{
  "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` [#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.
