Skip to main content

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
}
FieldMeaning
stateCoarse state that drives in-app UI. See LicenseState below.
activatedAtFirst successful activation timestamp. nil pre-activation, or in INVALID.
hardStopAtThe license's exp, equivalent to day 105 of issuance. Identical for every device that activates this license.
daysUntilHardStopceil((hardStopAt - now) / 1 day). Useful for "X days left" banners.
tierTier 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"
}
StateMeaning
NOT_ACTIVATEDLicense verified, never activated, still within the 90-day activation window.
ACTIVEActivated, more than 30 days until hard stop.
RENEWAL_RECOMMENDEDActivated, daysUntilHardStop ≤ 30. Surface a renewal banner if your UI does that.
GRACE_PERIODEither 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.
EXPIREDLicense past day 105 hard stop, OR cached activation past offline tolerance. SDK refused to start.
INVALIDSignature 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)
}
CaseWhenFix
MalformedKeyLocal PASETO signature / structural failure on the license key.Re-copy the key.
NoActivationNo cached activation, and offline (can't reach /v1/activate).Retry when network returns.
ExpiredLicense past day 105 hard stop, OR cached activation past 7-day offline grace.Request a new key.
ActivationWindowClosedServer returned 403 activation_window_closed. Fresh device, license past day 90 but within the 15-day grace.Request a new key.
RevokedServer returned 403 revoked (admin revoke).Contact support.
NetworkTransient network failure during activation. message (iOS) or cause (Android) carries the underlying error for diagnostics.Retry. Do not parse the message for control flow.
ServerRejectedAny 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
}

See also