Docs/API Reference/Octet.start

Octet.start(...)

The single bring-up call. Verifies the license key locally, activates against the Octet backend if needed, brings up the proof pipeline, and returns a fully-usable OctetSdk handle.

Signature

public enum Octet {
    public static let sdkVersion: String  // "1.2.1"

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

    public static func attestationEnrolmentBundle() -> AttestationEnrolmentBundle?
}
object Octet {
    const val SDK_VERSION: String  // "1.2.1"

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

    fun attestationEnrolmentBundle(): AttestationEnrolmentBundle?
}

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. Auto-detects the app id from the bundle (Bundle.main.bundleIdentifier / context.packageName).
  3. Verifies the license key's PASETO signature locally against the SDK's embedded public keys.
  4. Validates the cached activation token if present. Otherwise calls POST /v1/activate to acquire one.
  5. Brings up the internal proof pipeline.
  6. Attaches the resulting LicenseStatus to the returned OctetSdk.

OctetConfig

public struct OctetConfig: Sendable {
    public let licenseKey: String
    public var proofUploadUrl: String?       // opt-in proof upload; nil disables (default)
    public var telemetryEnabled: Bool        // aggregate usage counters; default true
    public var advanced: AdvancedConfig

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

    public static let defaultActivationServerUrl: String  // "https://api.octetproof.com"
}
data class OctetConfig(
    val licenseKey: String,
    val proofUploadUrl: String? = null,      // opt-in proof upload; null disables (default)
    val telemetryEnabled: Boolean = true,    // aggregate usage counters; default true
    val advanced: AdvancedConfig = AdvancedConfig(),
) {
    companion object {
        const val DEFAULT_ACTIVATION_SERVER_URL: String  // "https://api.octetproof.com"
    }
}

licenseKey is the only required field. It is a PASETO v4.public token in the wire form octet_live_v4.public.… (prod) or octet_test_… (staging).

AdvancedConfig

Still small. Battery profile, sensor tuning, and ML knobs stay internal. What is public: the activation server, log level, device-attestation cadence, and the per-platform attestation knobs.

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
}
data class AdvancedConfig(
    val activationServerUrl: String = OctetConfig.DEFAULT_ACTIVATION_SERVER_URL,
    val logLevel: LogLevel = LogLevel.INFO,
    // Google Cloud project NUMBER for Play Integrity; null uses the Play-Console-linked 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()
}

Override activationServerUrl only when pointing at a staging or local backend. attestationCadence and the Android playIntegrityCloudProjectNumber are covered in Device Attestation. enableCertPinning opts into the bundled certificate pin set for api.octetproof.com. The Play Integrity cloud-project knob is Android-only: App Attest on iOS has no equivalent.

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.

public struct AttestationEnrolmentBundle: Sendable {
    public func jsonString() -> String        // canonical v:1 envelope
    public func protoData() throws -> Data     // DeviceAttestation proto bytes
}
class AttestationEnrolmentBundle internal constructor(/* … */) {
    fun jsonString(): String
    fun protoData(): ByteArray
}

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

let config = OctetConfig(
    licenseKey: "octet_live_v4.public.…"
    // advanced left to defaults
)
let sdk = try await Octet.start(config: config)
lifecycleScope.launch {
    val sdk = Octet.start(
        context = applicationContext,
        config = OctetConfig(licenseKey = "octet_live_v4.public.…")
    )
}

Failure modes

Octet.start(...) throws a typed LicenseError 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.

UpgradeRequired is part of SDK-version upgrade gating, added in 1.2. The SDK reports its version and platform on every backend request, so the backend can refuse an out-of-support version at Octet.start, or return the softer LicenseStatus.upgradeRecommended and minSupportedVersion hints instead. Both paths are inert in 1.2, because the backend gates no version yet. See License Types for the field and case shapes.

See also