# iOS SDK Quickstart (/docs/consent-management/mobile-consent/ios-headless-consent/ios-sdk-quickstart)

The iOS Headless SDK provides a headless consent management API for iOS apps. The module is imported as `TranscendHeadless`, and all API calls will go through the imported `TranscendAPI`.

**Requirements: iOS 15.0+, Swift 5.9+**

### Installation [#installation]

#### CocoaPods [#cocoapods]

Add to your \`Podfile\`:

```markdown
pod 'TranscendHeadless'
```

Then run \`pod install\`.

#### Swift Package Manager [#swift-package-manager]

In XCode, import your package using this URL:

[https://github.com/transcend-io/consent-manager-headless-ios-library-spm](https://github.com/transcend-io/consent-manager-headless-ios-library-spm)

and then `import TranscendHeadless` in your code.

### Initialization [#initialization]

Create a `TranscendConfig` and call `TranscendAPI.initialize(config:)`. Register a ready callback \*\*before\*\* calling initialize, and it will fire once the SDK has loaded settings and (optionally) completed its initial sync.

```swift
import TranscendHeadless

let config = try TranscendConfig(
    bundleId: "YOUR_BUNDLE_ID", // Required — Transcend bundle ID
    mobileAppId: "com.example.myapp", // Required — your app's mobile identifier pulled from https://app.transcend.io/consent-manager/native/applications?pageSize=10
    isTest: false, // Whether to use the test or production deployment
    cdnUrl: "https://transcend-cdn.com", // Optional — defaults to Transcend's CDN
    token: "AUTH_TOKEN", // Optional — auth token for backend sync. Configured using your preference store signing and encryption keys https://docs.transcend.io/docs/articles/preference-management/storing-consent-preferences#generate-a-user-token-on-your-backend-server
    settingsOverrides: [ // Optional — override remote settings at init
        "country": "EU",
        "regime": "GDPR"
    ]
)
TranscendAPI.ready { event in
    if case .ready(let success, let error) = event {
        if success {
            // SDK is ready — safe to call consent APIs
        } else {
            print("Init failed:", error?.localizedDescription ?? "unknown")
        }
    }
}
try TranscendAPI.initialize(config: config)
```

#### Async initialization [#async-initialization]

Use `TranscendAPI.asyncInit(config:)` when you prefer non-throwing initialization. Errors are delivered via the `ready` callback instead of being thrown.

```swift
TranscendAPI.asyncInit(config: config)
```

#### Checking readiness [#checking-readiness]

You can check for readiness in one of two ways: registering a variable or function that is then mutated or invoked on `.ready` callback success, or through polling. Ready callback registration is shown in the Initialization section, and polling can be with a periodic interval check calling the following function:

```swift
TranscendAPI.isReady() // returns Bool
```

Note: most API methods require the SDK to be ready and will throw `TranscendError.notReady` otherwise.

#### Re-initialization [#re-initialization]

If your initialization has encountered an error and needs re-initialization, you can tear down the API with .destroy() and re-initialize. This should not be necessary during typical app usage, but may be helpful for debugging purposes.

```swift
try TranscendAPI.destroy()
```

### Listening to Events [#listening-to-events]

Register listeners with `addEventListener(type:callback:)`. Each call returns a `ListenerHandle` — store it and pass it to `removeEventListener(_:)` when done.

```swift
let consentHandle = TranscendAPI.addEventListener(type: .CONSENT_CHANGE) { event in
    if case .consentChange(let oldConsent, let newConsent, let changes) = event {
        print("Consent changed:", newConsent.purposes)
    }
}

let syncHandle = TranscendAPI.addEventListener(type: .SYNC) { event in
    if case .sync(let oldConsent, let newConsent, _) = event {
        print("Sync completed")
    }
}

// When no longer needed:
TranscendAPI.removeEventListener(consentHandle)
TranscendAPI.removeEventListener(syncHandle)
```

#### Event types [#event-types]

| Listener Type     | `ConsentEvent` type      | When it fires                                             |
| ----------------- | ------------------------ | --------------------------------------------------------- |
| `.CONSENT_CHANGE` | `.consentChange`         | Local consent state changes (e.g. after `setConsent`).    |
| `.SYNC`           | `.sync`                  | Backend sync completes and consent may have been updated. |
| \*(via `ready`)\* | `.ready(success:error:)` | SDK initialization succeeds or fails.                     |

### Getting and Setting Consent [#getting-and-setting-consent]

#### Get consent [#get-consent]

```swift
let consent = try TranscendAPI.getConsent()
// consent.purposes       — [String: PurposeValue] (.True / .False / .Auto)
// consent.confirmed      — Bool
// consent.prompted         — Bool
// consent.timestamp        — ISO 8601 string
// consent.booleanPurposes() — [String: Bool] convenience helper, coalesces .Auto to true
```

#### Set consent [#set-consent]

Pass a map of purpose names to boolean values. Syncs to the backend by default.

```swift
let success = try TranscendAPI.setConsent(
    purposes: [
        "Analytics": true,
        "Functional": true,
        "Advertising": false
    ],
    autoSync: true,       // Sync to backend after setting (default: true)
    confirmed: true,      // Mark consent as confirmed (default: true)
    timestamp: nil,       // ISO timestamp; defaults to now
    updated: true,        // Mark consent as updated (default: true)
    metadata: .unchanged, // ConsentMetadata; see ConsentMetadata enum
    metadataTimestamp: nil
)
```

When `autoSync` is true, the SDK pushes consent to the backend using the `token` from `TranscendConfig`. Listen for `.SYNC` events to react to remote updates.

### Registering Overrides [#registering-overrides]

If you need to test specific regimes, turn on/off sync, or force on any specific configuration values, the `TranscendConfig` object allows you to pass those through for a runtime override.

Pass overrides in `TranscendConfig.settingsOverrides`:

```swift
let config = try TranscendConfig(
    bundleId: "YOUR_BUNDLE_ID",
    mobileAppId: "com.example.myapp",
    settingsOverrides: [
        "country": "DE",
        "countryRegion": "EU",
        "regime": "GDPR"
    ]
)
```

Some common override keys include: `"partition"`, `"country"`, `"countryRegion"`, `"regime"`, `"defaultRegime"`.

### Syncing Consent [#syncing-consent]

\*\*Backend sync requires an auth token.\*\* Pass `token` when creating `TranscendConfig` at init — without it, sync is skipped and consent is not pushed to or pulled from the backend. You can generate an auth token by following the instructions [here](../../../preference-management/storing-consent-preferences.mdx#generate-a-user-token-on-your-backend-server).

```swift
let config = try TranscendConfig(
    bundleId: "YOUR_BUNDLE_ID",
    mobileAppId: "com.example.myapp",
    token: "AUTH_TOKEN"  // Required for sync
)
```

&#xA;The SDK syncs consent with the Transcend backend automatically in two cases:

1. During initialization, using the `token` from the `TranscendConfig`.
2. After `setConsent`, when `autoSync` is true (the default), using the same TranscendConfig `token` we initialized with.

There is no separate public `sync()` method. To trigger a sync, call `setConsent` with `autoSync: true` and ensure a valid `token` was provided at init.

To listen for sync completion:

```swift
TranscendAPI.addEventListener(type: .SYNC) { event in
    if case .sync(_, let newConsent, _) = event {
        // Backend sync finished; newConsent reflects any remote changes
    }
}
```

Sync is also skipped when `SYNC_DISABLED` is true in the loaded settings, or explicitly disabled using that field in an override.

### SDK Consent Status [#sdk-consent-status]

Use these APIs to determine whether a third-party SDK (identified by a service ID) is allowed to run given the current consent state.

#### Single service [#single-service]

```swift
// Async/await
let status = try await TranscendAPI.getSdkConsentStatus(
    serviceId: "analytics_sdk",
    cacheTTL: nil  // Optional cache TTL in milliseconds
)

// Callback-based
TranscendAPI.getSdkConsentStatus(serviceId: "analytics_sdk") { result in
    switch result {
    case .success(let status):
        switch status {
        case .ALLOW:       /* SDK may run */
        case .BLOCK:       /* User denied required purpose(s) */
        case .NO_SDK_FOUND: /* Service ID not in purpose map */
        case .INTERNAL_ERROR:
            break
        }
    case .failure(let error):
        print(error.localizedDescription)
    }
}
```

#### Batch lookup [#batch-lookup]

Pass specific service IDs, or \`nil\`/empty to evaluate all known SDKs:

```swift
// Specific IDs
let statuses = try await TranscendAPI.batchGetSDKConsentStatuses(
    serviceIds: ["analytics_sdk", "ads_sdk"],
    cacheTTL: nil
)
// Returns [String: ConsentStatus], e.g. ["analytics_sdk": .ALLOW, "ads_sdk": .BLOCK]

// All SDKs in the purpose map
let allStatuses = try await TranscendAPI.batchGetSDKConsentStatuses(
    serviceIds: nil,
    cacheTTL: nil
)
```

Callback variant:

```swift
TranscendAPI.batchGetSDKConsentStatuses(serviceIds: ["analytics_sdk"]) { result in
    if case .success(let map) = result {
        for (serviceId, status) in map {
            print("\(serviceId): \(status)")
        }
    }
}
```

#### `ConsentStatus` values [#consentstatus-values]

| Status            | Meaning                                                                                          |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| `.ALLOW`          | All required purposes for this SDK are granted (or SDK is Essential).                            |
| `.BLOCK`          | At least one required purpose was explicitly denied.                                             |
| `.NO_SDK_FOUND`   | The service ID is not in the SDK purpose map.                                                    |
| `.INTERNAL_ERROR` | Returned if consent resolution encountered an error while reconciling the service ID's purposes. |

The SDK purpose map is loaded from settings and cached. Pass \`cacheTTL\` (milliseconds) to control how long the cache is reused before re-reading from settings.

### Additional APIs [#additional-apis]

```swift
// Active privacy regimes (e.g. "GDPR")
let regimes = try TranscendAPI.getRegimes()

// Purposes applicable under the current regime
let purposes = try TranscendAPI.getRegimePurposes()

// Purpose display metadata (name, description)
let details = try await TranscendAPI.getPurposeDetails()

// Raw loaded settings
let settings = try await TranscendAPI.getCurrentSettings()
```

### Error Handling [#error-handling]

All throwing APIs can produce \`TranscendError\`:

| Error                           | Cause                                                   |
| ------------------------------- | ------------------------------------------------------- |
| `.notInitialized`               | \`initialize\` has not been called.                     |
| `.notReady`                     | SDK is still loading; wait for the `ready` callback.    |
| `.alreadyInitialized`           | `initialize` called more than once without `destroy()`. |
| `.invalidConfiguration(String)` | Invalid config (e.g. empty `bundleId`).                 |
| `.initFailed(String)`           | Settings fetch or initialization failed.                |
