# Errors

> Error responses have the shape {statusCode, name, message}. The full list of error names, their HTTP status and how to handle them.

Source: https://docs.pidgely.com/errors/

Pidgely uses conventional HTTP status codes: `2xx` for success, `4xx` for a problem with the request, `5xx` for a problem on our side. Errors have this body:

```json
{
  "statusCode": 422,
  "name": "validation_error",
  "message": "to: at least one recipient is required"
}
```

- `name` is stable and machine-readable: branch on it, never on `message`.
- `message` is meant for humans. For validation errors it starts with the field, for example `subject: is too long` or `[3].to: …` inside a batch.
- Every response carries an `X-Request-Id` header. Send your own (up to 64 characters) to correlate logs, and include it when you [contact support](https://docs.pidgely.com/support/).

## Error names

| Status | `name` | When | What to do |
|---|---|---|---|
| 400 | `invalid_idempotency_key` | `Idempotency-Key` longer than 256 characters | Use a shorter key |
| 401 | `missing_api_key` | No `Authorization: Bearer …` header | Send the key |
| 401 | `restricted_api_key` | A `sending_access` key called an endpoint that needs `full_access` | Use a full-access key for that call |
| 403 | `invalid_api_key` | The key does not exist or was revoked | Check the key and the environment |
| 403 | `validation_error` | The request is valid but not allowed: `from` domain not verified, key restricted to another domain, plan limit on domains reached, domain already added or verified by another account, sending paused or account suspended | Read `message`; verify the domain or fix the account state |
| 404 | `not_found` | The resource does not exist in your account (also returned for ids that are not valid UUIDs) | Check the id |
| 409 | `invalid_idempotent_request` | The `Idempotency-Key` was used before with a different request | Use a new key for a new request |
| 409 | `not_cancelable`, `not_reschedulable` | The email is no longer scheduled (already sent or canceled) | Nothing to cancel or update |
| 409 | `alias_taken` | Another template already uses this alias | Choose another alias |
| 409 | `broadcast_not_draft`, `broadcast_not_cancelable`, `broadcast_in_progress` | The broadcast is in a state that does not allow the operation | See [Broadcasts](https://docs.pidgely.com/guides/broadcasts/#lifecycle) |
| 422 | `validation_error` | A field is missing or invalid, the JSON is malformed, or the body is too large | Fix the request; `message` names the field |
| 422 | `invalid_attachment` | An attachment has neither `content` nor `path` | Provide one of them |
| 429 | `rate_limit_exceeded` | Too many requests per second | Wait `Retry-After` seconds and retry |
| 429 | `daily_quota_exceeded` | The daily quota (UTC day) is used up | Wait for the next UTC day or upgrade |
| 429 | `monthly_quota_exceeded` | The monthly quota is used up | Wait for the next month or upgrade |
| 500 | `application_error` | Unexpected error on our side | Retry with backoff (with the same `Idempotency-Key` for sends) |

## Retrying

- **Retry** `429` and `5xx` with exponential backoff. For `429 rate_limit_exceeded`, honour `Retry-After`.
- **Don't retry** other `4xx` errors unchanged: they fail the same way every time.
- **Make sends safe to retry** with an [`Idempotency-Key`](https://docs.pidgely.com/idempotency/): a network timeout then never turns into a duplicate email. Responses with `5xx` are not stored, so a retry with the same key runs again.

## Native API errors

The native `/v1` API uses a different shape, with the field that failed and the request id:

```json
{
  "error": {
    "code": "validation_error",
    "message": "at least one recipient is required",
    "field": "to",
    "request_id": "8f2c1a7d9e3b4c60"
  }
}
```

Codes there are the internal ones (`domain_not_verified`, `tenant_paused`, `plan_limit`, `idempotency_key_reused`, `rate_limited`, `insufficient_scope`, …), with the same statuses: 401 missing key, 403 forbidden, 404 not found, 409 conflict, 422 validation, 429 rate or quota. The [main API](https://docs.pidgely.com/api-reference/) is the recommended surface for new code.

## Request limits

| Limit | Value |
|---|---|
| `POST /emails` and `POST /emails/batch` body | 50 MiB |
| `POST`/`PATCH` on templates and broadcasts | 5 MiB |
| Other write endpoints | 1 MiB |
| Built message (headers, bodies and attachments, after encoding) | 40 MiB |
| Request timeout | 60 seconds |

A body over its limit gets `422 validation_error` with `request body too large`.
