Record a consent change for a contact
Appends a consent event and moves the contact’s state for that channel, in one transaction, so the proof and the state can never diverge. THE LEDGER CANNOT BE EDITED. Nothing on this API or in the dashboard can rewrite or remove a row, by design: that immutability is what makes it worth producing to a regulator. Correct a mistake by recording the event that is true now. A KEY HOLDING write_marketing_contacts CAN RECORD AN OPT-IN, which is the direction that costs the merchant rather than the shopper. Ask for this grant only where your system genuinely captured the consent. The response is the contact, so you can read whether the person may now be mailed.
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.
Path Parameters
Body
THIS WRITES EVIDENCE, NOT A SETTING. The row lands in an append-only ledger this platform holds no update or delete permission on, so a wrong record stands forever and can only be contradicted by a later one, and that ledger is what a merchant produces to show a subscribe was real. Send the action that is true. THE ACTION DECIDES THE STATE, which is why there is no consent field to write: subscribe, confirm and import all land on subscribed, unsubscribe lands on unsubscribed, and pending lands on pending. An event and the state it produced can never disagree. THE BODY IS TWO FIELDS AND THE SERVER OWNS THE REST OF THE ROW. source, text_shown, ip, ua and occurred_at are each refused BY NAME with a 400, not ignored, because all five describe the person who acted and this request was made by your server rather than by them. Your address and http client describe your integration; the disclosure text describes a page an api write showed nobody. The server stamps source as public_api and text_shown as its own sentence saying so, and that is the whole point of the ledger: an auditor has to be able to separate a row a recipient produced by clicking a link in their own inbox from a row a merchant's integration wrote on their behalf, because only the first is evidence the recipient consented. Consent captured from a real shopper on the storefront keeps its own source, text, address and device. The response is the CONTACT, so you can read the resulting state, not the event you just sent.
Which channel this decision is about. Consent is per channel: an email unsubscribe says nothing about sms.
email, sms, push What happened. confirm is the completion of a double opt-in, so send it only when you hold the evidence the person clicked a link in their own inbox; import is the right action when migrating a list from another system, and it records the subscription as inherited rather than freshly given. There is no default, because every value here changes who may be mailed.
subscribe, unsubscribe, confirm, pending, import Response
Success
