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. Octet maintains active licenses, so this advances as the key renews -- do not design around a fixed cutoff. 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. |
LicenseState
Octet maintains active licenses, so a key in normal use stays in ACTIVE. RENEWAL_RECOMMENDED, GRACE_PERIOD, and EXPIRED cover prolonged-offline and revocation handling -- not an age-based trial clock.
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. |
ACTIVE |
Activated and current. The normal steady state for a maintained key. |
RENEWAL_RECOMMENDED |
Activated, nearing the license exp. Maintained keys renew automatically; surface a banner only if you want to. |
GRACE_PERIOD |
Cached activation past its exp but within the offline tolerance -- the device could not reach the backend to refresh. SDK keeps working. |
EXPIRED |
Cached activation past the offline tolerance with no backend reachable to refresh. SDK refused to start. Age alone no longer expires a maintained key. |
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 |
Cached activation past the offline grace with the backend unreachable to refresh. | Reconnect so the SDK can re-activate. |
ActivationWindowClosed |
Server returned 403 activation_window_closed. Not returned under the current maintained-license model; retained for compatibility. |
Contact support. |
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
- License & Activation. The timeline model and activation flow.
Octet.start(...). The call that throwsLicenseError.- Troubleshooting FAQ. What to do when a key fails.