Age-Based Consent

Overview

Age-Based Consent in the Android and Android TV SDKs enables automatic consent enforcement based on a user’s age range.
Administrators configure age range groups in the OneTrust admin console and associate them with specific purposes or purpose groups. The SDK uses this configuration, along with the age range provided by your application, to enforce consent behavior at runtime.

When a user falls within a restricted age range:

  • Consent for associated purposes is automatically denied
  • The corresponding purpose toggles in the Preference Center are hidden

This ensures that age-based restrictions are consistently applied without requiring additional UI logic in the app.

How It Works

At a high level:

  • Your app provides the user's age range
  • The SDK sends this data to the OneTrust server
  • The server determines consent restrictions based on your settings in the admin console
  • The SDK enforces those restrictions in the UI and APIs

What's Changing

SDK Updates

  • New delegate method to provide the user's age range
  • New data model: ProfileAgeRange (lower and upper bounds)
  • Automatic consent restriction based on age overlap
  • Age range changes are logged to the server (AGEGATE_RANGE interaction)

Server (CMP API) Updates

  • Accepts and stores user age range data
  • Enforces restrictions by setting consentToggleStatus = -1 for restricted purposes
  • Maintains enforcement across devices for the same user profile

Implementation

1. Data Model: ProfileAgeRange

This represent a user's age range

PropertyDescription
lowerBoundOptional minimum age
upperBoundOptional maximum age
lowerBound: Int?   // -> Optional. Omit or pass null for "from 0".
upperBound: Int?   //  -> Optional. Omit or pass null for "no upper limit".

//NOTE: Android validates at init: bounds must be >= 0, and
      //lowerBound must be <= upperBound when both are set.

Sample Code

ProfileAgeRange(lowerBound = 13, upperBound = 17)  // 13-17
ProfileAgeRange(lowerBound = 18, upperBound = null) // 18+
ProfileAgeRange(lowerBound = null, upperBound = 12) // 0-12
📘

At least one bound is required. Both values cannot be nil.

2. Interface: getProfileAgeRange

Register this optional interface in your appropriate interface before or immediately after startSDK

The SDK calls it automatically:

  • During startSDK
  • When checkAndLogConsent is called

Sample code

val profileAgeRangeInterface = object : ProfileAgeRangeInterface {
    override fun getProfileAgeRange(
        profileId: String,
        callback: ProfileAgeRangeCallback,
    ) {
        if (profileId.equals("test1", ignoreCase = true)) {
            callback.completion(ProfileAgeRange(0, 4))
        } else if (profileId.equals("test2", ignoreCase = true)) {
            callback.completion(ProfileAgeRange(9, 10))
        } else {
            callback.completion(ProfileAgeRange(null))
        }
    }
}
 
ProfileAgeRangeInterface profileAgeRangeInterface = new ProfileAgeRangeInterface() {
    @Override
    public void getProfileAgeRange(@NotNull String profileId, @NotNull ProfileAgeRangeCallback callback) {
        if (profileId.equalsIgnoreCase("test1")) {
            callback.completion(new ProfileAgeRange(0, 4));
        } else if (profileId.equalsIgnoreCase("test2")) {
            callback.completion(new ProfileAgeRange(9, 10));
        } else {
            callback.completion(new ProfileAgeRange(null));
        }
    }
}; 

Parameters

  • profileId - identifies the current user/profile session
  • callback - Return a valid ProfileAgeRange, or nil if available

Notes

  • Supports async operations (network calls, database lookups, user prompts, etc.)
  • If not implemented or nil is returned:
    • No age range is set
    • The server may still enforce restrictions based on previously stored data (see Cross-Device Behavior)

3. Setting the Interface

Register your age range provider before calling startSDK:

// register via OTCustomConfigurator
OTCustomConfigurator.setupProfileAgeRangeManager(ageRangeProvider)
OTPublishersHeadlessSDK(context).startSDK(...)
OTCustomConfigurator.setupProfileAgeRangeManager(ageRangeProvider);
new OTPublishersHeadlessSDK(context).startSDK(...);

Public APIs

checkandLogConsent

Triggers an age range validation and logs consent if the value has changed.
Use this method when the user’s age range may have changed after SDK initialization, such as:

  • User profile updates
  • Account switching
  • User login / re-authentication
OTPublishersHeadlessSDK.AppPermissionType.AGE_GATE.checkAndLogConsent(object : OTCallback {
    override fun onSuccess(response: OTResponse) {
        // Age range has been checked and logged if changed.
    }
    override fun onFailure(response: OTResponse) {
        // Handle failure
    }
})
OTPublishersHeadlessSDK.AppPermissionType.AGE_GATE.checkAndLogConsent(
new OTCallback() { 
	@Override 
	public void onSuccess(@NonNull OTResponse response) { 
			// Age range has been checked and logged if changed 
	} 

	@Override 
	public void onFailure(@NonNull OTResponse response) { 
		// Handle failure
	} 
});

When invoked, the SDK performs the following steps:

  1. Calls the getProfileAgeRange interface to retrieve the latest age range
  2. Compares the value against the previously stored range
  3. If the value has changed:
    • Updates local storage
    • Logs the new value to the CMP API using the AGEGATE_RANGE interaction type
  4. If unchanged:
    • No action is taken

When to Call:

Call this API in any scenario where the user’s age range may be updated:

  • After profile updates that include age or date of birth changes
  • After account switching or user re-authentication
📘

Note: You do not need to call this method during startSDK. The SDK automatically performs this check during initialization.


getConsentStatus

Returns the consent status for a purpose group.

val status = OTPublishersHeadlessSDK(context).getConsentStatus("C0005")
int status = new OTPublishersHeadlessSDK(context).getConsentStatus("C0005");

Returns:

  • 1 = consent given
  • 0 = consent denied (including age-restrictions)
  • -1 = invalid/unknown purpose group ID

Age Gate Impact:

  • returns 0 automatically for any purpose linked to a restricted age range. No additional code is required

getPurposeConsentLocal

Returns the most up-to-date consent value available on the device, including values that may not yet be synced with the server.

val status = OTPublishersHeadlessSDK(context).getPurposeConsentLocal("C0005")
int status = new OTPublishersHeadlessSDK(context).getPurposeConsentLocal("C0005");

Age Gate Impact:

  • Returns 0 (denied) for any purpose associated with a restricted age group
  • Behavior is consistent with getConsentStatus for age-restricted purposes

updatePurposeConsent

Updates the consent value for a purpose group programmatically.

OTPublishersHeadlessSDK(context).updatePurposeConsent("C0005", true)
new OTPublishersHeadlessSDK(context).updatePurposeConsent("C0005", true);

With Age-Based Consent supported SDKs:

  • The SDK prevents enabling consent for age-restricted purposes
  • Any attempt to set consent to true is silently ignored

Without Age-Based Consent supported SDKs (older SDKs):

  • The update may succeed locally
  • The CMP API will override the value to 0 (denied) during the next sync

Behavior Changes

Preference Center

Restricted Users
If a user’s age range overlaps with a configured restricted age group:

  • The consent toggle for associated purposes is hidden (consentToggleStatus: -1)
  • Consent is automatically set to0 (denied)
  • The user cannot provide consent for those purposes

Non-Restricted Users
If the user’s age range does not overlap with any restricted age group:

  • All consent toggles remain visible and interactive
  • Standard consent behavior applies

Age Range Overlap Logic
A user is considered restricted if their age range shares any intersection with a restricted age group. Partial overlap also results in restriction.

Logic:

restricted = (user.lowerBound <= group.upperBound) && (user.upperBound >= group.lowerBound

Examples:

Restricted age group: 0–15:

User Age RangeResultReason
[0,18]RestrictedPossible overlap (e.g., age 14)
[14,16]RestrictedOverlaps with 14-15
[10,12]RestrictedFully within restricted range
[18,25]Not RestrictedNo overlap
[18, null]Not RestrictedOpen-ended (18+) does not intersect

Cross-Device Behavior (CRO Users)

For environments with Cross-Device Consent or Authenticated Consent enabled, age-based restrictions are enforced server-side and persist across devices.

How it Works

Once a user’s age range is logged for a profile from any device:

  • The CMP API server stores the age range
  • All subsequent consent responses for that profile enforce the same restrictions
  • Enforcement applies across:
    • Devices
    • Sessions
    • SDK versions

SDKs with Age Range Supported (Recommended)

This configuration provides complete enforcement and is strongly recommended.

  • The server returns consentToggleStatus: -1 for restricted purposes
  • The SDK:
    • Hides restricted toggles in the UI
    • Prevents consent from being enabled via public APIs (e.g., updatePurposeConsent)
  • Enforcement is applied at both:
    • UI level (toggle hidden)
    • API level (updates blocked)

SDKs without Age Range Supported (Older SDK Versions)

Note: Applies to CRO-enabled environments where server-side enforcement is active.

For SDKs that support CMP API but do not include Age-Based Consent logic:

  • The server continues to send consentToggleStatus: -1 for restricted purposes
  • The SDK:
    • Correctly hides toggles in the Preference Center (UI remains consistent)
    • Does not enforce age restrictions at the API level

Behavior Differences

  • updatePurposeConsent may still allow local updates for restricted purposes
  • However:
    • These updates are not authoritative
    • The CMP API server will override consent to 0 (denied) during the next sync
  • The toggle remains hidden in the UI regardless of local changes

Result

  • Users cannot bypass restrictions through the UI
  • Temporary local inconsistencies may occur if consent is updated programmatically
  • The server ensures the final persisted state is always compliant

Legacy SDKs without CMP API

Note: This section applies only to CRO (cross-device) scenarios where server-side enforcement is required.

This behavior applies to legacy SDKs that:

  • Use the legacy mobile-data API flow (pre-CMP API)
  • Include legacy Web SDKs
  • Include CTV SDKs not yet migrated to CMP API

Behavior Differences

  • These SDKs do not support consentToggleStatus: -1
  • As a result:
    • Consent toggles may remain visible in the UI
    • Users may be able to enable consent locally

However:

  • When consent is synchronized with the server:
    • The CMP API will override the value to 0 (denied) for restricted purposes
  • Any locally enabled state is not persisted

Result

  • Users may temporarily see consent toggled ON on the device
  • After the next server sync:
    • The toggle reflects the correct OFF (denied) state

Anonymous to Known User Consent Transfer

When a user transitions from an anonymous session to an authenticated (known) user, the SDK transfers consent using the SYNC_PROFILE interaction.

With Age-Based Consent enabled, this process includes additional safeguards to ensure age-based restrictions are correctly enforced.

Default Behavior (Without Age-Based Consent Enabled)

  1. The user provides consent during an anonymous session
  2. The user authenticates (logs in)
  3. The SDK invokes handleAuthenticatedConsent, sending a SYNC_PROFILE request to the CMP API
  4. The CMP API compares:
    • Anonymous consent
    • Stored preferences for the known profile
  5. The most recent consent value is applied for each purpose

Behavior with Age-Based Consent Enabled

When processing a SYNC_PROFILE request, the CMP API checks whether the known profile already has an associated age range.

If the known profile has an Age Range:

  • Consent for Age-Based Consent–linked purposes is not transferred, regardless of recency
  • The server preserves the existing age-restricted consent state
  • Consent for non-linked purposes continues to follow standard behavior (latest value wins)

If the known profile does not have an age range:

  • All consent transfers normally (latest value wins)
  • Age-based restrictions are not applied until an age range is provided
📘

Important: The anonymous user’s age range is not carried over to the known profile. This applies even if the anonymous session included age range data. This is because:

  • Age range is identity-specific and may not apply across users (e.g., shared devices)
  • The authenticated profile must provide its own age range
  • Enforcement relies on the age range returned via the getProfileAgeRange delegate

Implementation Guidance
To ensure proper enforcement:

  • Implement getProfileAgeRange for authenticated users (recommended)
  • Optionally implement it for anonymous users if age data is collected pre-login
  • When a user authenticates:
    • The SDK calls getProfileAgeRange with the new profile ID
    • Your app should return the correct age range for that user

If the age range differs from the previous session, the SDK:

  • Detects the change
  • Logs it using the AGEGATE_RANGE interaction

Example Flow

  1. Anonymous user (age 14–17) consents → Age Gate restricts linked purposes
  2. User logs in as [email protected]
  3. SDK calls handleAuthenticatedConsentSYNC_PROFILE request sent
  4. CMP API evaluates the known profile:
    • Age 14–17 → restrictions remain; consent for linked purposes is not transferred
    • Age 18–25 → no restriction; consent transfers normally
    • No age range → all consent transfers; restrictions applied only after age is provided
  5. SDK calls getProfileAgeRange("[email protected]") → app provides age range → SDK stores and logs

Recommendation
To ensure consistent and reliable behavior across devices:

  • Upgrade all applications to the latest CMP API-enabled SDK with Age-Based Consent supported

This enables:

  • Full UI enforcement (hidden toggles)
  • Full API enforcement (restricted updates blocked)
  • Elimination of temporary consent inconsistencies
  • Correct handling of anonymous-to-known user consent transfer

End-To-End Flow

  1. Admin configures restricted age groups on the OneTrust Console and links purpose groups/categories to them.

  2. App sets the OTCustomConfigurator delegate with the getProfileAgeRange implementation.

  3. App calls startSDK.

  4. SDK internally calls getProfileAgeRange to request the user's age range from the app.

  5. If the app provides a range:

    • SDK stores it locally.
    • SDK logs the age range to the CMP API server with an AGEGATE_RANGE consent interaction.
    • Server stores the age range for the profile and enforces restrictions in subsequent responses.
    • SDK hides toggles for restricted purposes in the Preference Center.
  6. If the app returns nil (age unknown):

    • SDK does not send an age range to the server.
    • The server checks if an age range already exists for this profile from a prior interaction or another device.
    • If found, the server still enforces age-based restrictions.
    • If no age range exists anywhere, no restrictions are applied.
  7. When the user's age changes (profile update, account switch):

    • App calls AppPermissionType.ageGate.checkAndLogConsent
    • SDK re-invokes the delegate, compares with stored range.
    • If changed, updates storage and logs to server.
    • Preference Center reflects the updated restriction state.

Important Notes

  • App must provide age range
    The SDK does not determine user age. Your app must supply it using internal data (e.g., profile, registration, Date of birth).

  • No system-level APIs
    The SDK does not use platform APIs (e.g., Android AccountManager). It requires the app user's age, not the device owner's.

  • Existing Age Gate UI unchanged
    showConsentUI and getAgeGatePromptValue() are unaffected and operate independently.

  • Supported platforms
    Android and Android TV (more platforms coming).

  • Consistent API naming
    APIs are aligned across platforms (e.g.,ProfileAgeRange, getProfileAgeRange, checkAndLogConsent, getConsentStatus, updatePurposeConsent).