Resolve or create a marketing contact
Resolves the one contact a set of identity keys names, CREATING it when none matches. This is how a person enters the marketing spine at all, so it is the first call in any list migration or CDP sync. IT IS A WRITE DESPITE READING LIKE A LOOKUP, and it costs write_marketing_contacts for that reason. It does NOT record consent. A contact created here starts at never on all three channels, which is not permission to mail; POST /api/v1/marketing-contacts//consent is the second call, and the one that carries the disclosure the person agreed to.
Authorizations
A secret API key. Publishable keys cannot reach this API. A key may carry an expiry, and an expired key is refused exactly like an unknown one, with a 401 that names no reason; check the key's expires_at in the dashboard rather than inferring it from a response. When a merchant rolls a key's secret they choose a grace window of up to 3 days, and for its duration BOTH the new secret and the one it replaced authenticate, so an integration moves over on its own deploy schedule instead of at the instant the button is pressed. Move before the window closes: after it, the old secret is refused. Nothing else about this contract moves with a roll. The key keeps its id and its scopes, so the only thing an integration updates is the credential itself.
Headers
A unique key per logical write. Replaying a request with the same key returns the first response byte for byte instead of applying the write twice.
Body
THIS IS THE CREATE. There is no separate one. The keys are searched strongest first (customer_id, then email, then phone_e164, then anonymous_id) and the first match wins; when nothing matches, a contact is CREATED. A pipeline pushing a malformed identity therefore does not error, it manufactures a person, so send keys you have verified. AT LEAST ONE KEY IS REQUIRED. first_name and last_name name nobody, so a body carrying only those is 400 no_identity. IDENTITY KEYS ARE WRITE-ONCE. A resolve that MATCHES an existing contact fills in a missing first or last name and changes nothing else: it will not attach an email to a contact keyed by a customer id and it will not re-point a key that is already set. That is the control that stops one caller merging two people's profiles or moving a subscribed address onto a row it does not own; correcting a key is a dashboard job, where a human can see both records. 200 ON EVERY CALL, never 201. Whether this matched or created is not something the response can honestly report: two concurrent resolves of the same address race, one inserts and the other re-selects, and both did the same thing from your side. Idempotent by identity is the property to depend on.
The storefront customer this person signs in as: the strongest key, searched first. Unique per store.
A browser identifier for someone not yet known by name: the weakest key, searched last. Unique per store.
Stored lower-cased and unique per store, so casing never splits one person into two contacts. Must be a single valid address.
E.164 with a LEADING + and a country code, for example +14155550123. A national number is refused rather than guessed at a country: guessing wrong routes a message to a different person.
Filled in on a match only where it is currently unset; an existing name is never overwritten.
Same rule as first_name.
Response
Success
