Sync a site's contacts
POST/contacts/sync
A connected site's customers, up to 500 per call. It is what the WordPress plugin calls
to bring in the site's users, customers and leads — and any custom platform holding a
connected site's API key can call it too. The answer is 200 even when rows fail: each
row's outcome comes in results, in the order sent.
POST /contacts/batch is a spreadsheet uploaded once. This route is the opposite: a
system reporting the same people again and again, so its rules only add, never destroy:
- a contact is found first by who they are on the site (
ref: WordPress user 42), then by phone. Someone who changed their number is still the same contact; if the new number already belongs to another contact, nothing is merged and the row comes back withphone_conflict; - with no phone and no link to the site, nobody is created (
no_phone,invalid_phone); - tags only come in, by name, and missing ones are created. To take one off, use
removeTags; - names, e-mail and custom fields only fill what is empty: what someone typed in the
dashboard wins over what the site reports. The fields the site computes itself
(WooCommerce's
wc_…) overwrite — unlessoccurredAtis older than the last one applied for that link (stale_snapshot); - an opt-out given on the site always applies. Consent only moves from
unknowntoopted_in, with evidence, and never over an opt-out (kept_opt_out) or a suppression (kept_suppression); - a value for a field the account does not have, or of the wrong type, is dropped and
listed in
warnings— the rest of the row is applied; - new contacts count against the plan's cap (
contact_limit_reached).
With options.triggers set to false — the default, what a backfill sends — nothing
fires: no flow starts and no contact.created webhook goes out. A backfill of ten
thousand customers must not greet ten thousand people. Send true for what just
happened on the site.
Idempotency. With the Idempotency-Key header, a batch retried after a dropped
connection returns the first result, with Idempotent-Replayed: true, instead of being
applied twice. The same key with another body is refused with
422 idempotency_key_reused; while the first request is still running, the repeat gets
409 idempotency_in_progress.
Only a connected site's API key syncs — it comes with the contacts:sync permission. A
regular API key gets 404 site_not_found, and an API key limited to some numbers, 403.
Request
Responses
- 200
- 401
- 402
- 403
- 404
- 409
- 413
- 422
- 429
Outcome per row.
Missing, malformed, unknown or revoked token (authentication), or a suspended
account (tenant_inactive).
No entitlement (payment_required). The body carries the reason in reason and the
way out in action: what to do (kind) and the dashboard page where it is done (url).
A dashboard session instead of an API key (site_key_required), an API key without the contacts:sync permission (key_permission_denied), or one limited to some numbers (forbidden).
The API key is not bound to a connected site (site_not_found). Connect the site again from the plugin.
The site is paused in the dashboard (site_paused), or a request with the same Idempotency-Key is still running (idempotency_in_progress).
The body is over 1 MB (payload_too_large). Split the batch.
Invalid body — contacts missing, empty or over 500 rows (invalid_request, with issues) — or the same Idempotency-Key with another body (idempotency_key_reused). An invalid row does not refuse the batch: it comes back in results as failed.
Rate limit exceeded (rate_limit).
Response Headers
Seconds to wait before retrying.
Which rate limit category applied — send, media or default.
Which bucket the other headers describe — key (the API key), session (the dashboard) or account (the account ceiling).
Request ceiling for this category in the current window.
Requests left in the current window.