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 │
│ │
│ GET /users/{id}/ │
│ documents/ │
│ outstanding │
│ │
│ POST /users/{id}/ │
│ documents/ │
│ consents │
└─────────────────────────┘
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 |
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 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",
"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. -
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.
Batch operations
The POST /documents/consents endpoint accepts up to 50 localizations per request. Always batch multiple consents in a single call rather than making individual requests per document.
The endpoint is fully transactional. If any localizationId in the batch is invalid (not found, or its version is not ACTIVE), the entire batch is rejected and no consents are recorded. Fix the invalid entry and resubmit.
Idempotency
The POST /documents/consents endpoint supports 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).
-
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 /outstanding:
-
Max retries: 3
-
Initial delay: 100ms
-
Backoff factor: 2× (100ms -> 200ms -> 400ms)
-
Max delay: 5 seconds
Recommended retry configuration for POST /consents:
-
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 document definition's
defaultLocale. No error is returned. -
The
localeSourcefield in the 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
GET /outstandingagain 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 configuration is cached at two levels (in-memory per pod and shared Redis). The cache is automatically invalidated when documents 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
POST /consentscall. -
Cache outstanding results: Optionally cache the
GET /outstandingresponse per user for 5–15 minutes. Invalidate the cache after a successfulPOST /consents.
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 |