# Errors

The error body, every error code, and what stops an account from sending.

Every error from the API is JSON, with an `error` code for your program and a `message` for a person. A request that fails validation also has `issues`, one per invalid field:

```json
{
  "error": "invalid_request",
  "message": "The request is invalid: to.0: Invalid email address",
  "issues": [{ "path": "to.0", "message": "Invalid email address" }]
}
```

Some errors add fields, such as the suppressed recipients or when a limit resets. Each operation's page in the [reference](https://sendora.se/docs/api) lists them.

Two refusals are plain text, not JSON: `429 Too Many Requests` and `413 Request Entity Too Large`. They come from the proxy in front of the API, as [Limits](https://sendora.se/docs/limits#requests-per-second) explains. A body over the size limit meets the proxy first, so it gets the plain-text 413.

## Retry or fix

- `rate_limited` (429): wait the seconds in `retryAfter`, which the `Retry-After` header also gives, then send again. The refused message was not stored. If you meet this limit often, ask support to raise it.
- `sending_disabled` (503): sending is disabled for everyone for the moment. Retry after the seconds in the `Retry-After` header.
- `monthly_cap_reached` (429): wait until `resetsAt`, or have an administrator of your account raise the cap under [Account › Usage](https://app.sendora.se/account/usage). [Limits](https://sendora.se/docs/limits#sending-rates-and-the-monthly-cap) says how far they can raise it.
- A 5xx status: retry with a growing delay, under the same idempotency key so the message cannot go twice.
- Any other code: read its meaning in the table below. Most need a fix to the request or the account, and fail the same way if sent again unchanged.

On `rate_limited` and `monthly_cap_reached`, `scope` says whose limit it is: the account's (`account`), the server's own (`server`) or the test servers' (`test`). Test mail never counts toward the account's monthly cap. A [webhook](https://sendora.se/docs/webhooks) can warn you before the cap is used up.

With the TypeScript SDK:

The SDK throws `SendoraError` for every failed call. It has the same `code`, the HTTP `status` and the body's extra fields, such as `issues`, `retryAfter`, `scope`, `limit`, `cap`, `used`, `resetsAt`, `suppressed` and `existingId`. `retryable` says whether trying again could succeed. A lost connection or a timeout is the same error, with `connection_failed` or `timeout` and no status.

The SDK retries rate limits, `sending_disabled` and server errors itself for a few seconds. If `retryAfter` is longer than that, it throws at once so you can schedule the retry.

With the Python SDK:

The SDK raises a `SendoraError` for every failed call. Its class depends on the status, such as `RateLimitError` for 429. It has the same `code`, the HTTP `status` and the body's extra fields, such as `issues`, `retry_after`, `scope`, `limit`, `cap`, `used`, `resets_at`, `suppressed` and `existing_id`. `retryable` says whether trying again could succeed. A lost connection is an `APIConnectionError` with `connection_failed`, and a timeout an `APITimeoutError` with `timeout`, both without a status.

The SDK retries rate limits, `sending_disabled` and server errors itself for a few seconds. If `retry_after` is longer than that, it raises at once so you can schedule the retry.

## When an account cannot send

- `payment_required` (402): the account has no active payment, for instance before a card is saved. Activate payment under [Account › Billing](https://app.sendora.se/account/billing) in the dashboard. Every send from a live server fails until then.
- `account_not_active` (403): Sendora has not approved the account yet. Every account is reviewed by hand before a live server can send. A [test server](https://sendora.se/docs/test-servers) works before that.
- `account_paused` (403): Sendora has paused the account, for instance after a failed payment. Support tells you why. A pause for an unpaid subscription lifts once you save a working card under Account › Billing.

Over SMTP, the same refusals arrive as SMTP replies, which [SMTP submission](https://sendora.se/docs/smtp#smtp-replies) lists.

## Error codes

| Code | Status | Meaning |
| --- | --- | --- |
| `account_not_active` | 403 | The account is closed, or not yet approved for a live server's sends or an inbound stream. |
| `account_paused` | 403 | The account is paused by Sendora. |
| `broadcast_not_open` | 409 | The broadcast is completed or cancelled already; nothing is left to cancel. |
| `content_expired` | 410 | The content of this received message is gone; the stream’s content window has passed. The message itself stays 13 months. |
| `content_unreadable` | 409 | The stored content of this received message cannot be opened. Sendora has been told. |
| `default_stream` | 409 | The default stream of a server cannot be archived. |
| `domain_exists` | 409 | The account already has that domain. |
| `domain_reserved` | 400 | The name is Sendora’s own, or a return-path host; no account can receive on it. |
| `from_domain_not_verified` | 422 | The From domain is not a verified sending domain of this server. |
| `idempotency_key_mismatch` | 422 | The Idempotency-Key was already used for a different request. |
| `idempotency_key_required` | 400 | Batch sends need an Idempotency-Key header. |
| `inbound_domain_exists` | 409 | The stream already has a domain, or another account holds the name verified. |
| `inbound_stream_exists` | 409 | A server has one inbound stream at a time; archive it to make another. |
| `invalid_request` | 400 | The body, the query or a header does not match what the route takes. |
| `last_secret` | 409 | The only live secret of a webhook cannot be deleted; create another first. |
| `last_token` | 409 | This is the only live key of the server; create another before revoking it. |
| `list_unsubscribe_reserved` | 422 | On a broadcast stream Sendora writes the List-Unsubscribe pair itself, so a message may not carry one. |
| `mode_immutable` | 400 | A server’s mode is fixed when it is created; create a new server for the other mode. |
| `monthly_cap_reached` | 429 | The monthly cap of the account, the server or the test servers is used up. |
| `not_found` | 404 | No such broadcast of this server. |
| `payment_required` | 402 | The account has no active subscription. |
| `rate_limited` | 429 | More was sent within a minute than the limit allows. |
| `recipient_suppressed` | 422 | One or more recipients are on the suppression list of the stream. |
| `request_too_large` | 413 | The body exceeds 10 MB. |
| `secret_limit` | 409 | The webhook already holds two live secrets: one in use and one to roll to. Delete one before creating another. |
| `sending_disabled` | 503 | Sending is disabled for everyone for the moment; retry after the seconds in Retry-After. |
| `server_exists` | 409 | The account already has a server with that name. |
| `server_in_flight` | 409 | Messages of the server are still being delivered; try again once they have left. |
| `server_limit` | 409 | The account has as many servers as it may; Sendora raises the cap on request. |
| `server_reserved` | 409 | This server sends Sendora’s own sign-in mail and cannot be renamed or removed. |
| `spam_complaint_locked` | 403 | The recipient reported a message as spam; only Sendora support lifts that, at the recipient’s own request. |
| `stream_archived` | 422 | The stream is archived and takes no new messages. |
| `stream_exists` | 409 | The server already has a stream with this name, archived or not. |
| `stream_kind_not_allowed` | 422 | A test server has no inbound stream; receive on a live server. |
| `stream_not_broadcast` | 422 | A broadcast goes on a broadcast stream; the streamId names a transactional one. |
| `stream_not_found` | 422 | The streamId names no stream of this server. |
| `stream_not_sendable` | 422 | An inbound stream receives mail; it takes no messages and has no suppression list. Name a transactional or broadcast stream. |
| `stream_paused` | 422 | Sendora has paused the stream after complaints; it takes no messages until support has resumed it. |
| `substitution_missing` | 422 | The subject, text or HTML names a {{ key }} that a message does not give. |
| `test_address_on_live_server` | 422 | Addresses at simulator.sendora.se act out an outcome on a test server; a live server never sends to them. |
| `token_limit` | 409 | The server or the account already holds two live keys: one in use and one to rotate to. Revoke one before creating another. |
| `unauthorized` | 401 | The key is missing, malformed or revoked. |
| `unsubscribe_locked` | 403 | The recipient unsubscribed themselves; only Sendora support lifts that, at the recipient’s own request. |
| `unsubscribe_placeholder_missing` | 422 | A message on a broadcast stream must carry {{ unsubscribe_url }} in every part it has. |
| `webhook_exists` | 409 | The server already has a webhook for that URL. |
| `wrong_token_kind` | 403 | The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes. |
