# Android SDK Quickstart (/docs/consent-management/mobile-consent/android-headless-consent/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**

### Installation [#installation]

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

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

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

```kotlin
repositories {
    google()
    mavenCentral()
}
```

The library declares `INTERNET` and `ACCESS_NETWORK_STATE` permissions automatically.

### Initialization [#initialization]

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.

```kotlin
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)
```

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

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

```kotlin
TranscendAPI.asyncInit(applicationContext, config)
```

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

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:

```kotlin
TranscendAPI.isReady() // returns Boolean
```

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

```kotlin
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.

```kotlin
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)
```

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

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

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

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

```kotlin
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
```

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

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

```kotlin
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.

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

```kotlin
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"`.

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

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

```kotlin
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.

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

```kotlin
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 */ }
            }
        }
    },
)
```

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

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

```kotlin
// 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:

```kotlin
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)
```

#### `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.                                             |
| `TCF_ALLOW`      | SDK uses TCF and the TCF API is available.                                                       |
| `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.

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

### Additional APIs [#additional-apis]

```kotlin
// 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()
```

### Error Handling [#error-handling]

All throwing APIs can produce \`TranscendError\`:

| Error                                         | Cause                                                |
| --------------------------------------------- | ---------------------------------------------------- |
| `TranscendError.NotInitialized`               | `init()` has not been called.                        |
| `TranscendError.NotReady`                     | SDK is still loading; wait for the `ready` callback. |
| `TranscendError.AlreadyInitialized`           | `init()` called more than once without `destroy()`.  |
| `TranscendError.InvalidConfiguration(detail)` | Invalid config (e.g. empty bundleId).                |
| `TranscendError.InitFailed(detail)`           | Settings fetch or initialization failed.             |
