import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# `Octet.start(...)`

The single bring-up call. It obtains the license by attesting the app (Apple App Attest, Android key attestation), brings up the proof pipeline, and returns an `OctetSdk` handle. See [Attested bootstrap](/docs/mobile/concepts/attested-bootstrap/) for the licensing model.

## Signature

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public enum Octet {
    public static let sdkVersion: String  // "3.1.2"

    public static func start(
        config: OctetConfig,
        startPosition: Position? = nil
    ) async throws -> OctetSdk

    public static func attestationEnrolmentBundle() -> AttestationEnrolmentBundle?
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
object Octet {
    const val SDK_VERSION: String  // "3.1.2"

    suspend fun start(
        context: Context,
        config: OctetConfig,
        startPosition: Position? = null,
    ): OctetSdk

    fun attestationEnrolmentBundle(): AttestationEnrolmentBundle?
}
```

  </TabItem>
</Tabs>

`startPosition` is an optional hint used internally during pipeline bring-up. Most integrators omit it.

## What it does, in order

1. Loads (or generates) a per-install UUID from secure storage (Keychain on iOS, `EncryptedSharedPreferences` on Android).
2. Reads the app identity from the bundle (`Bundle.main.bundleIdentifier` / `context.packageName`).
3. Reuses a valid cached lease if present. Otherwise bootstraps: attests the app and calls `POST /v1/bootstrap` to mint a device-bound license and lease. See [Attested bootstrap](/docs/mobile/concepts/attested-bootstrap/).
4. Brings up the internal proof pipeline.
5. Attaches the resulting `LicenseStatus` to the returned `OctetSdk`.

## `OctetConfig`

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct OctetConfig: Sendable {
    @available(*, deprecated) public var licenseKey: String { get }  // ignored; omit it; removed in 4.0
    public var proofUploadUrl: String?       // opt-in proof upload; nil disables (default)
    public var telemetryEnabled: Bool        // aggregate usage counters; default true
    public var creditServiceUrl: String?     // daily usage record endpoint; nil = default (testing override only)
    public var flagsEnabled: Bool            // remote configuration (sdk.flags); default true
    public var flagServiceUrl: String?       // remote configuration endpoint; nil = activation host (testing override only)
    public var advanced: AdvancedConfig
    public var sandboxBypassToken: String?   // dev / CI / simulator bootstrap; nil disables

    public init(
        proofUploadUrl: String? = nil,
        telemetryEnabled: Bool = true,
        creditServiceUrl: String? = nil,
        flagsEnabled: Bool = true,
        flagServiceUrl: String? = nil,
        advanced: AdvancedConfig = AdvancedConfig(),
        sandboxBypassToken: String? = nil
    )

    public static let defaultActivationServerUrl: String  // "https://api.octetproof.com"
    public static let defaultCreditServiceUrl: String     // "https://credits.octetproof.com"
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
data class OctetConfig(
    @Deprecated val licenseKey: String = "",  // ignored; omit it; removed in 4.0
    val proofUploadUrl: String? = null,      // opt-in proof upload; null disables (default)
    val telemetryEnabled: Boolean = true,    // aggregate usage counters; default true
    val creditServiceUrl: String? = null,    // daily usage record endpoint; null = default (testing override only)
    val flagsEnabled: Boolean = true,        // remote configuration (sdk.flags); default true
    val flagServiceUrl: String? = null,      // remote configuration endpoint; null = activation host (testing override only)
    val advanced: AdvancedConfig = AdvancedConfig(),
    val sandboxBypassToken: String? = null,  // dev / CI / simulator bootstrap; null disables
) {
    companion object {
        const val DEFAULT_ACTIVATION_SERVER_URL: String  // "https://api.octetproof.com"
        const val DEFAULT_CREDIT_SERVICE_URL: String     // "https://credits.octetproof.com"
    }
}
```

  </TabItem>
</Tabs>

Every field has a default, so `OctetConfig()` is a complete production configuration. `Octet.start` activates through [attested bootstrap](/docs/mobile/concepts/attested-bootstrap/). `licenseKey` is deprecated since 3.1 and removed in 4.0. `Octet.start` ignores it, so leave it out. Code that still passes it builds with a deprecation warning.

For local development, CI, and simulators, set `sandboxBypassToken` to bootstrap without hardware attestation.

`creditServiceUrl` is the endpoint for the [daily usage record](/docs/mobile/concepts/metrics/#daily-usage-record). It defaults to `https://credits.octetproof.com`. Set it only to point a test build at another environment. Setting it to `nil` / `null` does not disable reporting.

`flagsEnabled` switches [`sdk.flags`](/docs/mobile/api-reference/octet-sdk/#flags), the read-only remote configuration, on or off. It defaults to `true`. On Android it is the fifth constructor parameter, unchanged since 2.0.

## `AdvancedConfig`

Battery profile, sensor tuning, and ML parameters are internal. `AdvancedConfig`
exposes the activation server, log level, iOS certificate pinning, [device-attestation](/docs/mobile/concepts/device-attestation/)
cadence, the per-platform attestation settings, opt-in `PLAUSIBLE` containment, the
network transport and, on Android, the location notification.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct AdvancedConfig: Sendable {
    public var activationServerUrl: String        // default: production
    public var logLevel: LogLevel                 // default: .info
    public var enableCertPinning: Bool            // default: false
    public var acceptPlausibleContainment: Bool   // default: false
    public var attestationCadence: AttestationCadence  // default: .periodic(interval: 300)
    public var transport: TransportPolicy         // default: .direct

    public init(
        activationServerUrl: String = OctetConfig.defaultActivationServerUrl,
        logLevel: LogLevel = .info,
        enableCertPinning: Bool = false,
        acceptPlausibleContainment: Bool = false,
        attestationCadence: AttestationCadence = .periodic(interval: 300),
        transport: TransportPolicy = .direct
    )
}

public struct TransportPolicy: Sendable {
    public enum Mode: Sendable { case direct, octetGateway, integratorGateway }
    public enum ThirdParty: Sendable { case proxy, disable }

    public init(
        mode: Mode = .direct,
        gateway: String? = nil,
        thirdParty: ThirdParty = .proxy,
        pins: [String] = [],
        fallbackToDirect: Bool = false,
        fallbackAfterFailures: Int = 3,
        osAttestationInGatewayModes: Bool = true
    )

    public static let direct: TransportPolicy
    public static let octetGateway: TransportPolicy
}

public enum LogLevel { case verbose, debug, info, warn, error }

public enum AttestationCadence: Sendable {
    case perSession
    case periodic(interval: TimeInterval)   // default; 5 minutes
    case perProof
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
data class AdvancedConfig(
    val activationServerUrl: String = OctetConfig.DEFAULT_ACTIVATION_SERVER_URL,
    val logLevel: LogLevel = LogLevel.INFO,
    // binds the per-proof Play Integrity verdict to a GCP project; null = Play Console project
    val playIntegrityCloudProjectNumber: Long? = null,
    val attestationCadence: AttestationCadence = AttestationCadence.Periodic(intervalSeconds = 300),
    val acceptPlausibleContainment: Boolean = false,
    val transport: TransportPolicy = TransportPolicy.direct,
    val foregroundNotification: ForegroundNotification = ForegroundNotification(),  // new in 3.1
)

data class ForegroundNotification(
    val title: String? = null,              // null or blank: "Location active"
    val text: String? = null,               // null or blank: "Confirming your location in the background"
    @DrawableRes val smallIcon: Int? = null, // a monochrome drawable, not a launcher or adaptive icon
    val channelId: String? = null,          // your channel; created at low importance if it doesn't exist
    val channelName: String? = null,        // name when the SDK creates the channel; default "Location"
    val contentIntent: PendingIntent? = null, // what a tap opens; null: not tappable
    val provider: Provider? = null,         // build the whole Notification yourself; overrides the fields above
) {
    fun interface Provider { fun build(context: Context): Notification }
}

data class TransportPolicy(
    val mode: Mode = Mode.DIRECT,
    val gateway: String? = null,
    val thirdParty: ThirdParty = ThirdParty.PROXY,
    val pins: List<String> = emptyList(),
    val fallbackToDirect: Boolean = false,
    val fallbackAfterFailures: Int = 3,
    val osAttestationInGatewayModes: Boolean = true,
) {
    enum class Mode { DIRECT, OCTET_GATEWAY, INTEGRATOR_GATEWAY }
    enum class ThirdParty { PROXY, DISABLE }
    companion object {
        val direct: TransportPolicy
        val octetGateway: TransportPolicy
    }
}

enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR }

sealed class AttestationCadence {
    object PerSession : AttestationCadence()
    data class Periodic(val intervalSeconds: Int) : AttestationCadence()  // default; 5 minutes
    object PerProof : AttestationCadence()
}
```

  </TabItem>
</Tabs>

Override `activationServerUrl` only if Octet gives you a specific URL to point at. It defaults to production.
`attestationCadence` and the Android `playIntegrityCloudProjectNumber` are covered
in [Device Attestation](/docs/mobile/concepts/device-attestation/). `enableCertPinning` (iOS only) opts
into the bundled certificate pin set for `api.octetproof.com`. On Android, opt in by setting
`android:networkSecurityConfig="@xml/octet_network_security_config"` on your manifest's
`<application>` element. Both pin sets are separate from `TransportPolicy.pins`, which pins
your own gateway in `integratorGateway` mode. The Android
`playIntegrityCloudProjectNumber` binds the per-proof [Play Integrity](/docs/mobile/concepts/device-attestation/#play-integrity-on-android)
verdict to a Google Cloud project. Leave it `null` to use the project linked in the Play Console.

`acceptPlausibleContainment` (new in 3.0, default off) lets `contains(...)` and a disc
`isWithin(...)` answer `YES` or `NO` from a `PLAUSIBLE` proof. See
[Predicates](/docs/mobile/api-reference/predicates/#plausible-containment). `transport` (new in 3.0)
chooses where the SDK's network calls go. See [Gateway routing](/docs/mobile/concepts/gateway-routing/).

`foregroundNotification` (new in 3.1, Android only) sets the notification Android shows
while the SDK collects location with your app in the background. Set the title, text,
icon, channel and tap action, or set `provider` to build the whole notification yourself,
and the SDK then only shows and removes it. An unset field keeps the SDK's neutral default,
and the SDK never puts location or personal data in the notification. Android 14 and later
crash an app whose foreground service starts with a notification it rejects, such as a
missing or adaptive small icon or a channel that doesn't exist. The SDK checks for these
first, and on a problem logs a warning and shows its default notification instead.
`OctetSdk.close()` removes the notification. iOS has no equivalent.

```kotlin
val config = OctetConfig(
    advanced = AdvancedConfig(
        foregroundNotification = ForegroundNotification(
            title = "Checking your location",
            smallIcon = R.drawable.ic_location,
            contentIntent = openAppIntent)))
```

Android: `AdvancedConfig` gained constructor parameters in 3.0 and again in 3.1. Code
compiled against an earlier version that calls its constructor or `copy` must be
recompiled. Source that uses named arguments compiles unchanged.

## `attestationEnrolmentBundle()`

Added in 1.2. Returns this device key's `AttestationEnrolmentBundle`, or `nil` before the device key has been attested. Attestation first happens on the install's first proof. The read is local (a Keychain entry on iOS, a Keystore entry on Android) and makes no network call.

Hand the bundle to a verifier's enrolment step so the verifier can establish this device's hardware root ahead of time, without waiting for the once-per-key attestation object to arrive on a submitted proof. This helps a verifier that was freshly deployed, scaled out, or migrated.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct AttestationEnrolmentBundle: Sendable {
    public func jsonString() -> String        // canonical v:1 envelope
    public func protoData() throws -> Data     // DeviceAttestation proto bytes
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
class AttestationEnrolmentBundle internal constructor(/* ... */) {
    fun jsonString(): String
    fun protoData(): ByteArray
}
```

  </TabItem>
</Tabs>

On iOS, App Attest produces the object once per key, so a verifier that has not yet seen a proof from this device cannot check its hardware root until the bundle arrives. On Android, every attested proof already carries the full Key Attestation certificate chain, so the bundle duplicates that chain and is optional.

## Example

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
let sdk = try await Octet.start(config: OctetConfig())   // the SDK attests the app
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
lifecycleScope.launch {
    // the SDK attests the app
    val sdk = Octet.start(context = applicationContext, config = OctetConfig())
}
```

  </TabItem>
</Tabs>

## Failure modes

`Octet.start(...)` throws a typed [`LicenseError`](/docs/mobile/api-reference/license-types/) for every license-related failure. Other failures propagate as their native error types.

| `LicenseError` case | Meaning |
|---|---|
| `MalformedKey` | The license token the SDK holds did not verify. The deprecated `licenseKey` is not involved. |
| `NoActivation` | No cached activation, offline. |
| `Expired` | Cached activation past the offline grace with the backend unreachable to refresh. |
| `ActivationWindowClosed` | Not returned under the current maintained-license model. Retained for compatibility. |
| `Revoked` | Admin revoke. |
| `Network(message)` / `Network(cause)` | Transient network failure during activation. |
| `ServerRejected(httpStatus, reason)` | Backend rejected for another reason (e.g., `app_blocked`). |
| `UpgradeRequired(minVersion, message)` | Backend rejected this SDK version as out of support. |
| `BootstrapFailed(reason)` | Attested bootstrap failed. `reason` is a `BootstrapReason` (attestation invalid, app not registered, sandbox-token issues, ...). |

`BootstrapFailed` carries a `BootstrapReason`. When the account accepts no new devices, the reason is `newDevicesBlocked` (`NewDevicesBlocked` on Android) and a server-signed `upgradeUrl` comes with it: on iOS as the reason's associated value, on Android as `BootstrapFailed.upgradeUrl`. See [License Types](/docs/mobile/api-reference/license-types/) and [Attested bootstrap](/docs/mobile/concepts/attested-bootstrap/). `UpgradeRequired` is SDK-version upgrade gating: the SDK reports its version on every backend request, and the backend can refuse an out-of-support version at `Octet.start` or return the softer `LicenseStatus.upgradeRecommended` and `minSupportedVersion` hints.

## See also

- [License & Activation](/docs/mobile/concepts/license-activation/) for the timeline model and activation flow.
- [`OctetSdk`](/docs/mobile/api-reference/octet-sdk/) for what `start` returns.
- [License Types](/docs/mobile/api-reference/license-types/) for the full `LicenseError` reference.
