Integrate consent management
This page explains how to integrate Consent Management v2 into your application's login and sign-up flows using the Runtime API.
Architecture
The Runtime API is designed for backend-to-backend communication. It must never be called directly from a browser or mobile app, because it uses OAuth2 client credentials that must remain private.
┌──────────────┐ ┌─────────────────────┐ ┌───────────────────────────┐
│ End user │ ───── │ Your frontend │ ───── │ Your backend │
│ (browser / │ │ (React, Vue, etc.) │ │ (Node, Java, .NET, etc.) │
│ mobile) │ └─────────────────────┘ └───────────────────────────┘
└──────────────┘ │
│ OAuth2 S2S / M2M token
▼
┌─────────────────────────┐
│ Consent Management v2 │
│ Runtime API │
│ │
│ Document consent: │
│ GET /users/{id}/ │
│ documents/ │
│ outstanding │
│ POST /users/{id}/ │
│ documents/ │
│ consents │
│ │
│ Attribute consent: │
│ GET /users/{id}/ │
│ purposes/posture │
│ POST /users/{id}/ │
│ purposes/consents │
│ POST /users/{id}/ │
│ purposes/consents │
│ /revoke │
└─────────────────────────┘
Authentication
The Runtime API supports OAuth2 tokens.
Required OAuth2 scopes:
| Endpoint | Required scope |
|---|---|
GET /documents/outstanding |
consent-management:documents:outstanding |
POST /documents/consents |
consent-management:consents:post |
GET /purposes/posture |
consent-management:purposes:posture:get |
POST /purposes/consents |
consent-management:purposes:consents:post |
POST /purposes/consents/revoke |
consent-management:purposes:consents:post |
Document consent integration
Compliance determination logic
When the Runtime API evaluates whether a user has an outstanding consent, it follows these rules:
| Scenario | Compliant? | Reason |
|---|---|---|
The user consented to the current ACTIVE version in the requested language. |
Yes | Exact match |
The user consented to any localization of the current ACTIVE version (different language). |
Yes | Version-level compliance is language-agnostic. |
The user consented to a DERIVED localization that traces back to the same root as the ACTIVE version. |
Yes | Same legal content |
The user consented to an ARCHIVED version whose lineage chain reaches the same root as the ACTIVE version. |
Yes | Historical consent remains valid if legally equivalent. |
| The user never consented to this document. | No | No consent record exists. |
| The user's consent traces to a different root (legal content changed). | No | Different legal content requires re-consent. |
The active SUNSET version grace period is in force. |
No, but non-blocking | Outstanding, but the user can still access it during the grace period |
The requested locale is not available in the ACTIVE version. |
No (fall back to defaultLocale) |
The version does not include a localization for that locale. |
Login flow with consent check
The following steps describe a typical login flow that incorporates document consent enforcement.
Step 1: Authenticate the user
Your application authenticates the user as usual (validate credentials, issue a session token, and so on).
Step 2: Check for outstanding consents
After authentication, your backend calls the Runtime API to check whether the user has outstanding documents:
GET /consent-management/api/v1/users/{userId}/documents/outstanding
Authorization: Bearer {your-token}
Optional query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
locale |
string | Document's defaultLocale |
The user's preferred language is treated as a hint. If the active version does not have a localization for this locale, the defaultLocale is used. |
mandatoryOnly |
boolean | false |
Return only mandatory documents. |
Example response: Two outstanding documents:
{
"userId": "user-123",
"totalOutstanding": 2,
"mandatoryCount": 1,
"optionalCount": 1,
"documents": [
{
"definitionId": "DD-...",
"definitionName": "Privacy Policy",
"type": "PRIVACY_POLICY",
"isMandatory": true,
"defaultLocale": "en_US",
"versionId": "DV-...",
"versionNumber": 3,
"versionName": "Q1 2025 Update",
"versionStatus": "ACTIVE",
"effectiveDate": "2025-01-15T00:00:00Z",
"localizationId": "DL-...",
"locale": "en_US",
"localeSource": "REQUESTED",
"title": "Privacy Policy",
"localizationDescription": "English version of the Privacy Policy for EU customers",
"externalUrl": "https://example.com/legal/privacy-en-v3"
},
{
"definitionId": "DD-...",
"definitionName": "Marketing Communications",
"type": "MARKETING_PERMISSION",
"isMandatory": false,
"defaultLocale": "en_US",
"versionId": "DV-...",
"versionNumber": 1,
"versionName": "2024 Version",
"versionStatus": "ACTIVE",
"effectiveDate": "2024-01-01T00:00:00Z",
"localizationId": "DL-...",
"locale": "en_US",
"localeSource": "REQUESTED",
"title": "Marketing Communications",
"externalUrl": "https://example.com/legal/marketing-en-v1"
}
]
}
Step 3: Evaluate the response
Your backend decides whether to block or allow login based on the response:
| Condition | Action |
|---|---|
mandatoryCount > 0 and no grace period |
Block access. Return the list of documents to the frontend so that the user can consent. |
mandatoryCount > 0 and previousVersionOnGracePeriod is present |
Allow access, but present a non-blocking prompt showing the grace period end date. |
optionalCount > 0 only |
Allow access. Optionally present optional consents for the user to accept at their convenience. |
totalOutstanding == 0 |
Allow access immediately. No consent is required. |
Step 4: Present the consent UI (if needed)
Your frontend displays the outstanding documents to the user. For each document, show:
-
The
titleof the document. -
The
localizationDescription, if present — an optional summary description of the localization that can be displayed as a subtitle or tooltip to give users more context before reading the full document. -
A link to the
externalUrlso that the user can read the full content. -
Whether the document is required (
isMandatory). -
For grace periods: the date in
previousVersionOnGracePeriod.endsOnby which the user must accept.
The user must explicitly check or select each document. Do not pre-check consent check boxes on behalf of the user.
Step 5: Register consents
When the user accepts, your backend sends the accepted localizationId values in a single batch call:
POST /consent-management/api/v1/users/{userId}/documents/consents
Authorization: Bearer {your-token}
Content-Type: application/json
Idempotency-Key: {unique-uuid-per-user-action}
{
"consents": [
{ "localizationId": "DL-..." },
{ "localizationId": "DL-..." }
]
}
The localizationId values come directly from the documents[].localizationId field in the outstanding response.
Successful response (201 Created):
{
"userId": "user-123",
"totalConsents": 2,
"results": [
{
"id": "c1c1c1c1-...",
"document": {
"definitionName": "Privacy Policy",
"type": "PRIVACY_POLICY",
"locale": "en_US",
"title": "Privacy Policy"
},
"consentStatus": "GRANTED",
"createdDate": "2025-03-26T10:30:00Z"
}
]
}
Step 6: Complete login
After consents are registered, allow the user to proceed to the application.
Sign-up flow
The sign-up flow works the same way as the login flow. For a brand new user who has no consent history, the outstanding API returns all ACTIVE documents as outstanding. The user must consent to all mandatory documents before account activation is complete.
Grace periods
When the system returns a document with a previousVersionOnGracePeriod field, the user previously consented to an older version that is now in SUNSET status. The user can still access the service, but should be prompted to accept the new version.
The previousVersionOnGracePeriod.endsOn date indicates when the grace period expires. After that date, the document becomes blocking.
Recommended user experience during a grace period:
-
Show a non-blocking banner or notification: "Please review and accept the updated Privacy Policy by [date]."
-
Provide an Accept now button and a Remind me later option.
-
Escalate the urgency as the deadline approaches.
-
Do not block access during the grace period.
Attribute consent integration
Unlike document consent (which is gate-based), attribute consent is preference-based — it represents what a user allows your application to do with their personal data. Attribute consent is always optional. Use it for marketing communications, analytics, personalization, and similar use cases.
Consent posture
The posture describes a user's current consent state across all active processing purposes for the tenant. Use it to populate a preference center or to check whether a user has consented to specific purposes before processing their data.
GET /consent-management/api/v1/users/{userId}/purposes/posture
Authorization: Bearer {your-token}
The response is paginated and returns one entry per active purpose definition.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
locale |
string | Purpose's defaultLocale |
The preferred language for localization content. |
pageSize |
integer | 20 | Number of results per page (max 100). |
pageNumber |
integer | 0 | Zero-based page number. |
sortBy |
string | definitionName |
Field to sort by. |
sortDir |
string | asc |
Sort direction: asc or desc. |
Each entry in the response content array describes the user's posture for one purpose:
| Field | Description |
|---|---|
definitionId |
The purpose definition ID |
definitionName |
The purpose name |
legalBasis |
The GDPR Art. 6 legal basis |
consentStatus |
The user's current status: GRANTED, REVOKED, or NEVER_CONSENTED |
versionId |
The ID of the currently active version |
versionName |
The name of the currently active version |
localizationId |
The localization ID for the resolved locale |
locale |
The resolved locale |
title |
The localized purpose title |
legalText |
The localized legal text |
dataScope |
The identity attributes linked to this purpose |
versionPreferences |
The channel preferences configured on the purpose version |
userPreferences |
The channel preferences the user has currently consented to |
Register attribute consents
When a user grants consent to one or more processing purposes (for example, in a preference center):
POST /consent-management/api/v1/users/{userId}/purposes/consents
Authorization: Bearer {your-token}
Content-Type: application/json
Idempotency-Key: {unique-uuid-per-user-action}
{
"consents": [
{
"localizationId": "PL-...",
"preferences": {
"channels": [
{
"channelType": "EMAIL",
"frequencies": ["WEEKLY"]
}
]
}
},
{
"localizationId": "PL-..."
}
]
}
- Each item in
consentsrequires alocalizationIdfrom anACTIVEpurpose version. - The optional
preferences.channelsarray lets the user specify which communication channels and frequencies they consent to. The channels and frequencies must be a subset of those configured on the purpose version. - Up to 50 consents can be registered in a single batch. The batch is atomic — if any item is invalid, the entire batch is rejected.
Successful response (201 Created):
{
"userId": "user-123",
"totalConsents": 2,
"results": [
{
"id": "c1c1c1c1-...",
"purpose": {
"definitionId": "PD-...",
"definitionName": "Email Marketing",
"legalBasis": "CONSENT",
"locale": "en_US",
"title": "Email Marketing Consent"
},
"consentStatus": "GRANTED",
"createdDate": "2025-03-26T10:30:00Z"
}
]
}
Revoke attribute consents
When a user withdraws consent for one or more processing purposes:
POST /consent-management/api/v1/users/{userId}/purposes/consents/revoke
Authorization: Bearer {your-token}
Content-Type: application/json
Idempotency-Key: {unique-uuid-per-user-action}
{
"revocations": [
{ "definitionId": "PD-..." },
{ "definitionId": "PD-..." }
]
}
- Each revocation identifies a purpose definition by
definitionId. The system revokes the user's consent for the currently active version of that purpose. - Up to 50 revocations can be submitted in a single batch. The batch is atomic.
Successful response (200 OK):
{
"userId": "user-123",
"totalRevocations": 1,
"results": [
{
"id": "r1r1r1r1-...",
"purpose": {
"definitionId": "PD-...",
"definitionName": "Email Marketing"
},
"consentStatus": "REVOKED",
"createdDate": "2025-03-26T11:00:00Z"
}
]
}
Batch operations
Both document and attribute consent registration endpoints accept up to 50 items per request. Always batch multiple consents in a single call rather than making individual requests per item.
Both endpoints are fully transactional. If any item in the batch is invalid, the entire batch is rejected and no consents are recorded. Fix the invalid entry and resubmit.
Idempotency
The POST /documents/consents, POST /purposes/consents, and POST /purposes/consents/revoke endpoints support idempotent retries via the Idempotency-Key header. This prevents duplicate consent records if a network failure occurs after the server processes the request but before the client receives the response.
How it works:
-
Generate a unique UUID for each consent action (per user click of Accept or Revoke).
-
Send the same UUID in
Idempotency-Keyfor any retries of the same request. -
Within 24 hours, the server returns the original response without creating new ledger entries.
When to use the same key:
-
Network timeout (no response received): Retry with the same key.
-
5xx server error: Retry with the same key.
When to generate a new key:
-
400 validation error: Fix the request data and use a new key.
-
User clicks Accept again on a new session: Use a new key.
Error handling and retries
Retryable errors (use the same Idempotency-Key):
| HTTP status | Reason | Action |
|---|---|---|
| Network timeout | Unknown if server processed the request | Retry with same key |
500 Internal Server Error |
Server-side failure | Retry with same key |
503 Service Unavailable |
Temporary overload | Retry with exponential backoff |
429 Too Many Requests |
Rate limiting | Retry after the indicated delay |
Non-retryable errors (fix the request, use a new key):
| HTTP status | Reason | Action |
|---|---|---|
400 Bad Request |
Invalid localization ID or non-ACTIVE version |
Inspect details array, fix the localizationId, use a new key |
401 Unauthorized |
Token expired or invalid | Refresh the OAuth2 token |
403 Forbidden |
Insufficient scope | Check OAuth2 scope configuration |
404 Not Found |
User or resource not found | Verify the userId |
Recommended retry configuration for GET endpoints:
-
Max retries: 3
-
Initial delay: 100ms
-
Backoff factor: 2× (100ms -> 200ms -> 400ms)
-
Max delay: 5 seconds
Recommended retry configuration for POST endpoints:
-
Max retries: 2
-
Initial delay: 200ms
-
Backoff factor: 2× (200ms -> 400ms)
-
Max delay: 5 seconds
Locale handling
-
Pass the user's preferred locale in the
localequery parameter. -
If the requested locale is not available in the active version's localizations, the API falls back silently to the definition's
defaultLocale. No error is returned. -
The
localeSourcefield in the outstanding/posture response indicates whether the locale was matched exactly (REQUESTED) or fell back to the default (DEFAULT). -
If the user changes their language preference mid-session, call the outstanding or posture endpoint again with the new locale. Re-consenting is not required unless the legal content has changed.
Performance considerations
The Runtime API is optimized for high-throughput login flows:
-
Caching: The tenant document and purpose configuration is cached at two levels (in-memory per pod and shared Redis). The cache is automatically invalidated when documents or purposes are modified. This reduces database load by over 99% and delivers responses in under five milliseconds for most requests.
-
Batch: Always register multiple consents in a single
POSTcall. -
Cache posture/outstanding results: Optionally cache the
GET /outstandingorGET /postureresponse per user for 5–15 minutes. Invalidate the cache after a successfulPOST /consentsorPOST /consents/revoke.
API endpoints reference
For full request and response schemas, see the Runtime API reference.
All paths are relative to /consent-management.
| Operation | Method | Path |
|---|---|---|
| Get outstanding documents | GET |
/api/v1/users/{userId}/documents/outstanding |
| Register document consents | POST |
/api/v1/users/{userId}/documents/consents |
| Get user purpose consent posture | GET |
/api/v1/users/{userId}/purposes/posture |
| Register attribute consents | POST |
/api/v1/users/{userId}/purposes/consents |
| Revoke attribute consents | POST |
/api/v1/users/{userId}/purposes/consents/revoke |