Skip to main content
Contacts API

Sync a contact batch

v2

Send up to 50 contacts in one request. This endpoint always returns 200 for a syntactically valid request — even if every contact in it fails — because a batch's outcomes can't collapse into one status code. Only sending one contact? Use the single-contact endpoint instead, which returns a real per-outcome status.

POSThttps://api.scorehq.co/api/v2/contacts/batch

Send your secret key in the x-api-key header. Never call this endpoint directly from a browser. Authentication details

Authenticationx-api-key
Rate limit300 / 60 sec (shared)
Contacts per request1–50
Body limit1 MB JSON
01

Request body

An array of 1–50 contact objects — sending a bare object here returns 400 INVALID_REQUEST.

emailstringrequired
Contact email address, per contact. Score trims whitespace and converts it to lowercase before matching.
list_idsstring[]required
Between 1 and 20 unique list IDs. Every list must exist, be active, and belong to your organization.
overwriteboolean
Defaults to false. Set it to true to merge fields into an existing contact. See Overwrite behavior.
fieldsobject
Standard contact fields and existing custom fields. Omitted fields are left unchanged.

Standard and custom fields

These keys inside fields map directly to built-in contact properties.

first_name · stringlast_name · stringphone · stringcity · stringcountry · string

Every other key is a custom field and must already exist under Settings → Custom Fields. Unknown names fail that row with UNKNOWN_FIELD; the API never creates definitions. Custom fields support text, number, date, and boolean. Dates accept 2026-08-31 or a full timestamp such as 2026-08-31T14:30:00Z, with or without milliseconds.

Clearing fields

Leave unchanged

Omit the key from fields.

Clear an optional field

Send null.

Clear a text field

Send "" (an empty string).

Clear a required custom field

Not allowed; that row returns REQUIRED_FIELD_MISSING.

Custom fields are merged into the stored set, never replaced wholesale. Sending one custom field never removes the others.

One email per request

A normalized email may appear only once per batch. A repeated email fails both of its rows with DUPLICATE_IN_REQUEST — duplicate rows are never merged automatically.

02

Overwrite behavior

How existence, status, and overwrite determine each contact's own result.

New contacttrue or false

Created and added to every list in list_ids. Row status created.

Active contactfalse

Nothing changes. Row status failed, code DUPLICATE_EMAIL.

Active contacttrue

Sent fields are merged and new list memberships are added. Row status updated or unchanged.

Suppressed contactfalse

Nothing changes. Row status failed, code SUPPRESSED_EMAIL.

Suppressed contacttrue

Profile fields update, but no lists or journeys are added. Row status updated or unchanged.

Suppressed contacts stay protected

Even with overwrite: true, this endpoint never re-subscribes a bounced, complained, or unsubscribed contact for any row. It does not add them to a list or enroll them in a journey. Re-enabling email must be done deliberately through suppression management in the dashboard.

Adding an active existing contact to a new list can enroll them in a published journey with a matching list-entry trigger, just as a dashboard or CSV import can.

03

Response

Always 200 for a syntactically valid request — read results[] to see what actually happened.

Each entry in results has a status of created, updated, unchanged, or failed, in the same order and count as the contacts you sent. A failed row includes a machine-readable code and a human-readable message; the rest of the batch is unaffected by any one row's failure.

success = created + updated + unchanged

Do not treat HTTP 200 alone as "everything succeeded" — check failed and the individual row statuses. The example panel above shows both an all-success and a partial-failure batch.

04

Errors

Request-level errors stop the whole batch; per-contact errors fail just one row.

Request-level errors

Nothing in the batch is written when one of these occurs.

400INVALID_REQUEST

A bare object was sent to this endpoint — wrap it in an array, or use the single-contact endpoint for one contact.

400BATCH_LIMIT_EXCEEDED

The request contains more than 50 contacts.

401INVALID_API_KEY

The API key is missing, invalid, expired, or revoked.

413PAYLOAD_TOO_LARGE

The decoded JSON request body is larger than 1 MB.

429RATE_LIMIT_EXCEEDED

The organization exceeded 300 requests in 60 seconds — shared with the single-contact endpoint.

500INTERNAL_ERROR

Score could not complete the request.

Request-level error shape
{
"request_id": "req_01J7X8W3E4N8",
"error": {
"code": "INVALID_API_KEY",
"message": "The supplied API key is invalid"
}
}

Per-contact errors

Each of these fails one row inside results[] — the overall response stays 200, and the rest of the batch still processes.

INVALID_EMAIL

The email isn't valid after trimming and lowercasing.

DUPLICATE_EMAIL

The contact exists and overwrite was not set to true.

SUPPRESSED_EMAIL

The contact is suppressed and overwrite was not set to true.

EMPTY_LIST_IDS

No list ID was provided.

TOO_MANY_LIST_IDS

More than 20 list IDs were provided.

INVALID_LIST

A list doesn't exist, is archived, or belongs to another organization.

UNKNOWN_FIELD

A fields key doesn't match a standard or existing custom field.

FIELD_TYPE_MISMATCH

A value doesn't match its field's configured type.

REQUIRED_FIELD_MISSING

A required custom field has no value or was cleared with null.

DUPLICATE_IN_REQUEST

The same normalized email appears more than once in this batch. Duplicate rows are not merged automatically — send each email only once per request.

INVALID_LIST row detail
{
"email": "alex@example.com",
"status": "failed",
"code": "INVALID_LIST",
"message": "One or more lists were not found in this organization",
"invalidListIds": ["000000000000000000000000"]
}