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+
Add to your `Podfile`:
pod 'TranscendHeadless'Then run `pod install`.
In XCode, import your package using this URL:
https://github.com/transcend-io/consent-manager-headless-ios-library-spm
and then import TranscendHeadless in your code.
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.
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)Use TranscendAPI.asyncInit(config:) when you prefer non-throwing initialization. Errors are delivered via the ready callback instead of being thrown.
TranscendAPI.asyncInit(config: config)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:
TranscendAPI.isReady() // returns BoolNote: most API methods require the SDK to be ready and will throw TranscendError.notReady otherwise.
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.
try TranscendAPI.destroy()Register listeners with addEventListener(type:callback:). Each call returns a ListenerHandle — store it and pass it to removeEventListener(_:) when done.
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)| 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. |
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 truePass a map of purpose names to boolean values. Syncs to the backend by default.
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.
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:
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".
**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.
let config = try TranscendConfig(
bundleId: "YOUR_BUNDLE_ID",
mobileAppId: "com.example.myapp",
token: "AUTH_TOKEN" // Required for sync
)
The SDK syncs consent with the Transcend backend automatically in two cases:
- During initialization, using the
tokenfrom theTranscendConfig. - After
setConsent, whenautoSyncis true (the default), using the same TranscendConfigtokenwe 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:
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.
Use these APIs to determine whether a third-party SDK (identified by a service ID) is allowed to run given the current consent state.
// 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)
}
}Pass specific service IDs, or `nil`/empty to evaluate all known SDKs:
// 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:
TranscendAPI.batchGetSDKConsentStatuses(serviceIds: ["analytics_sdk"]) { result in
if case .success(let map) = result {
for (serviceId, status) in map {
print("\(serviceId): \(status)")
}
}
}| 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.
// 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()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. |