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

# Upgrading from 2.x

3.0 is a major release. This page lists what changes for an app moving from 2.0.0, breaking changes first. The 2.0 docs stay at [/docs/v2.0/](/docs/v2.0/). For the full list, see [What's new in 3.0](/docs/whats-new/).

## 1. Update the version

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

```swift
.package(url: "https://github.com/octetproof/octet-sdk-ios", exact: "3.0.0")
```

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

```kotlin
implementation("com.octetproof:sdk:3.0.0")
```

  </TabItem>
</Tabs>

If you verify proofs yourself with `octet-verify`, run 1.5.0 or later. To enforce a verdict tier on your backend with `--require-verdict`, run 1.6.0 or later.

## 2. Review the breaking changes

### Usage reporting is always on

After the first proof of each UTC day, the SDK sends one `device_active` usage record with the next background heartbeat, so Octet can count active devices for billing. The record carries no location, no proof data, no user or account identity and no new device identifier. It never blocks or delays a proof.

`OctetConfig.creditServiceUrl`, previously a reserved opt-in field, now defaults to `https://credits.octetproof.com` and only overrides the endpoint for testing. Leaving it unset, or setting it to `nil` / `null`, no longer disables anything. The default is exposed as `OctetConfig.defaultCreditServiceUrl` (iOS) and `OctetConfig.DEFAULT_CREDIT_SERVICE_URL` (Android). `CreditStatus` gains an optional `reason`.

Update your app's privacy disclosures to include the daily usage record. See [Data collection](/docs/concepts/data-collection/) and [Metrics and usage reporting](/docs/concepts/metrics/).

### US territories claim their own country

A device in Puerto Rico, Guam, the US Virgin Islands, American Samoa, the Northern Mariana Islands or the US Minor Outlying Islands now claims that territory's ISO 3166 code: `PR`, `GU`, `VI`, `AS`, `MP` or `UM`. A `country("US")` query answers `NO` there. To accept those users, add the territory codes to your region list.

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

```swift
let accepted = ["US", "PR", "GU", "VI", "AS", "MP", "UM"]
for code in accepted {
    let v = await sdk.loc.isWithin(region: .country(isoCode: code))
    if v.result == .yes { return v }
}
```

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

```kotlin
val accepted = listOf("US", "PR", "GU", "VI", "AS", "MP", "UM")
for (code in accepted) {
    val v = sdk.loc.isWithin(OctetRegion.country(code))
    if (v.result == OctetVerdict.Result.YES) return v
}
```

  </TabItem>
</Tabs>

### Country and state claims follow real borders

The SDK claims a country or a state only when the device is clearly inside it. Close to a border, or when the location fix is too coarse to tell, a country or state query returns `INDETERMINATE` with reason `regionUnresolved` (`REGION_UNRESOLVED` on Android) where 2.0.0 answered `YES` or `NO`. Handle that reason as a benign gap, for example with a retry or a step-up check. See [Verdicts](/docs/concepts/verdicts/#reason-codes).

State claims now also work outside the US, starting with Ukraine and Russia. Crimea, Sevastopol, Donetsk, Luhansk, Zaporizhzhia and Kherson always claim `UA`.

### A pinned `claimedRegion` must be an assigned ISO 3166 code

A pinned `claimedRegion` whose code is malformed or not assigned in ISO 3166 produces no proof, and the verdict reason is `regionUnresolved`. 2.0.0 signed it.

### Android: a debugger refuses proofs outside a sandbox session

An attached debugger or native tracer now refuses a proof with `TAMPERING`, as on iOS. A sandbox session (`sandboxBypassToken` set) still allows one, so debugging against the sandbox keeps working.

### Android: `BootstrapReason.NewDevicesBlocked`

`BootstrapReason` gains `NewDevicesBlocked`. A `when (reason)` over `BootstrapReason` without an `else` branch no longer compiles. Add the new case or an `else` branch:

```kotlin
try {
    val sdk = Octet.start(context = applicationContext, config = config)
} catch (e: LicenseError.BootstrapFailed) {
    when (e.reason) {
        // Open the server-signed link exactly as received.
        BootstrapReason.NewDevicesBlocked -> e.upgradeUrl?.let { openInBrowser(it) }
        else -> showStartupError(e)
    }
}
```

On iOS the new case is `.newDevicesBlocked(upgradeUrl:)`. A `switch` with a `default` keeps compiling. See [License Types](/docs/api-reference/license-types/#bootstrapreason).

### Android: Kotlin binary compatibility

`AdvancedConfig` and `LicenseStatus` gained constructor parameters with defaults. Source that uses named arguments compiles unchanged. Code compiled against 2.0.0 that calls their constructors or `copy` must be recompiled.

## 3. Check the behavior changes

- **iOS: direct mode uses `ipwho.is` for IP location instead of `ipapi.co`.** Add `ipwho.is` to any network allowlist, or set `TransportPolicy.thirdParty` to `.disable`.
- **The first proof after `Octet.start` waits for startup to finish**, for up to 30 s, instead of returning `noFix` / `NO_FIX`. If your app applies its own timeout to the first call, allow for this.
- **On a fresh install, `Octet.start` can take a few seconds longer** while it retries the first remote-configuration fetch.
- **`achievableLevel` reports `subdivision`, never `city`**, when the fix is too coarse for the requested level.
- **A cached activation is reused only with the same licence and server.** Starting offline with an activation from a different licence or server fails with `noActivation` / `LicenseError.NoActivation`.
- **TLS pins.** The built-in pins for `api.octetproof.com` and `gw.octetproof.com` are updated. If you turned on certificate pinning with 2.0.0, upgrade to 3.0.0.

## 4. Adopt what is new

- **Gateway routing.** Route every SDK network call through your own HTTPS host or through `gw.octetproof.com` with `OctetConfig.advanced.transport`. See [Gateway routing](/docs/concepts/gateway-routing/).
- **Assurance tier.** Read `LocationProof.spoofingVerdict` to decide what a `YES` is worth. See [`OctetVerdict`](/docs/api-reference/octet-verdict/#the-assurance-tier).
- **Verdict tier on verification.** `VerifyOptions.requireVerdict` fails a proof below the tier you require. See [`Octet.verify`](/docs/api-reference/octet-verify/).
- **`PLAUSIBLE` containment.** Turn on `OctetConfig.advanced.acceptPlausibleContainment` to let disc queries answer from a `PLAUSIBLE` proof. See [Predicates](/docs/api-reference/predicates/#plausible-containment).
- **Upgrade link on new-device refusal.** Handle `newDevicesBlocked` and open its `upgradeUrl`. See [License Types](/docs/api-reference/license-types/#bootstrapreason).
