Docs

Consent statements & versioning

Create a purpose, write its text, publish it, and change it later without losing proof.

A purpose is one thing a person can say yes or no to — “send me marketing email”, “measure how I use the site”. It has a stable key you use in the API, a category, and a legal basis. Its wording — the statement the person actually reads — is versioned separately, so you can prove exactly what was shown on any date.

1. Create the purpose. In the console, Purposes & statements → New purpose:

FieldValueWhy
Keymarketing_emailWhat you pass to the API. Lowercase, snake_case, never changes.
CategoryMarketingGroups it in the banner and in reporting.
Legal basisConsentFor marketing email under GDPR this is consent, not legitimate interest.
RequiredoffThe person can use your product without opting in.

2. Write the statement. On the draft, add the text a person will see — one entry per language:

Title:  Marketing emails
Body:   We'll email you product news, tips and the occasional offer.
        You can unsubscribe at any time.
Link:   https://acme.com/privacy#marketing   (optional — your policy)

3. Publish it. Pick Display-only change (this is the first version, nobody has consented yet). The wording is now frozen as v1.

4. Record consent against it from your app:

curl -X POST $API/v1/consent \
  -H "authorization: Bearer sk_live_…" \
  -H "idempotency-key: $(uuidgen)" \
  -d '{"subject":"email:sam@example.com","purpose":"marketing_email",
       "state":"GRANTED","locale":"en","channel":"signup_form"}'

The vault stores the decision with v1’s exact text hash, the IP, the timestamp and a signed receipt. If a regulator later asks “what did Sam agree to on this date?”, you can show the precise words.

Why there is always a draft

Publishing freezes the current draft as an immutable version and opens a fresh draft with the same text copied in. So a purpose always has: one live version (what GET /v1/purposes serves and what new consent cites) and one draft you can edit freely. The console shows “up to date” when the draft matches the live version — i.e. there is nothing new to publish.

Changing the text later: which kind of change is it?

When you publish a new version you choose what it means for people who already answered:

You did thisChooseEffect
Fixed a typo, softened a sentence, added a linkDisplay-onlyExisting consent stays valid. The new wording just shows from now on.
Added a new use of the data (e.g. “and share with partners”)Requires re-consentExisting consent is treated as stale. reconsent.required fires; needsReconsent reads true until the person answers the new version.
Translated it into a new languageDisplay-onlyYou’re adding text, not changing the deal.
When in doubt, ask: would a reasonable person feel they’d agreed to something different? If yes, choose re-consent.

Add a language

  1. Open the purpose → Draft text+ locale.
  2. Enter the locale code (es, fr-CA, …), the translated title and body.
  3. Publish as display-only.

Your banner then requests GET /v1/purposes?locale=es and renders the Spanish text. If a locale isn’t translated yet, the API falls back to the site’s default locale.

Version tags

Optionally tag a version with a release number (v1.0, 2.3) or a date (2024-05-01). It’s shown next to the version everywhere and is included in GET /v1/purposes. Useful when you’re importing an existing statement history from another system and want to keep your original numbering.

Enforce locale (optional)

Turn this on for a purpose and every consent write for it must send a locale that the published version has text for — otherwise the API returns 422. Use it when you legally need to record which language a person was shown. Off by default; locale is then just stored as metadata.

GET /v1/purposes/{key}/current serves only the published version, never your draft. A banner can never accidentally show unpublished wording.