Errors
The shape of an Emelia API error, what each HTTP status means today, the stable error codes, and how to retry safely.
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:
{
"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:
{ "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. |
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. |
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/...) answer401when the campaign is not found, and401 {"error": "You must be logged in"}on any internal error. A401there does not always mean a bad key: check the campaign id first. GET /webhookanswers401 {"error": "You must be logged in"}when reading the webhooks fails.GET /credits/usageanswers{"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"}or422 {"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:
{ "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.
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
500and503after a pause (60 seconds for the quota, theRetry-Aftervalue otherwise), a few times at most. - Do not retry a
4xxunchanged: 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.