# Errors

> Every error has a stable code. Match on the code, show the message to people.

Errors come back with an HTTP status and this body:

```json
{
  "error": {
    "code": "limit_pending_streams",
    "message": "At most 2 streams may be waiting to go live at once. Start or delete one first.",
    "details": { "limit": 2, "current": 2 }
  }
}
```

`code` never changes for a given condition, so program against it. `message` is written for people and may change. `details` appears when there are numbers worth having.

## Codes

| Status | `code` | Meaning | What to do |
|---|---|---|---|
| 400 | `invalid_request` | A field is missing, unknown or out of range. The message names it. | Fix the request. |
| 401 | `unauthorized` | Missing or wrong API key. | Send `Authorization: Bearer joa_live_…`. |
| 402 | `insufficient_credit` | Less than $1.00 of credit left. `details` has the balance. | Add credit. |
| 403 | `account_pending` | The account hasn't been approved yet. | Wait for the approval email. |
| 403 | `tenant_suspended` | The account is suspended. | Contact JustOnAir. |
| 404 | `not_found` | No such stream in this account. | Check the id and the key's account. |
| 409 | `stream_not_pending` | The key can only be replaced before the stream goes live. `details.status` says the current status. | For an expired stream, create a new one. |
| 409 | `stream_ended` | The stream has ended, so no new playback links. | There is no replay; use the recording. |
| 409 | `recording_not_ready` | The MP4 isn't ready. `details.status` says why. | Poll the stream until `recording.status` is `ready`. |
| 422 | `idempotency_key_reused` | This `Idempotency-Key` was already used with a different body. | Use a new key for a different request. |
| 429 | `limit_pending_streams` | Too many streams waiting to go live. | Start or delete one first. |
| 429 | `limit_creation_rate` | Too many streams created in the last hour. | Wait; reuse pending streams. |
| 429 | `limit_ingest_hours` | Monthly ingest hours used up (new accounts: 20). | Wait for next month or ask to be trusted. |
| 429 | `limit_resolution` | `max_resolution` above what the account may send (new accounts: 720). | Ask to be trusted for 1080p. |
| 429 | `limit_api_keys` | 20 active API keys already. | Revoke one in the dashboard. |
| 429 | `limit_webhook_endpoints` | 5 webhook endpoints already. | Delete one, or subscribe one endpoint to more events. |
| 429 | `rate_limited` | Too many unknown ids on the public embed endpoint. | Slow down. |
| 503 | `no_capacity` | No ingest capacity free right now. | Retry in a minute. |

A 5xx without one of these codes is a problem on our side; retrying with the same `Idempotency-Key` is always safe.

## Refusals that aren't HTTP errors

Some problems happen on the video connection, not on an API call. A publisher can be refused (wrong key, stream already finished, another publisher connected, account out of credit), or a live stream can be ended. The API call to read the stream then shows why: `status` and `end_reason` on [Stream lifecycle](/streams).
