import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# License Types

The public types backing [`Octet.start(...)`](/docs/api-reference/octet-start/) and `OctetSdk.licenseStatus`. See [License & Activation](/docs/concepts/license-activation/) for the timeline model.

## `LicenseStatus`

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

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
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
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
data class LicenseStatus(
    val state: LicenseState,
    val activatedAt: Instant?,
    val hardStopAt: Instant?,
    val daysUntilHardStop: Int?,
    val tier: String,
)
```

  </TabItem>
</Tabs>

| 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.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
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"
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
enum class LicenseState {
    NOT_ACTIVATED,
    ACTIVE,
    RENEWAL_RECOMMENDED,
    GRACE_PERIOD,
    EXPIRED,
    INVALID,
}
```

  </TabItem>
</Tabs>

| 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.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
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)
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
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(...)
}
```

  </TabItem>
</Tabs>

| 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

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
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
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
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
    }
}
```

  </TabItem>
</Tabs>

## See also

- [License & Activation](/docs/concepts/license-activation/). The timeline model and activation flow.
- [`Octet.start(...)`](/docs/api-reference/octet-start/). The call that throws `LicenseError`.
- [Troubleshooting FAQ](/docs/troubleshooting/faq/). What to do when a key fails.
