Partially update a contact

Search Documentation

Search across all developer documentation

contacts

Partially update a contact

PATCH /v1/contacts/{email}
Auth required api.contacts.manage

Partial diff-merge: only the fields present in the body are applied.

  • attributes — keys in the map are set or cleared (a null value removes the key, and the next read no longer returns it); keys absent from the map are left untouched.
  • topic_preferences — only the listed topics are updated; others keep their existing preference.
  • unsubscribe_all — when present, sets the master opt-out flag.
  • lists — add/remove apply a delta; set, when present, replaces membership declaratively.

Returns 404 not_found when the contact does not exist or has been deleted. Supports the Idempotency-Key header.

Path parameters

email string<email> required

Recipient address. Percent-encode @ as %40. Matched case-insensitively.

Query parameters

strict boolean optional

Opt out of attribute auto-registration. With strict=true an attribute name your org has never used is 422 attribute_validation_failed and nothing is registered — the behaviour every /v1 contact write had before this parameter existed. Absent or false registers the name as a string attribute and reports it in attributes_registered. A value that is not a boolean is 422 validation_failed, rather than being read as false: a caller who sent strict=yes believing they had opted in, and then had an attribute registered anyway, would have been told nothing.

Request body

Content type: application/json

attributes object optional

Keys in the map are set or cleared (a null value removes the key); keys absent from the map are left untouched. An attribute name your org has never used is REGISTERED as a string attribute and named in the response's attributes_registered. Send strict=true to opt out: an unknown key is then 422 attribute_validation_failed and nothing is registered. Regardless of strict: a value that does not fit an EXISTING attribute's declared type, a name that is not a valid registry identifier, a reserved name, and a direct write to a derived (formula) attribute are all 422 attribute_validation_failed with a per-attribute errors[] array.

unsubscribe_all boolean | null optional

When present, sets the master opt-out flag.

topic_preferences array<object> | null optional

When present, only the listed topics are updated; others keep their preference.

Each entry in topic_preferences:

topic_name string required

The topic's name (see /v1/topics).

subscription_status string enum required

One of: OPT_IN, OPT_OUT

lists object optional

Inline static-List membership directive carried on POST and PATCH contact writes. add/remove apply a delta; set, when present, replaces membership declaratively (an empty set clears all memberships). Every entry is a List key (the org-unique handle, e.g. beta-cohort) or the List's id — the same pair /v1/lists/{id} resolves, so one identifier works across the whole API. Use the key: it is immutable, readable in a config file, and what GET /v1/lists shows beside each List. An entry that names no static List in your org is a 422 validation_failed raised before anything is written; a Segment's key is not accepted here.

Fields of lists:

add array<string> optional

List keys (or ids) to add the contact to.

remove array<string> optional

List keys (or ids) to remove the contact from.

set array<string> optional

When present, replaces the contact's static-List membership exactly. An empty array clears all memberships. Mutually exclusive with the add/remove delta.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 Contact updated application/json
email string<email> required
unsubscribe_all boolean required

Master opt-out — when true the contact receives no topic email.

attributes object required

Custom attribute map as stored in SES. Always an object, never null.

topic_preferences array<object> required

Each entry in topic_preferences:

topic_name string required

The topic's name (see /v1/topics).

subscription_status string enum required

One of: OPT_IN, OPT_OUT

lists array<string<uuid>> required

Ids of the static Lists the contact currently belongs to. Always an array. Ids, not keys — GET /v1/lists maps one to the other, and either form is accepted on the way in.

attributes_registered array<string> required

Attribute names this call added to your org's attribute registry, each registered as a string attribute. Always present; [] when the call registered nothing, and always [] when the request set strict=true. Reported rather than silent because registering a definition changes your audience schema: the name becomes an attr.<name> a segment can filter on, and a typo becomes a permanent attribute nobody asked for. Rename an unwanted one with PUT /v1/attributes/{id}.

created_at string<date-time> required
updated_at string<date-time> required
ses_sync object optional

Present ONLY while the contact has not reached the customer's SES account yet; absent means synced. A 2xx on a contact write means SendOps has accepted and durably stored the change, not that SES has it — writes are mirrored afterwards, paced against the AWS account-wide budget of one contact request per second. Everything user-visible (segments, consent filtering, exports, sends) reads SendOps' own copy, so a pending mirror holds nothing back.

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
404 Resource not found application/problem+json
422 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. 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
503 An upstream dependency — the AWS SES account connected to this org — refused, throttled, or did not answer in time. The code is upstream_unavailable; the status is 503 when the call failed and 504 when it exceeded our deadline. Retryable. Always accompanied by Retry-After. On a 504 the outcome is genuinely unknown (the write may have reached SES), so retries of a non-idempotent call should carry an Idempotency-Key. The detail string is deliberately generic: the underlying AWS error describes the customer's own account and is recorded in SendOps logs against the request_id, not returned here. application/problem+json
504 An upstream dependency — the AWS SES account connected to this org — refused, throttled, or did not answer in time. The code is upstream_unavailable; the status is 503 when the call failed and 504 when it exceeded our deadline. Retryable. Always accompanied by Retry-After. On a 504 the outcome is genuinely unknown (the write may have reached SES), so retries of a non-idempotent call should carry an Idempotency-Key. The detail string is deliberately generic: the underlying AWS error describes the customer's own account and is recorded in SendOps logs against the request_id, not returned here. application/problem+json