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/v3.0/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.0.0"

    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.0.0"

    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/v3.0/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 {
    public let licenseKey: String            // required by the config type, ignored at runtime; pass ""
    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(
        licenseKey: String,
        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(
    val licenseKey: String,                  // required by the config type, ignored at runtime; pass ""
    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>

`licenseKey` is required by the config type but ignored by `Octet.start`, which activates through [attested bootstrap](/docs/v3.0/concepts/attested-bootstrap/). Pass an empty string on native (Swift and Kotlin). The React Native wrapper validates it as non-empty, so pass any non-empty placeholder there (for example `"unused"`).

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

`creditServiceUrl` is the endpoint for the [daily usage record](/docs/v3.0/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/v3.0/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/v3.0/concepts/device-attestation/)
cadence, the per-platform attestation settings, opt-in `PLAUSIBLE` containment, and the
network transport.

<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,
)

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/v3.0/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/v3.0/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/v3.0/api-reference/predicates/#plausible-containment). `transport` (new in 3.0)
chooses where the SDK's network calls go. See [Gateway routing](/docs/v3.0/concepts/gateway-routing/).

Android: `AdvancedConfig` gained constructor parameters in 3.0. Code compiled against
2.0.0 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 config = OctetConfig(licenseKey: "")   // required by the type, ignored by start; the SDK attests the app
let sdk = try await Octet.start(config: config)
```

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

```kotlin
lifecycleScope.launch {
    // licenseKey required by the type, ignored by start; the SDK attests the app
    val config = OctetConfig(licenseKey = "")
    val sdk = Octet.start(context = applicationContext, config = config)
}
```

  </TabItem>
</Tabs>

## Failure modes

`Octet.start(...)` throws a typed [`LicenseError`](/docs/v3.0/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. `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/v3.0/api-reference/license-types/) and [Attested bootstrap](/docs/v3.0/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/v3.0/concepts/license-activation/) for the timeline model and activation flow.
- [`OctetSdk`](/docs/v3.0/api-reference/octet-sdk/) for what `start` returns.
- [License Types](/docs/v3.0/api-reference/license-types/) for the full `LicenseError` reference.
