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.
- Swift (iOS)
- Kotlin (Android)
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
- Swift (iOS)
- Kotlin (Android)
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.
- Swift (iOS)
- Kotlin (Android)
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
- Swift (iOS)
- Kotlin (Android)
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.