> ## Documentation Index
> Fetch the complete documentation index at: https://developer.onetrust.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Age-Based Consent

This page provides context on how to implement an age-based consent solution for the OneTrust iOS/tvOS SDK.

# Overview

Age-Based Consent in the iOS and tvOS SDKs enables automatic consent enforcement based on a user’s age range.<br />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 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

| Property     | Description          |
| ------------ | -------------------- |
| `lowerBound` | Optional minimum age |
| `upperBound` | Optional maximum age |

```swift
lowerBound: Int?  // Optional (omit if 0 or not applicable)
upperBound: Int?  // Optional (omit for open-ended ranges)
```

**Sample code**

```swift
ProfileAgeRange(lowerBound: 13, upperBound: 17)   // 13–17
ProfileAgeRange(lowerBound: 18, upperBound: nil)  // 18+
ProfileAgeRange(lowerBound: nil, upperBound: 12)  // 0–12
```

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

### 2. Delegate: `getProfileAgeRange`

Implement this optional delegate in your appropriate delegate/interface.

The SDK calls it automatically:

* During `startSDK`
* When `checkAndLogConsent` is called

```swift
func getProfileAgeRange(
    for profileID: String,
    completion: @escaping (ProfileAgeRange?) -> Void
)
```

**Parameters**

* `profileID` - identifies the current user/profile session
* `completion` - 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)

**Sample code**

```swift
 class MyAppConfigurator: CustomConfigurator {

            func getProfileAgeRange(
                for profileID: String,
                completion: @escaping (ProfileAgeRange?) -> Void
            ) {
                // Fetch age range from your user profile system.
                // This can be synchronous or asynchronous.

                guard let user = UserService.getUser(profileID) else {
                    completion(nil) // Age unknown
                    return
                }

                // User is between 13 and 17
                completion(ProfileAgeRange(lowerBound: 13, upperBound: 17))

                // OR: User is 18 or older (open-ended)
                 completion(ProfileAgeRange(lowerBound: 18, upperBound: nil))

                // OR: Age not available
                 completion(nil)
            }
        }
```

### 3. Setting the Delegate/Provider

Register your age range provider before calling startSDK:

```swift
//Set the CustomConfigurator delegate
OTPublishersHeadlessSDK.shared.customConfigurator = MyAppConfigurator()
OTPublishersHeadlessSDK.shared.startSDK(...)
```

> 📘
>
> `getProfileAgeRange` is part of the CustomConfigurator protocol which also handles proxy URLs.

## 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

```swift
AppPermissionType.ageGate.checkAndLogConsent { success in
  // Completed
}
```

When invoked, the SDK performs the following actions:

1. Calls the `getProfileAgeRange` delegate 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
* In flows similar to ATT/IDFA updates where consent state may change

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

### `getConsentStatus`&#x20;

Returns the consent status for a purpose group.

```swift
let status = OTPublishersHeadlessSDK.shared.getConsentStatus(forCategory: "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`&#x20;

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

```swift
let status = OTPublishersHeadlessSDK.shared.getPurposeConsentLocal(forCustomGroupId: "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`&#x20;

Updates the consent value for a purpose group programmatically.

```swift
OTPublishersHeadlessSDK.shared.updatePurposeConsent(forGroup: "C0005", consentValue: 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

### `getAgeGatePromptValue`&#x20;

> This API and the associated UI are not impacted by the Age-Based Consent feature and will continue to function independently of age range–based enforcement.

Returns the user’s interaction state with the existing Age Gate prompt.

```swift
let value = OTPublishersHeadlessSDK.shared.getAgeGatePromptValue()
```

**Returns:**

* 1 = user selected Yes
* 0 = user selected No
* -1 = user has not interacted with the prompt

***

# Behavior Changes

### Preference Center

**Restricted Users**<br />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 to`0`**&#x20;(denied)**
* The user cannot provide consent for those purposes

**Non-Restricted Users**<br />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**<br />A user is considered restricted if their age range share&#x73;**&#x20;**&#x61;ny intersectio&#x6E;**&#x20;**&#x77;ith a restricted age group. Partial overlap also results in restriction.

**Logic:**

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

**Examples:<br />**<br />Restricted age group: 0–15:<br />

| User Age Range | Result         | Reason                              |
| -------------- | -------------- | ----------------------------------- |
| \[0,18]        | Restricted     | Possible overlap (e.g., age 14)     |
| \[14,16]       | Restricted     | Overlaps with 14-15                 |
| \[10,12]       | Restricted     | Fully within restricted range       |
| \[18,25]       | Not Restricted | No overlap                          |
| \[18, null]    | Not Restricted | Open-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-Based Consent 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:&#x20;**&#x41;pplies 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&#x20;**&#x61;uthoritative
  * The CMP API server **will** **override&#x20;**&#x63;onsent to 0 (denied)**&#x20;**&#x64;uring the next sync
* The toggle remain&#x73;**&#x20;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 remai&#x6E;**&#x20;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) session, the SDK transfers consent using the `SYNC_PROFILE` interaction.<br /><br />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 valu&#x65;**&#x20;**&#x69;s 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:&#x20;**&#x54;he 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
* &#x20;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-Based Consent restricts linked purposes
2. User logs in as <john@example.com>
3. SDK calls `handleAuthenticatedConsent` → `SYNC_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`("<john@example.com>") → app provides age range → SDK stores and logs

**Recommendation**<br />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 in the OneTrust admin console and links purpose groups/categories to them.

2. App sets the `CustomConfigurator` 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**<br />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., `Apple DeclaredAgeRange`). 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**
  iOS / tvOS and Android (more platforms coming).

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

<br />