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 a fully-usable `OctetSdk` handle. See [Attested bootstrap](/docs/v2.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  // "2.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  // "2.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/v2.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 sandboxBypassToken: String?   // dev / CI / simulator bootstrap; nil disables
    public var advanced: AdvancedConfig

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

    public static let defaultActivationServerUrl: String  // "https://api.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 sandboxBypassToken: String? = null,  // dev / CI / simulator bootstrap; null disables
    val advanced: AdvancedConfig = AdvancedConfig(),
) {
    companion object {
        const val DEFAULT_ACTIVATION_SERVER_URL: String  // "https://api.octetproof.com"
    }
}
```

  </TabItem>
</Tabs>

`licenseKey` is required by the config type but ignored by `Octet.start`, which activates through [attested bootstrap](/docs/v2.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.

## `AdvancedConfig`

Still small. Battery profile, sensor tuning, and ML knobs stay internal. What is
public: the activation server, log level, [device-attestation](/docs/v2.0/concepts/device-attestation/)
cadence, and the per-platform attestation knobs.

<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 attestationCadence: AttestationCadence  // default: .periodic(interval: 300)

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

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

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/v2.0/concepts/device-attestation/). `enableCertPinning` opts
into the bundled certificate pin set for `api.octetproof.com`. The Android
`playIntegrityCloudProjectNumber` binds the per-proof [Play Integrity](/docs/v2.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.

## `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 proof already carries the full Key Attestation certificate chain, so the bundle is a convenience mirror rather than a requirement.

## 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/v2.0/api-reference/license-types/) for every license-related failure. Other failures propagate as their native error types. The SDK does **not** throw a raw `Error` / `Exception` for license reasons.

| `LicenseError` case | Meaning |
|---|---|
| `MalformedKey` | The key isn't a valid signed token. |
| `NoActivation` | No cached activation, offline. |
| `Expired` | Cached activation past the offline grace with the backend unreachable to refresh. |
| `ActivationWindowClosed` | Fresh device trying to activate after day 90. |
| `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`. See [License Types](/docs/v2.0/api-reference/license-types/) and [Attested bootstrap](/docs/v2.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/v2.0/concepts/license-activation/) for the timeline model and activation flow.
- [`OctetSdk`](/docs/v2.0/api-reference/octet-sdk/) for what `start` returns.
- [License Types](/docs/v2.0/api-reference/license-types/) for the full `LicenseError` reference.
