# Errors, limits and good practice

## Error format

Every error comes back like this:

```json
{
  "error": "validation_failed",
  "message": "A field is wrong. `details` says which and why.",
  "details": { "name": ["Required"] },
  "request_id": "01a3c0de-7a11-7b22-8c33-9d44e55f6677"
}
```

- `error` is the stable code: your system decides what to do from it.
- `message` explains, in the language of the `Accept-Language` header (Portuguese, English or Spanish; English by default).
- `request_id` identifies the call: quote it to support. You may send your own in `X-Request-Id`.

| HTTP | Code | What to do |
|---|---|---|
| 401 | `unauthorized` | The key is missing. Send Authorization: Bearer <key>. |
| 401 | `api_key_invalid` | Invalid or revoked key. Check it or create another in Settings › API. |
| 401 | `api_key_expired` | The key has expired. Create another in Settings › API. |
| 403 | `api_key_address_not_allowed` | This key does not accept calls from this IP address. Add the address to the key. |
| 403 | `insufficient_scope` | The key lacks this call's scope. `details.required` lists the ones that work. |
| 403 | `not_available_to_api_keys` | This call is not part of the public API. |
| 403 | `not_available_to_test_keys` | This call has no test mode: use a production key. |
| 403 | `permission_denied` | The key may not do this. |
| 403 | `integration_template_only` | Text and files need the messages:send_text scope; without it, approved templates only. |
| 404 | `not_found` | Not found. |
| 404 | `campaign_not_found` | Campaign not found in this company. |
| 404 | `contact_not_found` | Contact not found in this company. |
| 422 | `validation_failed` | A field is wrong. `details` says which and why. |
| 409 | `campaign_not_editable` | The campaign is no longer a draft; only a draft can be edited or started. |
| 409 | `template_not_approved` | Meta has not approved the template yet. Wait for approval or pick another. |
| 409 | `campaign_audience_empty` | No one in the audience can receive the campaign. See why in the audience preview. |
| 409 | `contact_exists` | A contact with this phone already exists. `details.contactId` says which. |
| 409 | `window_closed` | The 24-hour window is closed: only an approved template can start the conversation. |
| 409 | `idempotency_key_reused` | This Idempotency-Key was already used with another request. Use a new key per request. |
| 409 | `idempotency_in_progress` | The first request with this Idempotency-Key is still running. Retry in a moment. |
| 429 | `rate_limited` | Limit of 600 calls per minute. Wait the seconds in Retry-After. |
| 500 | `internal_error` | Error on our side. We were alerted; retry, and quote the request_id if it persists. |

## Rate limit

Each key may make 600 calls per minute. Every answer says where you are:

```http
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 41
```

Past the limit, the answer is `429 rate_limited` with `Retry-After` (seconds). Wait that long and retry. A campaign's sending does not count: DoneTalk sends at the pace Meta allows for the number.

## Retry without duplicating

Send `Idempotency-Key` (up to 200 letters, digits, `_ . : -`) on creations and sends. If the network fails and you send the same call again with the same key within 24 hours, you get the first answer back, with `Idempotent-Replayed: true`, and nothing is done twice. The same key with another request returns `409 idempotency_key_reused`.

```http
Idempotency-Key: order-88213-campaign
```

## Paging

- **Campaigns:** `limit` and `cursor`; the answer carries `nextCursor` (null on the last page).
- **Contacts:** `limit` and `before` (the page's last `lastActivityAt`).
- **A campaign's recipients:** `limit` and `offset`, or `format=csv` for everything.

## Versions

v1 only gains things: more fields and calls, never a change to what exists. Ignore fields you do not know. If something ever must change, v2 is born and v1 keeps working for 12 months, with notice.
