# Errors

> The shape of an Emelia API error, what each HTTP status means today, the stable error codes, and how to retry safely.

Source: https://docs.emelia.io/docs/errors

## The shape of an error

The main API answers errors with a JSON body that always has an `error` message. Catalogued errors also carry a stable `code`, and sometimes `params`:

```json
{
  "error": "You cannot add more than 40 LinkedIn contacts per day when your sequence contains a connection request",
  "code": "MC_LINKEDIN_DAILY_LIMIT_CONNECTION",
  "params": { "limit": 40 }
}
```

Branch on `code` when there is one, never on the text of `error`: messages can be reworded, codes are never renamed. Most business errors have no code yet: rely on the HTTP status and show the message.

The lists and contacts routes (`/lists/...`) are served by another service and use a `message` field instead, sometimes with a `code`:

```json
{ "message": "Request body too large: the limit is 5mb", "code": "PAYLOAD_TOO_LARGE", "params": { "limit": "5mb" } }
```

Their `401` is the exception: `{ "error": "Authentication required", "code": "AUTH_REQUIRED" }`.

## Status codes

| Status       | Meaning today                                                                                                                                                                                                                                                                                                                                      | What to do                                                                         |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `200`, `201` | Success. Adding a contact that already exists also answers `200`: read `duplicate` in the body.                                                                                                                                                                                                                                                    |                                                                                    |
| `400`        | The request is invalid, a business rule refused it, **or the resource was not found**: the main API answers "not found" with a `400`, for example `{"error": "Campaign not found"}`. Validation errors carry `code: "INVALID_REQUEST"` and name the field, for example `"\"country\" is required"`. A malformed JSON body gets an HTML `400` page. | Fix the request. Do not retry it unchanged.                                        |
| `401`        | Missing, unknown or disabled API key: `{"error": "Authentication required"}`.                                                                                                                                                                                                                                                                      | See [Authentication](https://docs.emelia.io/docs/authentication.md).                                        |
| `404`        | Lists and contacts: the list or contact does not exist, or belongs to another account (`{"message": "List not found"}`). Any route: an unknown path, answered with an HTML page.                                                                                                                                                                   | Check the id, the path and the method.                                             |
| `409`        | `ACCOUNT_DISCONNECTED`: the LinkedIn account used by the request is disconnected.                                                                                                                                                                                                                                                                  | Reconnect the account in the app.                                                  |
| `413`        | Lists and contacts: the body is larger than 5 MB (`PAYLOAD_TOO_LARGE`).                                                                                                                                                                                                                                                                            | Send fewer contacts per call.                                                      |
| `422`        | Lists and contacts: the body is well formed but not acceptable (a field holds an array, a batch has more than 100 contacts, an email and a LinkedIn URL in the same lookup).                                                                                                                                                                       | Read `message`.                                                                    |
| `429`        | `RATE_LIMITED`: LinkedIn itself slows the account down.                                                                                                                                                                                                                                                                                            | Retry later.                                                                       |
| `500`        | **Your per minute quota is exceeded** (`{"error": "Internal server error"}`, no `Retry-After`), or an unexpected server error.                                                                                                                                                                                                                     | Wait for the next minute, then retry slower. See [Rate limits](https://docs.emelia.io/docs/rate-limits.md). |
| `503`        | `STATS_COMPUTING`: the statistics of the campaign are being computed, with `Retry-After: 30`.                                                                                                                                                                                                                                                      | Retry after the delay.                                                             |

### Routes with their own format

* The statistics routes (`/stats/...`) answer `401` when the campaign is not found, and `401 {"error": "You must be logged in"}` on any internal error. A `401` there does not always mean a bad key: check the campaign id first.
* `GET /webhook` answers `401 {"error": "You must be logged in"}` when reading the webhooks fails.
* `GET /credits/usage` answers `{"success": false, "error": "..."}`.
* The deliverability diagnosis (`POST /email-providers/warmup/check-health`) answers `{"success": false, "message": "..."}`.
* The message of a send activity answers `404 {"error": "NOT_FOUND"}` or `422 {"error": "NO_MESSAGE"}`.
* The LinkedIn scraper routes answer English sentences without code, all with a `400`.

## Out of credits

Enrichment calls fail with a `400` and this message when your balance is too low:

```json
{ "error": "You do not have enough credits to perform this action" }
```

Stop the batch rather than retrying row by row, and top up your credits. See [Credits](https://docs.emelia.io/docs/credits.md).

## Error codes

### Starting a campaign

`POST /advanced/campaigns/{campaignId}/start` runs these checks in this order, and stops at the first that fails:

| Code                                                                              | Meaning                                                                        |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `SUBSCRIPTION_REQUIRED`                                                           | A campaign needs an active subscription to start.                              |
| `CAMPAIGN_NOT_FOUND`                                                              | No campaign with this id on your account.                                      |
| `NO_IDENTITY_SET`                                                                 | The campaign has no identity (sender).                                         |
| `IDENTITY_FOREIGN_ACCOUNT`                                                        | An identity uses an account that does not belong to you.                       |
| `LINKEDIN_AUDIO_MISSING`                                                          | A LinkedIn voice note step has no recording.                                   |
| `EMAIL_STEP_NO_ACTIVE_VERSION`                                                    | An email step has no active version.                                           |
| `EMAIL_VERSION_NO_CONTENT`                                                        | A version of an email step is empty.                                           |
| `EMAIL_VERSION_NO_SUBJECT`                                                        | The first email of the sequence needs a subject.                               |
| `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`                                | Reconnect the LinkedIn account, or renew its slot.                             |
| `LINKEDIN_ACCOUNT_AMBIGUOUS`                                                      | Use a single LinkedIn account for all identities, or one per identity.         |
| `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`                           | A mailbox is disconnected, or its subscription expired.                        |
| `IDENTITY_EMAIL_PROVIDER_MISSING`                                                 | The sequence has an email step, so every identity needs an email provider.     |
| `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_AMBIGUOUS` | The same checks for WhatsApp accounts.                                         |
| `SCHEDULE_MISSING`                                                                | The campaign has no schedule.                                                  |
| `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`                                              | More than 40 new LinkedIn contacts per day with an invitation in the sequence. |
| `MC_LINKEDIN_DAILY_LIMIT`                                                         | More than 80 new LinkedIn contacts per day.                                    |
| `NO_RECIPIENTS`                                                                   | No contact in the attached lists.                                              |
| `TOO_MANY_RECIPIENTS`                                                             | More than 30,000 recipients.                                                   |
| `CAMPAIGN_ALREADY_RUNNING`                                                        | The campaign is already running.                                               |
| `RECIPIENTS_NO_EMAIL`                                                             | The sequence has an email step but no contact has an email.                    |
| `RECIPIENTS_NO_LINKEDIN`                                                          | The sequence has a LinkedIn step but no contact has a LinkedIn profile URL.    |

### Other codes

| Code                                                 | Meaning                                                               |
| ---------------------------------------------------- | --------------------------------------------------------------------- |
| `INVALID_REQUEST`                                    | The body or the query failed validation. The message names the field. |
| `INVALID_PARAMETERS`                                 | Invalid parameters.                                                   |
| `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`     | `plannedStart` is not a valid date, or is in the past.                |
| `EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_MISSING` | No mailbox, or no LinkedIn account, to act with.                      |
| `TRACKING_DOMAINS_REQUIRED`                          | Every email provider needs a tracking domain for this campaign.       |
| `WARMUP_PROVIDER_UNSUPPORTED`                        | Warmup cannot be enabled on this provider.                            |
| `ACCOUNT_DISCONNECTED`                               | The LinkedIn account is disconnected (`409`).                         |
| `RATE_LIMITED`                                       | LinkedIn slows the account down (`429`).                              |
| `STATS_COMPUTING`                                    | Statistics are being computed (`503`).                                |

## Retrying safely

* Retry `500` and `503` after a pause (60 seconds for the quota, the `Retry-After` value otherwise), a few times at most.
* Do not retry a `4xx` unchanged: the answer will be the same.
* Reads (`GET`) are safe to retry.
* Adding a contact to a list is safe to retry when the contact has an email or a LinkedIn URL: the duplicate is detected. Without either, every retry creates a new contact.
* Never retry a request that started a paid job (Email Finder, Phone Finder, Email Verifier) without checking the job first: you would pay twice.
