Create or update contact
POST/api/v1/contacts/upsert
Creates the contact if the address is new; otherwise it is updated. The usual call for an integration.
Responds 201 when the contact was created and 200 when it was updated. Only the fields you send are changed. add_tags/remove_tags change tags without touching the others.
consent_source is required when the contact is created. The address is sent in the body, never in the URL, so that it does not end up in logs.
List membership is not changed here: sign up to a list with POST /lists/{id}/members.
Scope
contacts:writeEdit contacts – the person behind the key must be editor or higher.
Body
JSON, Content-Type: application/json. Unknown fields return 422.
emailstringrequired- The email address.at most 254 characters
first_namestring | null- First name.at most 100 characters
last_namestring | null- Last name.at most 100 characters
companystring | null- Company.at most 200 characters
phonestring | null- Phone number. Without a country code, it is interpreted according to the workspace's country.at most 50 characters
fieldsobject<string | number | boolean>- Custom fields by key (GET /fields). The values are checked against the field's type. null or "" removes a value. Keys that are not fields in the workspace are saved as free-form fields. Sensitive fields (personal identity numbers) cannot be set via the API.
tagsarray<string>- Replaces all tags. Do not use together with add_tags/remove_tags.at most 50
add_tagsarray<string>- Tags to add; the others are kept.at most 50
remove_tagsarray<string>- Tags to remove.at most 50
consent_sourcestring- How the person consented. Required when the contact is created; changes nothing on an existing contact.at least 3 characters · at most 255 characters
Retrying a request
Feel free to send the Idempotency-Key header (e.g. a UUID). If the same request is sent again with the same key within 24 hours, the first response is returned, without anything being done twice. Batch and idempotency
Response
200 The contact (201 if it was created).
idstringrequired- The contact's ID.
emailstringrequired- The email address (lower case). Never changes: consents and suppressions apply to the address.
first_namestring | nullrequiredlast_namestring | nullrequiredcompanystring | nullrequiredphonestring | nullrequired- In international format (E.164), e.g. +46701234567.
statusstringrequired- active: can receive mailings · unsubscribed: has unsubscribed from everything · bounced: the address bounces · complained: has reported spam · deleted: erased (GDPR), the personal data is gone.
activeunsubscribedbouncedcomplaineddeleted fieldsobject<string | number | boolean>required- Custom fields by key (see GET /fields). Sensitive fields, such as personal identity numbers, are never sent out.
tagsarray<string>requiredsourcestringrequired- How the contact was added: manual, import, form, api …
consentobjectrequiredconsent.atstring (time, ISO 8601) | nullrequired- When consent was given.
consent.sourcestring | nullrequired- How the person consented, in your own words.
suppressionobject | nullrequired- The workspace's suppression of the address. A suppressed address never receives mailings, regardless of status.
suppression.reasonstringrequired- unsubscribed, bounced, complained, manual or imported.
suppression.created_atstring (time, ISO 8601)required- When the address was suppressed.
sharedbooleanrequired- Shared from a level above in the organisation: can be read and receive mailings from here, but can only be changed where it belongs.
created_atstring (time, ISO 8601)required- When the contact was created.
updated_atstring (time, ISO 8601)required- When the contact was last changed.
unsubscribed_atstring (time, ISO 8601) | nullrequired- When the person unsubscribed.
listsarray<object>required- The workspace's lists that the contact is on. Empty for a shared contact.
lists[].list_idstringrequiredlists[].namestringrequiredlists[].statusstringrequired- subscribed: on the list · pending: waiting to confirm by email · unsubscribed: has left the list.
subscribedpendingunsubscribed lists[].subscribed_atstring (time, ISO 8601) | nullrequired- When the person joined the list.
Errors
- 400
- bad_request
- 429
- rate_limited