# Error codes and retries

> Every error code with its status, whether it is retryable and its message, when to retry, and the request ids and logs that trace a failure.

Source: https://docs.minket.com/errors/
Updated: 2026-10-29

## The shape

Every non-2xx answer carries one body:

```json
{
  "error": {
    "code": "concurrent_idempotent_requests",
    "message": "A request with this idempotency key is still being processed. Retry shortly.",
    "retryable": true,
    "request_id": "req_019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
  }
}
```

| Field | Meaning |
|---|---|
| `code` | A stable identifier from the table below. Branch on this, not on the message. |
| `message` | What happened and what to do next. A validation message names the first invalid field by its full path (``Missing `definition.steps.0.template_id` field.``). |
| `retryable` | True when repeating the same request with the same idempotency key can succeed. |
| `request_id` | The request's id, see [Request ids and logs](https://docs.minket.com/errors/#request-ids-and-logs). |
| `issues` | Validation only: `[{ "path": "attachments.0.filename", "message": "…" }]`, one per invalid field, with dotted paths into the request body. |

A failed item of a batch carries `{ "code", "message", "retryable" }` in its own result.

## Retrying

- When `retryable` is true, repeat the request with the same `Idempotency-Key`: the key makes sure a retry never sends a second email. A `retry-after` header says how many seconds to wait first; it comes with `429 rate_limit_exceeded` and `409 concurrent_idempotent_requests`.
- `500` errors and `502 upstream_unavailable` are retryable. `502 outcome_unknown` is not: the provider's answer was lost, so the email may have gone out, and it is failed rather than sent again ([Never sent twice](https://docs.minket.com/delivery/#never-sent-twice)).
- `429 daily_quota_exceeded` lasts until the next UTC day, or until the cap is raised in the project's settings.
- Validation errors (`400`, `422`) need a changed request; `issues` names the fields.
- A body that is not valid JSON is `422 validation_error`, and a body sent without a JSON content type is `415 unsupported_media_type`. Send `Content-Type: application/json`, which `fetch` leaves out for a string body. A request with no body at all reads as `{}`.
- `501 not_implemented` means the route is not served yet. It is not retryable.

## Codes

| Code | Status | Retryable | Message |
| --- | --- | --- | --- |
| `invalid_idempotency_key` | 400 | no | The idempotency key must be between 1 and 256 characters. |
| `invalid_sign_in_code` | 400 | no | That sign-in code is not valid. Check it and try again, or ask for a new code. |
| `missing_api_key` | 401 | no | Missing API key in the authorization header. |
| `restricted_api_key` | 401 | no | This API key is restricted to only send emails. |
| `unauthorized` | 401 | no | Sign in to continue. |
| `account_blocked` | 403 | no | This account is blocked. Contact Minket if you think this is a mistake. |
| `domain_authentication_failed` | 403 | no | SPF, DKIM or DMARC is failing for this domain. Sending resumes when DNS is fixed. |
| `domain_not_verified` | 403 | no | This domain is not verified yet. Finish its DNS records in the console. |
| `forbidden` | 403 | no | Your role does not allow this. Ask an owner or admin of the organization. |
| `invalid_access` | 403 | no | Access to this resource is not allowed. |
| `invalid_api_key` | 403 | no | API key is invalid. |
| `invalid_from_address` | 403 | no | The from address does not belong to a verified domain of this project. |
| `organization_suspended` | 403 | no | This organization is suspended. Contact Minket to restore sending. |
| `signup_closed` | 403 | no | Sign-up is closed. Ask an owner of an organization for an invitation. |
| `test_mode_only` | 403 | no | Test keys cannot do this. Use a live key. |
| `not_found` | 404 | no | The requested item does not exist. |
| `method_not_allowed` | 405 | no | This method is not allowed for this endpoint. |
| `concurrent_idempotent_requests` | 409 | yes | A request with this idempotency key is still being processed. Retry shortly. |
| `conflict` | 409 | no | This already exists. |
| `invalid_state` | 409 | no | This action is not possible in the item's current state. Check its status first. |
| `mailbox_unavailable` | 409 | no | This mailbox needs to be reconnected before it can send. |
| `payload_too_large` | 413 | no | The request body is too large. |
| `unsupported_media_type` | 415 | no | Send JSON with a content-type of application/json. |
| `content_blocked` | 422 | no | The content failed a deliverability check. See issues for details. |
| `invalid_attachment` | 422 | no | An attachment is invalid. |
| `invalid_idempotent_request` | 422 | no | This idempotency key was already used with a different request. |
| `invalid_parameter` | 422 | no | A parameter has an invalid value. |
| `invalid_region` | 422 | no | This region is not supported. |
| `missing_required_field` | 422 | no | A required field is missing. |
| `stream_rule_violation` | 422 | no | Marketing email needs an unsubscribe link and List-Unsubscribe headers. |
| `validation_error` | 422 | no | The request is not valid. |
| `daily_quota_exceeded` | 429 | no | This organization has used its daily sending quota. It resets at midnight UTC; ask Minket to raise it. |
| `monthly_quota_exceeded` | 429 | no | This organization has used its monthly sending quota. It resets on the first of the month (UTC); ask Minket to raise it. |
| `rate_limit_exceeded` | 429 | yes | Too many requests. Slow down and retry after the time in retry-after. |
| `security_error` | 451 | no | This request was blocked for security reasons. |
| `application_error` | 500 | yes | Something went wrong on our side. Retry with the same idempotency key. |
| `internal_server_error` | 500 | yes | Something went wrong on our side. Retry with the same idempotency key. |
| `not_implemented` | 501 | no | This endpoint is not available yet. Check the deployment's version. |
| `outcome_unknown` | 502 | no | The provider's answer was lost, so it is unknown whether this email went out. It was not sent again, to avoid a duplicate. |
| `upstream_unavailable` | 502 | yes | A provider did not respond. Retry with the same idempotency key. |
| `sending_paused` | 503 | yes | Sending is paused for this project. Check alerts in the console. |

## Request ids and logs

Every response carries an `x-request-id` header. Send your own (8 to 128 characters from `A-Z`, `a-z`, `0-9`, `_` and `-`) and it comes back unchanged; otherwise the API makes one, such as `req_019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b`.

Every request made with a key is logged with its method, route pattern (`/v1/emails/:id`, never the concrete path), status, latency, key id, environment, mode, error code, request id, the caller's address and user agent, and the SHA-256 of its `Idempotency-Key`. Bodies and recipients are never logged. `GET /v1/logs` lists a key's own environment and mode, newest first, filtered by `status`, `path`, `api_key_id`, `error_code`, `from_date` and `to_date`; `GET /v1/logs/{id}` returns one. Logs are kept 30 days, and the console shows them under Logs.

```sh
curl "https://api.minket.com/v1/logs?error_code=rate_limit_exceeded" \
  -H "Authorization: Bearer $MINKET_API_KEY"
```
