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 for the licensing model.
Signature
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?
}
object Octet {
const val SDK_VERSION: String // "3.1.2"
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
- Loads (or generates) a per-install UUID from secure storage (Keychain on iOS,
EncryptedSharedPreferenceson Android). - Reads the app identity from the bundle (
Bundle.main.bundleIdentifier/context.packageName). - Reuses a valid cached lease if present. Otherwise bootstraps: attests the app and calls
POST /v1/bootstrapto mint a device-bound license and lease. See Attested bootstrap. - Brings up the internal proof pipeline.
- Attaches the resulting
LicenseStatusto the returnedOctetSdk.
OctetConfig
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"
}
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"
}
}
Every field has a default, so OctetConfig() is a complete production configuration. Octet.start activates through 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. 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, 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
cadence, the per-platform attestation settings, opt-in PLAUSIBLE containment, the
network transport and, on Android, the location notification.
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
}
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()
}
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. 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
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. transport (new in 3.0)
chooses where the SDK's network calls go. See 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.
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.
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 attested proof already carries the full Key Attestation certificate chain, so the bundle duplicates that chain and is optional.
Example
let sdk = try await Octet.start(config: OctetConfig()) // the SDK attests the app
lifecycleScope.launch {
// the SDK attests the app
val sdk = Octet.start(context = applicationContext, config = OctetConfig())
}
Failure modes
Octet.start(...) throws a typed LicenseError 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 and 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 for the timeline model and activation flow.
OctetSdkfor whatstartreturns.- License Types for the full
LicenseErrorreference.