Docs/API Reference/License Types

License Types

The public types backing Octet.start(...) and OctetSdk.licenseStatus. See License & Activation for the timeline model.

LicenseStatus

Snapshot of the SDK's current license state. Read synchronously from OctetSdk.licenseStatus.

public struct LicenseStatus: Sendable, Equatable {
    public let state: LicenseState
    public let activatedAt: Date?
    public let hardStopAt: Date?
    public let daysUntilHardStop: Int?
    public let tier: String
}
data class LicenseStatus(
    val state: LicenseState,
    val activatedAt: Instant?,
    val hardStopAt: Instant?,
    val daysUntilHardStop: Int?,
    val tier: String,
)
Field Meaning
state Coarse state that drives in-app UI. See LicenseState below.
activatedAt First successful activation timestamp. nil pre-activation, or in INVALID.
hardStopAt The license's exp, equivalent to day 105 of issuance. Identical for every device that activates this license.
daysUntilHardStop ceil((hardStopAt - now) / 1 day). Useful for "X days left" banners.
tier Tier carried in the license token. "trial" at v1.

LicenseState

public enum LicenseState: String, Sendable {
    case notActivated       = "NOT_ACTIVATED"
    case active             = "ACTIVE"
    case renewalRecommended = "RENEWAL_RECOMMENDED"
    case gracePeriod        = "GRACE_PERIOD"
    case expired            = "EXPIRED"
    case invalid            = "INVALID"
}
enum class LicenseState {
    NOT_ACTIVATED,
    ACTIVE,
    RENEWAL_RECOMMENDED,
    GRACE_PERIOD,
    EXPIRED,
    INVALID,
}
State Meaning
NOT_ACTIVATED License verified, never activated, still within the 90-day activation window.
ACTIVE Activated, more than 30 days until hard stop.
RENEWAL_RECOMMENDED Activated, daysUntilHardStop ≤ 30. Surface a renewal banner if your UI does that.
GRACE_PERIOD Either license past the 90-day activation window but within the 15-day aging grace, OR cached activation past its exp but within the 7-day offline tolerance. SDK keeps working. Prompt for renewal.
EXPIRED License past day 105 hard stop, OR cached activation past offline tolerance. SDK refused to start.
INVALID Signature failed, server rejected, or malformed. SDK refused to start.
Caution

LicenseState drives in-app UI only. It never drives the cryptographic gate. That is enforced by the activation token. Forging state = ACTIVE accomplishes nothing.

LicenseError

Thrown by Octet.start(...) for every license-related failure. The SDK guarantees one of these subtypes. It never throws a raw Error / Exception for license reasons.

public enum LicenseError: Error, Sendable, Equatable {
    case malformedKey
    case noActivation
    case expired
    case activationWindowClosed
    case revoked
    case network(message: String)
    case serverRejected(httpStatus: Int, reason: String)
}
sealed class LicenseError(message: String) : Exception(message) {
    object MalformedKey            : LicenseError(...)
    object NoActivation            : LicenseError(...)
    object Expired                 : LicenseError(...)
    object ActivationWindowClosed  : LicenseError(...)
    object Revoked                 : LicenseError(...)
    data class Network(override val cause: Throwable) : LicenseError(...)
    data class ServerRejected(val httpStatus: Int, val reason: String) : LicenseError(...)
}
Case When Fix
MalformedKey Local PASETO signature / structural failure on the license key. Re-copy the key.
NoActivation No cached activation, and offline (can't reach /v1/activate). Retry when network returns.
Expired License past day 105 hard stop, OR cached activation past 7-day offline grace. Request a new key.
ActivationWindowClosed Server returned 403 activation_window_closed. Fresh device, license past day 90 but within the 15-day grace. Request a new key.
Revoked Server returned 403 revoked (admin revoke). Contact support.
Network Transient network failure during activation. message (iOS) or cause (Android) carries the underlying error for diagnostics. Retry. Do not parse the message for control flow.
ServerRejected Any other HTTP rejection (app_blocked, ip_blocked, …). Inspect reason.

Reading the status at runtime

guard let status = sdk.licenseStatus else { return }
switch status.state {
case .renewalRecommended:
    showBanner("Renew within \(status.daysUntilHardStop ?? 0) days")
case .gracePeriod:
    showBanner("Grace period: renew before the SDK stops")
case .expired, .invalid:
    showBlocker()
case .active, .notActivated:
    break  // happy path
}
val status = sdk.licenseStatus ?: return
when (status.state) {
    LicenseState.RENEWAL_RECOMMENDED ->
        showBanner("Renew within ${status.daysUntilHardStop ?: 0} days")
    LicenseState.GRACE_PERIOD ->
        showBanner("Grace period: renew before the SDK stops")
    LicenseState.EXPIRED, LicenseState.INVALID ->
        showBlocker()
    LicenseState.ACTIVE, LicenseState.NOT_ACTIVATED -> {
        // happy path
    }
}

See also