Error Codes
There are two kinds of errors: request-level errors, which stop the entire request before any contact is processed, and per-contact errors, which describe one contact's outcome. How a per-contact error surfaces depends on which endpoint you called — see Create or Update a Contact or Sync a Contact Batch for the full explanation.
Request-level errors
These prevent the whole request from being processed at all — nothing gets written.
| HTTP status | Code | Meaning | Applies to |
|---|---|---|---|
400 | INVALID_REQUEST | An array was sent to /contacts, a bare object was sent to /contacts/batch, the array is empty, or the body is otherwise invalid | All endpoints |
400 | BATCH_LIMIT_EXCEEDED | More than 50 contacts were sent in one request | /contacts/batch only |
401 | INVALID_API_KEY | The API key is missing, invalid, expired, or revoked | All endpoints |
413 | PAYLOAD_TOO_LARGE | The request body is larger than 1 MB | All endpoints |
429 | RATE_LIMIT_EXCEEDED | You've exceeded 300 requests/minute for your organization (shared across every endpoint) — see Rate Limits | All endpoints |
500 | INTERNAL_ERROR | Something went wrong on our end | All endpoints |
A request-level error looks like this:
{
"request_id": "req_01J7X8W3E4N8",
"error": {
"code": "INVALID_API_KEY",
"message": "The supplied API key is invalid"
}
}
Per-contact errors
The same codes apply on both endpoints, but they surface differently — see the HTTP status on /contacts column, which only means something on the single-contact endpoint:
| Code | Meaning | HTTP status on /contacts |
|---|---|---|
INVALID_EMAIL | The email address isn't valid, even after trimming and lowercasing | 400 |
DUPLICATE_EMAIL | The contact already exists and overwrite wasn't set to true | 409 |
SUPPRESSED_EMAIL | The contact is suppressed (bounced/complained/unsubscribed) and overwrite wasn't set to true | 409 |
EMPTY_LIST_IDS | No list ID was provided | 400 |
TOO_MANY_LIST_IDS | More than 20 list IDs were provided | 400 |
INVALID_LIST | One or more list IDs don't exist, are archived, or belong to a different organization — the response includes which ones under invalidListIds | 400 |
UNKNOWN_FIELD | A key in fields doesn't match any standard field or existing custom field — check for typos, or define the field first in Settings → Custom Fields | 400 |
FIELD_TYPE_MISMATCH | A value doesn't match its field's configured type (e.g. text sent for a number field) | 400 |
REQUIRED_FIELD_MISSING | A required custom field has no value and no configured default — or you tried to clear one with null, which is never allowed | 400 |
DUPLICATE_IN_REQUEST | The same normalized email appeared more than once in the same batch. Duplicate rows aren't merged automatically — send each email only once per request | n/a — only possible on /contacts/batch, since it requires two rows |
On POST /api/v2/contacts/batch, a per-contact error appears inside results, alongside any contacts that succeeded, under an overall 200:
{
"email": "not-an-email",
"status": "failed",
"code": "INVALID_EMAIL",
"message": "Enter a valid email address"
}
On POST /api/v2/contacts, the same shape is the entire response body — no results wrapper — and the HTTP status matches the table above:
{
"request_id": "req_...",
"email": "alex@example.com",
"status": "failed",
"code": "DUPLICATE_EMAIL",
"message": "A customer with this email already exists"
}
INVALID_LIST also includes the specific offending IDs, on either endpoint:
{
"email": "alex@example.com",
"status": "failed",
"code": "INVALID_LIST",
"message": "One or more lists were not found in this organization",
"invalidListIds": ["000000000000000000000000"]
}
What's next
- Create or Update a Contact — single-contact endpoint
- Sync a Contact Batch — batch endpoint
- Rate Limits & Retries