Partially update a contact
Partial diff-merge: only the fields present in the body are applied.
attributes— keys in the map are set or cleared (anullvalue 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/removeapply 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.
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.
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 code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json 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 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