Android SDK Quickstart

The Transcend Headless SDK provides a headless consent management API for Android apps. The module is imported as io.transcend.sdk, and all API calls will go through the imported TranscendAPI.

Requirements: Android API 24+, Kotlin, JVM target 11

Add the dependency to your module-level build.gradle.kts:

dependencies {
    implementation("io.transcend.sdk.headless:headless:0.9.0")
}

Ensure mavenCentral() is in your repository list (no custom repository required):

repositories {
    google()
    mavenCentral()
}

The library declares INTERNET and ACCESS_NETWORK_STATE permissions automatically.

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

import io.transcend.sdk.TranscendAPI
import io.transcend.sdk.models.TranscendConfig

val config = 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
    cdn = "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 = mapOf(                // Optional — override remote settings at init
        "country" to "EU",
        "regime" to "GDPR",
    ),
)

TranscendAPI.ready { event ->
    if (event.success) {
        // SDK is ready — safe to call consent APIs
    } else {
        Log.e("Transcend", "Init failed: ${event.errorDetails}")
    }
}

TranscendAPI.init(applicationContext, config)

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

TranscendAPI.asyncInit(applicationContext, config)

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

TranscendAPI.isReady() // returns Boolean

Note: most API methods require the SDK to be ready and will throw TranscendError.NotInitialized 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.

TranscendAPI.destroy()

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

val consentHandle = TranscendAPI.addEventListener(EventListenerType.CONSENT_CHANGE) { event ->
    val change = event as ConsentEvent.ConsentChange
    Log.d("Transcend", "Consent changed: ${change.newConsent.purposes}")
}

val syncHandle = TranscendAPI.addEventListener(EventListenerType.SYNC) { event ->
    val sync = event as ConsentEvent.Sync
    Log.d("Transcend", "Sync completed")
}

// When no longer needed:
TranscendAPI.removeEventListener(consentHandle)
TranscendAPI.removeEventListener(syncHandle)
Listener typeConsentEvent subclassWhen it fires
CONSENT_CHANGEConsentEvent.ConsentChangeLocal consent state changes (e.g. after setConsent).
SYNCConsentEvent.SyncBackend sync completes and consent may have been updated.
*(via ready)*ConsentEvent.ReadySDK initialization succeeds or fails.
val consent = TranscendAPI.getConsent()
// consent.purposes        — Map<String, PurposeValue>? (.True / .False / .Auto)
// consent.confirmed       — Boolean
// consent.prompted        — Boolean
// consent.timestamp       — ISO 8601 string
// Boolean purposes map    — consent.purposes?.mapValues { it.value.toBoolean() }, coalesces .Auto to true

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

val success = TranscendAPI.setConsent(
    purposes = mapOf(
        "Analytics" to true,
        "Functional" to true,
        "Advertising" to false,
    ),
    autoSync = true,                          // Sync to backend after setting (default: true)
    confirmed = true,                         // Mark consent as confirmed (default: true)
    timestamp = null,                         // ISO timestamp; defaults to now
    updated = true,                           // Mark consent as updated (default: true)
    metadata = ConsentMetadata.Unchanged,     // ConsentMetadata; see ConsentMetadata sealed class
    metadataTimestamp = null,
)

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:

val config = TranscendConfig(
    bundleId = "YOUR_BUNDLE_ID",
    mobileAppId = "com.example.myapp",
    settingsOverrides = mapOf(
        "country" to "DE",
        "countryRegion" to "EU",
        "regime" to "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.

val config = 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:

  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:

TranscendAPI.addEventListener(EventListenerType.SYNC) { event ->
    val sync = event as ConsentEvent.Sync
    // Backend sync finished; sync.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.

TranscendAPI.getSdkConsentStatus(
    context,
    "analytics_sdk",
    object : TranscendCallback.ConsentStatusListener {
        override fun onStatusReceived(status: ConsentStatus) {
            when (status) {
                ConsentStatus.ALLOW -> { /* SDK may run */ }
                ConsentStatus.BLOCK -> { /* User denied required purpose(s) */ }
                ConsentStatus.TCF_ALLOW -> { /* Allowed via TCF */ }
                ConsentStatus.NO_SDK_FOUND -> { /* Service ID not in purpose map */ }
                ConsentStatus.INTERNAL_ERROR -> { /* Error occurred */ }
            }
        }
    },
)

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

// Specific IDs
TranscendAPI.batchGetSDKConsentStatuses(
    context,
    arrayOf("analytics_sdk", "ads_sdk"),
    object : TranscendCallback.ConsentStatusListListener {
        override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
            // e.g. {"analytics_sdk": ALLOW, "ads_sdk": BLOCK}
        }
    },
)

// All SDKs in the purpose map
TranscendAPI.batchGetSDKConsentStatuses(
    context,
    null,
    object : TranscendCallback.ConsentStatusListListener {
        override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
            // statusMap contains every known service ID and its status
        }
    },
)

Callback variant:

val callback = object : TranscendCallback.ConsentStatusListListener {
    override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
        for ((serviceId, status) in statusMap) {
            Log.d("Transcend", "$serviceId: $status")
        }
    }
}
TranscendAPI.batchGetSDKConsentStatuses(context, arrayOf("analytics_sdk"), callback)
StatusMeaning
ALLOWAll required purposes for this SDK are granted (or SDK is Essential).
BLOCKAt least one required purpose was explicitly denied.
TCF_ALLOWSDK uses TCF and the TCF API is available.
NO_SDK_FOUNDThe service ID is not in the SDK purpose map.
INTERNAL_ERRORReturned 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.

Note: Callbacks are invoked on a background thread. You should marshal to the main thread before updating UI.

// Active privacy regimes (e.g. "GDPR")
val regimes = TranscendAPI.getRegimes()

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

// Purpose display metadata (name, description, defaultConsent, essential, etc.)
val details = TranscendAPI.getPurposeDetails()

// Raw loaded settings
val settings = TranscendAPI.getLoadOptions()

All throwing APIs can produce `TranscendError`:

ErrorCause
TranscendError.NotInitializedinit() has not been called.
TranscendError.NotReadySDK is still loading; wait for the ready callback.
TranscendError.AlreadyInitializedinit() called more than once without destroy().
TranscendError.InvalidConfiguration(detail)Invalid config (e.g. empty bundleId).
TranscendError.InitFailed(detail)Settings fetch or initialization failed.