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.
Worked example: consent to send marketing email
1. Create the purpose. In the console, Purposes & statements → New purpose:
| Field | Value | Why |
|---|---|---|
| Key | marketing_email | What you pass to the API. Lowercase, snake_case, never changes. |
| Category | Marketing | Groups it in the banner and in reporting. |
| Legal basis | Consent | For marketing email under GDPR this is consent, not legitimate interest. |
| Required | off | The 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 this | Choose | Effect |
|---|---|---|
| Fixed a typo, softened a sentence, added a link | Display-only | Existing 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-consent | Existing consent is treated as stale. reconsent.required fires; needsReconsent reads true until the person answers the new version. |
| Translated it into a new language | Display-only | You’re adding text, not changing the deal. |
Add a language
- Open the purpose → Draft text → + locale.
- Enter the locale code (
es,fr-CA, …), the translated title and body. - 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.