Upsert a contact by external id

Search Documentation

Search across all developer documentation

contacts

Upsert a contact by external id

PUT /v1/contacts/by-external-id/{external_id}
Auth required api.contacts.manage

Idempotent upsert of a contact keyed by a stable, customer-supplied external id — the canonical "sync my system's truth into SendOps" call. The external id is a permanent dedup key (unlike the 24-hour Idempotency-Key window): re-running the same request always converges to the same contact.

Resolution order: (1) if a contact already carries this external_id it is updated in place — including changing its email; (2) otherwise, if the supplied email already exists it is adopted (the external id is stamped onto it), unless that email already belongs to a different external id, which returns 409 conflict; (3) otherwise a new contact is created (email is required).

attributes and topic_preferences are the desired set. unsubscribe_all is omitted-means-untouched (a null/absent value never changes the current opt-out). lists, when present, replaces static-List membership exactly (an empty array clears all); when absent, membership is unchanged. Returns 201 on creation and 200 on update.

Also supports the Idempotency-Key header for transport-level retry de-duplication, independent of the permanent external-id key.

Path parameters

external_id string required

The caller's stable identifier for this contact. Opaque, unique per org.

Request body

Content type: application/json

{
  "email": "user@example.com",
  "attributes": {},
  "unsubscribe_all": true,
  "topic_preferences": [
    {
      "topic_name": "string",
      "subscription_status": "OPT_IN"
    }
  ],
  "lists": [
    "00000000-0000-0000-0000-000000000000"
  ]
}

Responses

200 Contact updated application/json
201 Contact created application/json
401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
409 The mutation is rejected by a state rule rather than a bad request — e.g. editing a git-backed (read-only) definition. The code is conflict. application/problem+json
422 Query parameter or path value failed validation application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json