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
- License & Activation. The timeline model and activation flow.
Octet.start(...). The call that throwsLicenseError.- Troubleshooting FAQ. What to do when a key fails.