Contacts
Import contacts
Upsert up to 500 contacts by email. For existing contacts, attributes are merged and first_name, last_name and status are updated only when the row sets them, so an unsubscribed contact stays unsubscribed. New contacts default to subscribed.
POST
https://api.sndr.sh/api/cranl/v1/contacts/bulkImport contacts
curl -X POST 'https://api.sndr.sh/api/cranl/v1/contacts/bulk' \
-H 'Authorization: Bearer sndr_your_access_token' \
-H 'Content-Type: application/json' \
-d '{
"contacts": [
{ "email": "alice@example.com", "first_name": "Alice", "attributes": { "tier": "gold" } },
{ "email": "bob@example.com", "first_name": "Bob" }
],
"source": "import",
"group_ids": ["grp_4d8a0c5e-1f7"]
}'200OK
{
"inserted": 1,
"updated": 1,
"skipped": 0
}Authorization
AuthorizationstringheaderrequiredBearer followed by an access token. Needs the send scope.
Body
application/jsoncontactsobject[]requiredContacts as {email, first_name?, last_name?, status?, attributes?}, 1–500 per request.
sourcestringSource label for newly created contacts. Defaults to manual.
group_idsstring[]Groups (grp_...) to add every contact in the batch to.
Errors
401
unauthenticatedMissing or invalid Bearer token.401
token_expiredAccess token expired — exchange the refresh token for a new pair.401
token_revokedToken was explicitly revoked.400
invalid_requestBody is not valid JSON, has an unknown field, or a field has the wrong type.400
chunk_too_largeMore than 500 contacts in one request. Split the import.403
insufficient_scopeToken is missing the send scope.422
validation_failedcontacts is empty, or a row has a missing or invalid email or an unknown status (fields names each row, e.g. row.<index>.email). Nothing is saved.