# Troubleshooting & FAQ

Indexed by the symptom you see. Each entry gives the cause and the fix.

Check the [service status page](https://api.octetproof.com/status) to see whether an Octet service is having problems.

---

## Verdict reasons

### `INDETERMINATE / NO_FIX` on emulator or simulator

```
result:  INDETERMINATE
reason:  NO_FIX
message: running on simulator -- location proofs are unavailable in this environment
         (Android: "running on emulator -- ...")
```

**Why.** A simulator or emulator has no real GNSS, motion or cellular hardware and cannot attest the app. The SDK detects the developer environment and returns `NO_FIX` with this message instead of producing a proof.

**Fix.** Run on a real device. A simulator or emulator never produces a proof, however long you wait.

`verdict.message` omits the underlying tamper signals (mock-location flag, jailbreak / root indicators, GNSS anomalies, attestation results) on purpose. Exposing which signal fired is a security risk: it tells an attacker exactly what to defeat.

---

### `INDETERMINATE / NO_FIX` on a real device

The SDK started but has not seen a usable fix yet.

**Fix.**

- **Indoors.** Step outside or near a window and try again.
- **GPS disabled at the OS level.** Re-enable in Settings -> Location.
- **No fix yet.** The SDK has started but has not yet seen a usable fix. Retry after a few seconds, outdoors if you can.

---

### `INDETERMINATE / STALE_FIX`

`atTime` falls outside the validity window of any cached proof.

**Fix.**

- **You're querying a past time.** If you need historical answers, query within a few minutes of when the user was there. The per-resolution windows are: country ±5 min, subdivision ±3 min, city ±1 min, finer shapes ±30 s.
- **You're querying near-now.** Wait briefly. The SDK is likely producing a fresh proof on this exact call. If `STALE_FIX` persists, see `NO_FIX` above.

---

### `INDETERMINATE / FUTURE_TIME`

`atTime > now + 2 s` (the SDK's clock-skew tolerance).

**Fix.** Do not query the future. If you are seeing this with `atTime: Date()` or `Instant.now()` exactly, your device clock is significantly skewed relative to the SDK's internal clock. Re-sync the device clock.

---

### `INDETERMINATE / NO_PROOF_AT_RESOLUTION`

The cached proof is too coarse for the query. For example, you asked `isWithin(city("San Francisco"))` and the most recent proof is at country level.

**Fix.** The SDK should regenerate at the finer level on the next live query. If it persists indoors with no GPS, you may be in an environment where the finer resolution is not provable. Fall back to a coarser predicate (subdivision instead of city) or accept the indeterminate result.

---

### `INDETERMINATE / MOCK_LOCATION_DETECTED`

The OS flagged the fix as mocked. The SDK refuses to issue proofs for mocked fixes.

**Fix.** Disable any mock-location apps. On Android, also check Developer Options -> "Select mock location app" and clear it.

---

### `INDETERMINATE / ATTESTATION_FAILED`

The device or app failed platform integrity attestation.

**Fix.** This usually means the device or build environment failed integrity checks: rooted device, custom ROM, modified APK or IPA, debugger attached in a way the attestation provider rejects. The SDK cannot override this. The device or build needs to pass platform integrity.

---

### `INDETERMINATE / TAMPERING` while debugging on Android

Since 3.0, the SDK on Android refuses a proof with `TAMPERING` when a debugger or native tracer is attached, as on iOS.

**Fix.** Debug against a sandbox session. With `OctetConfig.sandboxBypassToken` set, a proof is still allowed with a debugger attached. See [sandbox bypass token](/docs/mobile/concepts/attested-bootstrap/#devices-that-cannot-attest).

---

### `INDETERMINATE / REGION_UNRESOLVED`

A country or state query could not resolve the region. A pinned `claimedRegion` that is not an assigned ISO 3166 code also produces it. In a [gateway mode](/docs/mobile/concepts/gateway-routing/#what-your-gateway-must-forward), a gateway that does not forward `/ext/geo/` makes country and subdivision proofs return `REGION_UNRESOLVED`.

**Fix.** Treat it as a benign gap: retry later or apply a step-up check. In a gateway mode, forward `/ext/geo/`.

---

### `INDETERMINATE / SDK_NOT_RUNNING`

You called a predicate before `Octet.start(...)` completed, or after the SDK session ended.

**Fix.** Hold the `OctetSdk` handle that `start` returned and call predicates on it. Do not call into a nil or disposed reference.

---

### `INDETERMINATE / NOT_YET_RELEASED`

The predicate path is declared by the v1 API but not available in this SDK build (e.g. some shape-pair containment combinations).

**Fix.** Until the feature lands, fall back to a supported predicate. The combinations that currently produce `NOT_YET_RELEASED` are documented per release in the [`octet-sdk-ios` CHANGELOG](https://github.com/octetproof/octet-sdk-ios/blob/main/CHANGELOG.md) and the [`octet-sdk-android` CHANGELOG](https://github.com/octetproof/octet-sdk-android/blob/main/CHANGELOG.md).

---

## License errors

### `LicenseError.MalformedKey`

The license token the SDK holds did not verify: the signature failed, the prefix is unknown, the `typ` claim is wrong, or a required claim is missing.

**Fix.** Since 2.0, the SDK obtains its license through [attested bootstrap](/docs/mobile/concepts/attested-bootstrap/), so there is no key to paste or copy. This points to a corrupted or unsupported license token rather than anything you configure. Confirm the SDK is on a supported version, and contact support if it persists.

---

### `LicenseError.NoActivation`

No cached activation token, and the device is offline.

**Fix.** Connect to a network long enough for one successful activation. After that, the SDK works offline for up to 14 days on a cached activation.

---

### `LicenseError.Expired`

The cached activation is past the offline grace and the SDK could not reach the backend to refresh. Age alone no longer expires a maintained key.

**Fix.** Reconnect so the SDK can refresh the activation. If it persists, contact support.

---

### `LicenseError.ActivationWindowClosed`

Not returned under the current maintained-license model. Octet maintains active licenses, so there is no activation window to close.

**Fix.** If you hit this, contact support.

---

### `LicenseError.Revoked`

The license was revoked by an Octet admin (typical reasons: key was leaked, abuse, payment dispute).

**Fix.** Contact Octet support.

---

### `LicenseError.Network`

Transient network failure during activation. The associated `message` (iOS) or `cause` (Android) carries the underlying error for diagnostics. Do not parse it for control flow.

**Fix.** Retry. If a usable cached activation exists, the SDK never falls into this path on subsequent launches.

---

### `LicenseError.ServerRejected(httpStatus, reason)`

The backend rejected the request for a reason that has no dedicated case (for example `app_blocked`, `ip_blocked`).

**Fix.** The `reason` tag identifies the policy that fired. Contact support for `app_blocked` or `ip_blocked`. For other tags, check the release notes of the SDK version you are on.

---

### `LicenseError.BootstrapFailed(reason)`

Attested bootstrap could not mint a license at `Octet.start`. The `reason` is a `BootstrapReason` naming the cause: the challenge was not recognized or expired, the attestation did not verify, or the app identity is not registered.

**Fix.** Depends on the `reason`.

- **App not registered.** Add the app's identity at [sdk.octetproof.com/apps](https://sdk.octetproof.com/apps), matching the bundle id and Apple Team ID (iOS) or package name and signing-certificate SHA-256 (Android) of the build you are running. A Debug build needs its Debug certificate registered.
- **Attestation did not verify.** Run on a real device. An emulator, simulator, or CI runner has no hardware attestation. For those, register a Debug certificate and pass a [sandbox bypass token](/docs/mobile/concepts/attested-bootstrap/#devices-that-cannot-attest) as `OctetConfig.sandboxBypassToken`.
- **Challenge expired.** On a slow or interrupted first launch, the single-use challenge can expire before it is used. Retry `start`.
- **New devices blocked.** The account accepts no new devices. Open the error's `upgradeUrl` exactly as received, and never build the link yourself. Devices that already activated are unaffected. See [Handling `newDevicesBlocked`](/docs/mobile/api-reference/license-types/#handling-newdevicesblocked).

See [Attested bootstrap](/docs/mobile/concepts/attested-bootstrap/) and [License Types](/docs/mobile/api-reference/license-types/).

---

## Platform-specific issues

### iOS: app crashes on launch with "privacy-sensitive data" error

Missing `Info.plist` key. The error message names the missing key, usually `NSMotionUsageDescription` or `NSLocationWhenInUseUsageDescription`. See [Prerequisites](/docs/mobile/getting-started/prerequisites/).

---

### iOS: SDK works in foreground but stops generating proofs in background

Background location requires two things. First, these `Info.plist` keys:

```xml
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>...</string>
<key>UIBackgroundModes</key>
<array><string>location</string></array>
```

Second, the user must grant `.authorizedAlways` (`.authorizedWhenInUse` is not enough). The SDK silently falls back to foreground-only if either is missing.

---

### Android: SDK refuses to start with location permission granted in manifest

Manifest-level permission is necessary but not sufficient on Android 6 (API 23)+. You must request location at runtime (`ACCESS_FINE_LOCATION` and `ACCESS_COARSE_LOCATION`) and wait for the user to grant precise or approximate location before calling `Octet.start(...)`.

---

### Android: motion classification looks degraded

`ACTIVITY_RECOGNITION` was likely not granted (Android 10+ / API 29+). The SDK keeps working, with lower confidence. Request the permission at runtime alongside location.

---

### Both platforms: `Octet.start(...)` is slow on first launch

Expected on first launch. The first launch includes producing the platform attestation, the attested-bootstrap round-trip (`POST /v1/bootstrap/challenge` then `POST /v1/bootstrap`), and the proof pipeline bring-up. Subsequent launches reuse the cached lease and skip the network call. If first-launch time matters, call `start` from a coroutine or task on app launch and gate UI on its completion.

---

## Session binding

### A session-bound proof shows `NOT-CHECKED` for the session binding

You called a predicate with a `sessionNonce`, but `octet-verify` reports the session binding as `NOT-CHECKED` instead of checking it.

**Fix.** Build `octet-verify` at 1.8.0 or later, the version to run with SDK 3.1. A build older than 1.2.0 does not implement the session-binding stage, so it skips it and validates the rest of the proof. See [Proof Binding](/docs/mobile/concepts/proof-binding/) and the [Verifier Quick Start](/docs/mobile/getting-started/verifier-quickstart/).

---

## See also

- [Verdicts](/docs/mobile/concepts/verdicts/). Full `ReasonCode` taxonomy.
- [License & Activation](/docs/mobile/concepts/license-activation/). Timeline model and activation flow.
- [License Types API Reference](/docs/mobile/api-reference/license-types/). Exact `LicenseError` cases.
- [What's new in 3.0](/docs/mobile/whats-new/). The 3.0 release.
- [Upgrading from 2.x](/docs/mobile/getting-started/upgrading-from-2x/). Breaking changes in 3.0.
