Create or update contacts by e-mail
const url = 'https://api.getsitecontrol.com/v1/sites/1/contacts/upsert';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"items":[{"email":"example","status":"subscribed","first_name":"example","last_name":"example","country":"example","phone":"example","timezone":"example","language":"example","fields":{"additionalProperty":"example"},"add_tags":["example"],"remove_tags":["example"]}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.getsitecontrol.com/v1/sites/1/contacts/upsert \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "email": "example", "status": "subscribed", "first_name": "example", "last_name": "example", "country": "example", "phone": "example", "timezone": "example", "language": "example", "fields": { "additionalProperty": "example" }, "add_tags": [ "example" ], "remove_tags": [ "example" ] } ] }'Creates or updates contacts by e-mail, up to 100 per call: each item finds
the contact by email, without case, and updates it, or creates it when the
address is free. Call it to add a list of people or to sync many contacts
from another system without looking each one up first. One contact with the
contact back in the answer is upsert_contact.
The answer reports every item in the order given: created, updated,
restored (the address belonged to a deleted contact, which is back with
its old data and the item applied over it), unchanged or failed with a
reason; one failed item writes nothing of its own and does not stop the
others.
A field left out is kept; null clears first_name, last_name, phone,
country, timezone and language; fields are keyed by field ids from
list_contact_fields, merged by key, and null removes a value. Tags go by
name, compared without case: add_tags adds them and creates a tag the site
does not have, remove_tags takes them off; tags not named stay.
changed names the fields that were written.
Status: subscribed is allowed from unsubscribed, and subscribes again a
contact who opted out, as the admin does; leave status out to keep what the
contact chose. unsubscribed is allowed from subscribed or from awaiting
double opt-in. A bounced contact changes status only with a new e-mail, and a
contact awaiting double opt-in is never subscribed here. A status change that
is not allowed does not stop the rest: the other fields are written and
reason says why.
A new contact is subscribed unless status says unsubscribed, which stores
it as never subscribed; it cannot be created bounced. Its source is api,
with the API key as id, or the user when the call is made with a user’s
access token. A contact created here starts “new contact” automations when
subscribed and emits contact.created; a restored one does neither. Its
e-mail is checked in the background. A found contact keeps the stored case
of its e-mail and its source.
Items of one contact, here, in upsert_contact, update_contacts and
delete_contacts, run one after another, so concurrent calls keep each
other’s tags and field keys.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Header Parameters
Section titled “Header Parameters”Makes a retry of a POST safe: a repeat with the same key, body and GSC-Version returns the stored response with Idempotency-Replayed: true instead of running again. The key protects against repeated execution while the key store works. Saving the change and storing the response under the key are not atomic: if the process or the key store fails between them, the change is kept, the key is released after 15 minutes and a retry runs the request again. After a timeout or a 5xx, read the resource to check the result before retrying. 5xx and 429 responses and responses over 256 KB are not stored, so a repeat runs the request again. A key is kept for 24 hours. The same key with another body or version is 422; a repeat while the first request runs is 409.
Makes a retry of a POST safe: a repeat with the same key, body and GSC-Version returns the stored response with Idempotency-Replayed: true instead of running again. The key protects against repeated execution while the key store works. Saving the change and storing the response under the key are not atomic: if the process or the key store fails between them, the change is kept, the key is released after 15 minutes and a retry runs the request again. After a timeout or a 5xx, read the resource to check the result before retrying. 5xx and 429 responses and responses over 256 KB are not stored, so a repeat runs the request again. A key is kept for 24 hours. The same key with another body or version is 422; a repeat while the first request runs is 409.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Up to 100 contacts, each e-mail once
The fields every contact write takes; an upsert finds by email.
object
Finds the contact, without case; kept as stored when found, stored in lower case when created
subscribed or unsubscribed; bounced is set only by e-mail delivery. Left out: unchanged, a new contact is subscribed; may not be null
Null clears it
Null clears it
ISO 3166-1 alpha-2 code or an English country name; stored as the code; null clears it
In the international format, or national with country in the same request; stored normalized; null clears it
IANA time zone name such as Europe/Berlin; null clears it
ISO 639-1 code such as de; stored in lower case; null clears it
Values by field id from list_contact_fields: a string for text, a whole number for integer, a number, an ISO 8601 string for a datetime (UTC without an offset), YYYY-MM-DD for a date, true or false for a boolean. Fields of the other type are read-only. Merged into the stored values: null removes a value, keys not given stay as they are
object
Tag names to add, compared without case; a tag the site does not have is created. Tags the contact has stay
Tag names to remove, compared without case; a name with no tag, or a tag the contact does not have, is no change. Never creates a tag
Responses
Section titled “Responses”object
Every item, in the order given
object
Position of the item in the request, from 0
The contact; a failed item of update_contacts or delete_contacts keeps the id it was given. Null when no contact was found
Created: a new contact, by upsert_contacts or upsert_contact.
updated: something was written, changed names it; in delete_contacts
the contact was deleted. restored: the address belonged to a deleted
contact, which is back with its old data and the item applied over it.
unchanged: nothing to change, nothing written; in delete_contacts the
contact was deleted before. failed: nothing of this item was written, see
reason.
Names of the fields whose stored value changed, from is_deleted, email, status, first_name, last_name, phone, country, timezone, language, fields, tags; empty when none did. A new contact names email, status and every field it was given; a restored one starts with is_deleted
Why it failed, or why the status sent was not applied; null otherwise
The contact after the call; null when it was not found
object
As stored
After the call
{ "items": [ { "result": "created", "reason": "contact_not_found", "contact": { "status": "subscribed" } } ]}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
True when the response is a stored one, replayed
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
Authentication scheme: Bearer
forbidden: the key or token lacks contacts.edit
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
idempotency_in_progress: a repeat of a request still running
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
validation_failed: the body is invalid: more than 100 items, an id or an e-mail repeated, a tag both added and removed. idempotency_key_reused: the Idempotency-Key was sent before with another body or version
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
rate_limited: a request limit is spent. Every call spends the contact creation limit once, whatever the number of items
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds
Seconds until the bucket refills
temporarily_unavailable: the request took too long, or limits or idempotency keys cannot be enforced; retry later
The documented shape of every error body on the versioned surface.
object
object
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example", "reason": "example", "next": "example", "request_id": "example", "errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ], "metadata": {}}Headers
Section titled “Headers”API version the response was produced for
Request id, echoed from the request when valid
Limit of the tightest bucket
Requests left in its window
End of its window, epoch seconds