> For the complete documentation index, see [llms.txt](https://docs.idlayr.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.idlayr.com/guides/migration.md).

# Migrate from v0.x to v1

This guide is for teams with a live v0.x integration who want to move to v1. It walks each product one at a time and shows the exact request and response diff.

If you only need the enumerated list of what changed, see [v0.x to v1 breaking changes](/guides/breaking-changes.md). If you're looking for the policy behind the version bump, see [API versioning](/get-started/concepts/api-versioning.md).

Legacy v0.1 and v0.2 endpoints stay operational on security-only maintenance until the end of Q1 2027, so you can migrate on your own timeline.

## Before you start

* **Your existing credentials work for v1.** The same `client_id` and `client_secret` you use today mint tokens for both v0.x and v1. Two things change:
  * The OAuth2 token URL: `/oauth2/v1/token` becomes `/oauth2/token`.
  * v1 introduces additional scopes (`secure_sna`, `sim_swap`, `reverse_sms`, `discovery`, `eligibility`) that you request when minting a token.
* **Confirm the products you use.** The rest of this guide is organised per product; you only need to work through the sections that apply to you.
* **Read** [**v0.x to v1 breaking changes**](/guides/breaking-changes.md) for the full change catalogue in one place.
* **Set up sandbox tests.** Sandbox mode is available for Secure SNA, Reverse SMS, SIM Swap, and Eligibility (by phone number and by mobile IP). Simulated outcomes are driven by the last digits of the submitted MSISDN (or the submitted IP for Eligibility by IP). See [Sandbox testing](/get-started/sandbox-testing.md) for the per-product suffix maps. Discovery does not currently support sandbox mode; if you use Discovery, exercise it against a real device on a supported cellular network in a production project.

**Region placeholder.** All cURL examples below use `{data_residency}` for the host prefix, e.g. `https://{data_residency}.api.idlayr.com`. Replace with the region your project runs in; the current region is shown on your Enterprise Portal project settings.

Skip ahead to your product:

* [PhoneCheck → Secure SNA](#phonecheck-secure-sna)
* [SIMCheck → SIM Swap](#simcheck-sim-swap)
* [MOCheck → Reverse SMS](#mocheck-reverse-sms)
* [Coverage → Eligibility](#coverage-eligibility)
* [DiscoveryCheck → Discovery](#discoverycheck-discovery)

## PhoneCheck → Secure SNA

Secure SNA is the v1 name for PhoneCheck. Same product and same mechanism (device-side verification via mobile network operator attribution). The changes are in the URI, the completion verb, and the removal of billing fields.

**What's the same**

* OAuth2 client-credentials grant against your project's credentials.
* Device-side flow: create the Check on your backend, hand the `url` to your app, the SDK drives the redirect chain on cellular data.
* Callback signing model (JWKS + HTTP Signatures) and the terminal-status enum (`ACCEPTED`, `PENDING`, `COMPLETED`, `EXPIRED`, `ERROR`).
* Request body fields on Create: `phone_number` (required, E.164), `phone_ip`, `reference_id`, `callback_url`, `redirect_url`.
* Response field for the verification result stays `match: true | false`.

**What changed**

* Path: `/phone_check/v0.2/checks` becomes `/v1/number-verification/secure-sna-checks`.
* The completion step. In v0.2 you sent a JSON Patch document to `PATCH /phone_check/v0.2/checks/{check_id}`. In v1 you POST the code directly to a dedicated sub-resource.
* Billing fields (`charge_amount`, `charge_currency`, `snapshot_balance`) are removed from every response and callback.

### Step 1: create the Check

Before (v0.2):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/phone_check/v0.2/checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "callback_url": "https://your.app/callbacks/phone-check",
    "redirect_url": "https://your.app/return"
  }'
```

After (v1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/number-verification/secure-sna-checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "callback_url": "https://your.app/callbacks/secure-sna",
    "redirect_url": "https://your.app/return"
  }'
```

The request body is unchanged. The response carries the same `check_id`, `url`, `status`, and `ttl` fields you use today, minus the billing fields.

### Step 2: drive the device-side redirect

Unchanged. The SDK still opens the `url` returned in step 1 on a pinned cellular connection. See [iOS SDK](/get-started/sdks/ios.md) and [Android SDK](/get-started/sdks/android.md) for the v1 SDK setup.

### Step 3: complete the Check

Before (v0.2): PATCH with a JSON Patch envelope.

```bash
curl -X PATCH https://{data_residency}.api.idlayr.com/phone_check/v0.2/checks/{check_id} \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json-patch+json" \
  -d '[{ "op": "add", "path": "/code", "value": "abc123def456" }]'
```

After (v1): POST the code as a plain JSON body to the `/code` sub-resource.

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/number-verification/secure-sna-checks/{check_id}/code \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "code": "abc123def456" }'
```

The response returns the completed `SecureSnaCheck` with `status: COMPLETED` and `match: true` or `false`.

### Step 4: handle the callback

The callback body has the same shape and signing as v0.x. The `x-idlayr-callback` header identifies the product; the value is `secure_sna` in v1. If you have one callback receiver serving multiple products, update its discriminator.

### iOS SDK

Replace the legacy import and package.

|         | Before                     | After                                                                      |
| ------- | -------------------------- | -------------------------------------------------------------------------- |
| Package | `tru-sdk-ios` (public SPM) | `IDlayrKit` (private Cloudsmith Swift Package Registry or CocoaPods Specs) |
| Import  | `import TruSDK`            | `import IDlayrKit`                                                         |
| Method  | `TruSDK().check(url)`      | `IDlayrSDK().checkWithDataCellular(url:)`                                  |

You'll also need a Cloudsmith entitlement token in your build, set up during onboarding. See [iOS SDK](/get-started/sdks/ios.md) for the full snippet.

### Android SDK

|             | Before                                             | After                                                                                                                   |
| ----------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Coordinates | `id.tru.sdk:tru-sdk-android` (public GitLab Maven) | `com.idlayr:idlayr-sdk-android` (private Cloudsmith Maven, also on Maven Central from 2.3.0)                            |
| Import      | `import id.tru.sdk.*`                              | `import com.idlayr.sdk.*`                                                                                               |
| Init        | `TruSDK.initializeSdk(context)` at app start       | Auto-initialises via a `ContentProvider`. Remove the `initializeSdk(...)` call. Use `IDlayrSDK.getInstance()` directly. |

See [Android SDK](/get-started/sdks/android.md) for the current version and the Gradle snippet.

## SIMCheck → SIM Swap

Rename and billing-field removal. Request shape, response shape, callback shape, and product semantics are unchanged; only the URI moves and the response no longer carries billing metadata.

**What changed**

* Path: `/sim_check/v0.1/checks` becomes `/v1/sim-swap/checks`.
* Billing fields (`charge_amount`, `charge_currency`, `snapshot_balance`) are removed from the response.

### Step 1: create the Check

Before (v0.1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/sim_check/v0.1/checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "period": 10080
  }'
```

After (v1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/sim-swap/checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "period": 10080
  }'
```

The `period` semantics are unchanged: minutes to look back for a SIM change, defaulting to 10080 (7 days), range 60 to 540000. The response returns `no_sim_change` with the same meaning as v0.x.

### Step 2: interpret the response

Fields on the response (`check_id`, `status`, `no_sim_change`, `created_at`) are unchanged. Only the billing fields are gone.

## MOCheck → Reverse SMS

Reverse SMS is the v1 name for MOCheck. The device flow (user opens the native SMS composer with the values your backend returns and taps Send) is exactly what it was in v0.x. What changes is the URI, a set of field renames on the create response, and the removal of billing fields.

**What changed**

* Path: `/mo_check/v0.1/checks` becomes `/v1/number-verification/reverse-sms-checks`.
* Two response fields are renamed on the create response:
  * `mo_receiver` becomes `receiver_phone_number`.
  * `mo_body` becomes `expected_sms_body`.
* The verification result field on completed checks is renamed:
  * `verified` becomes `match`.
* Billing fields (`charge_amount`, `charge_currency`, `snapshot_balance`) are removed from every response and callback.
* Traces endpoints (`/mo_check/v0.1/checks/{check_id}/traces` and the individual-trace variant) are removed. Per-check trace inspection moves to the [Enterprise Portal verification logs](/enterprise-portal/verification-logs.md).

### Step 1: create the Check

Before (v0.1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/mo_check/v0.1/checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "callback_url": "https://your.app/callbacks/mo-check"
  }'
```

Sample response (v0.1):

```json
{
  "check_id": "c69bc0e6-a429-11ea-bb37-0242ac130002",
  "status": "ACCEPTED",
  "mo_receiver": "+441234000000",
  "mo_body": "PLEASE TAP TO SEND ... abc123",
  "charge_amount": 1,
  "charge_currency": "API"
}
```

After (v1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/number-verification/reverse-sms-checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+447700900000",
    "callback_url": "https://your.app/callbacks/reverse-sms"
  }'
```

Sample response (v1):

```json
{
  "check_id": "c69bc0e6-a429-11ea-bb37-0242ac130002",
  "status": "ACCEPTED",
  "receiver_phone_number": "+441234000000",
  "expected_sms_body": "PLEASE TAP TO SEND ... abc123",
  "ttl": 120
}
```

Update your backend to read `receiver_phone_number` where it used to read `mo_receiver`, and `expected_sms_body` where it used to read `mo_body`. Your backend must persist these values if it needs them later; subsequent GETs do not echo them back.

### Step 2: open the SMS composer on the device

Unchanged pattern from v0.x. Your backend passes the destination and body down to your app over your own application API. The app opens the native SMS composer via the standard `sms:` URL scheme:

```
sms:{receiver_phone_number}?body={url-encoded-expected_sms_body}
```

For example on iOS:

```swift
let destination = "+441234000000"  // receiver_phone_number
let body = "PLEASE TAP TO SEND ... abc123"  // expected_sms_body
let encoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
if let url = URL(string: "sms:\(destination)?body=\(encoded)") {
    UIApplication.shared.open(url)
}
```

And on Android:

```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:$destination"))
intent.putExtra("sms_body", body)
startActivity(intent)
```

The user reviews and taps Send. From there the platform-carrier-IDlayr chain is unchanged from v0.x.

### Step 3: handle the callback

Same shape and signing as before. `x-idlayr-callback` header value becomes `reverse_sms`. The verification result field is renamed from `verified` to `match` (see above). Update your callback handler accordingly.

## Coverage → Eligibility

Coverage becomes Eligibility. The two backend lookup endpoints are kept and renamed with no shape change. Two other legacy endpoints are removed with no v1 equivalent.

**What's the same**

* Backend-only, no SDK.
* The two lookups: by phone number, and by mobile IP address. Both were GET in v0.x and are still GET in v1, with the value in the path.
* The three-gate model (connectivity, authorisation, product support). See [Eligibility → How it works](/products/eligibility/how-it-works.md).

**What changed — kept endpoints (URL rename only)**

* `GET /coverage/v0.1/phone_numbers/{phone_number}` becomes `GET /v1/eligibility/phone-numbers/{phone_number}`. Same shape; note the leading `+` on the phone number is required in v1.
* `GET /coverage/v0.1/device_ips/{ip}` becomes `GET /v1/eligibility/device-ips/{ip}`. Same shape.

**What changed — removed endpoints**

* `GET /coverage/v0.1/countries/{code}` (Get Country Coverage) is removed. Country-level coverage listings are no longer exposed on the API surface. Talk to your account team if you need the equivalent data for capacity planning.
* `GET /coverage/v0.1/device_ip` (Get Device Reachability) is removed. This endpoint was designed to be called from the device over cellular data; the platform resolved eligibility from the IP of the incoming connection with no argument in the request. There is no direct v1 replacement — the closest pattern is a device-assisted `GET /v1/eligibility/device-ips/{ip}` in which the device reports its own IP to your backend and your backend calls the endpoint. See [Eligibility → Integration](/products/eligibility/integration.md) for that pattern.
* `GET /public/coverage/v0.1/device_ip` (the unauthenticated public variant) is removed with no replacement.

### Step 1: look up by phone number

Before (v0.1):

```bash
curl https://{data_residency}.api.idlayr.com/coverage/v0.1/phone_numbers/447700900000 \
  -H "Authorization: Bearer {access_token}"
```

After (v1):

```bash
curl https://{data_residency}.api.idlayr.com/v1/eligibility/phone-numbers/+447700900000 \
  -H "Authorization: Bearer {access_token}"
```

Note the leading `+` on the phone number in the path in v1. The response returns the matched network and the list of eligible IDlayr products for it.

### Step 2: look up by IP

Before (v0.1):

```bash
curl https://{data_residency}.api.idlayr.com/coverage/v0.1/device_ips/192.0.2.42 \
  -H "Authorization: Bearer {access_token}"
```

After (v1):

```bash
curl https://{data_residency}.api.idlayr.com/v1/eligibility/device-ips/192.0.2.42 \
  -H "Authorization: Bearer {access_token}"
```

URL rename only. Response shape carries over: matched network plus eligible products.

## DiscoveryCheck → Discovery

Discovery is the v1 name for the legacy DiscoveryCheck endpoint. Same product and same mechanism: the platform discovers the device's MSISDN from the cellular session, and the discovered MSISDN is returned to your backend when it submits the completion code. What changes is the URI and the completion verb (in the same way Secure SNA changed).

**How the flow actually works** (same in v0.x and v1):

1. Your backend creates the check.
2. Your app hands the `url` to the IDlayr SDK.
3. The SDK follows the redirect on cellular data. IDlayr attributes the MSISDN. The SDK gets a `code` back at the end of the redirect chain.
4. Your app sends the `code` to your backend over your own application API.
5. Your backend submits the `code` to IDlayr. IDlayr returns the discovered MSISDN in the response.

**The device never sees the MSISDN.** Only your backend does. Any user-facing display of the number (show full, show masked, skip entirely) happens after your backend has fetched it and passed it back down to the app. That's a customer-side choice, not a step in the IDlayr protocol.

**What's the same**

* The device-side redirect flow driven by the SDK on cellular data.
* The SDK returns a `code` to the app, and the app hands it to your backend.
* The create payload takes no phone number (the point of Discovery is to discover it).
* Terminal-status enum and the callback signing model.
* Response field carrying the discovered number stays `phone_number` (E.164, present when `status: COMPLETED`).

**What changed**

* Path: `/discovery_check/v0.1/checks` becomes `/v1/number-verification/discovery-checks`.
* Completion step: `PATCH /discovery_check/v0.1/checks/{check_id}` with a JSON Patch document becomes `POST /v1/number-verification/discovery-checks/{check_id}/code` with a plain `{ "code": "..." }` body.

### Step 1: create the Check

Before (v0.1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/discovery_check/v0.1/checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://your.app/callbacks/discovery",
    "redirect_url": "https://your.app/return"
  }'
```

After (v1):

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/number-verification/discovery-checks \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://your.app/callbacks/discovery",
    "redirect_url": "https://your.app/return"
  }'
```

Request body is unchanged. Response returns `check_id`, `url`, `status`, and `ttl`.

### Step 2: drive the device-side redirect

Unchanged. The v1 IDlayr SDK opens the `url` returned in step 1 on a pinned cellular connection and follows the redirect chain. At the end of the chain the SDK receives a `code`, which it hands back to your app. See [iOS SDK](/get-started/sdks/ios.md) and [Android SDK](/get-started/sdks/android.md).

### Step 3: pass the code from the device to your backend

Unchanged. Your app sends the `code` to your backend over your own application API. The device does not talk directly to IDlayr's completion endpoint; that call is made by your backend so it can present the OAuth2 access token.

### Step 4: submit the code from your backend

Before (v0.1): PATCH with a JSON Patch envelope.

```bash
curl -X PATCH https://{data_residency}.api.idlayr.com/discovery_check/v0.1/checks/{check_id} \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json-patch+json" \
  -d '[{ "op": "add", "path": "/code", "value": "abc123def456" }]'
```

After (v1): POST the code as a plain JSON body to the `/code` sub-resource.

```bash
curl -X POST https://{data_residency}.api.idlayr.com/v1/number-verification/discovery-checks/{check_id}/code \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "code": "abc123def456" }'
```

The response returns the completed `DiscoveryCheck` with `status: COMPLETED` and the discovered `phone_number`. Your backend now has the MSISDN. Whether you pass it back down to the app for a confirmation UX, mask it, or use it silently is a customer-side decision.

### Step 5: handle the callback

`x-idlayr-callback` header value becomes `discovery` in v1. Callback body shape and signing carry over. The callback carries the discovered `phone_number` alongside the terminal `status`.

## Cross-cutting changes

These apply regardless of which products you use.

### OAuth2 token endpoint

Before: `POST /oauth2/v1/token`. After: `POST /oauth2/token`.

The credentials themselves (your project's `client_id` and `client_secret`) are unchanged; use the same pair. When minting a token for v1 traffic, request the v1 scope for each product you're calling (`secure_sna`, `sim_swap`, `reverse_sms`, `discovery`, `eligibility`).

```bash
curl -X POST https://{data_residency}.api.idlayr.com/oauth2/token \
  -u "{client_id}:{client_secret}" \
  -d "grant_type=client_credentials" \
  -d "scope=secure_sna sim_swap reverse_sms discovery eligibility"
```

### Error responses (RFC 7807)

All v1 errors are `application/problem+json` with a stable `error_code` field and a `type` URI that resolves to [docs.idlayr.com/api-reference/errors](https://docs.idlayr.com/api-reference/errors). If your v0.x error handler was reading a legacy JSON envelope, adapt it to the problem-detail shape.

```json
{
  "type": "https://docs.idlayr.com/api-reference/errors#mno_not_supported",
  "title": "Bad Request",
  "status": 400,
  "detail": "Mobile Network Operator not supported for this product.",
  "error_code": "mno_not_supported"
}
```

### Callback signing

Unchanged. Your v0.x callback signature verification (JWKS + HTTP Signatures, verifying `(request-target) host date x-idlayr-callback digest`) continues to work against v1 callbacks. What changes is the `x-idlayr-callback` header value, which now uses v1 product names (`secure_sna`, `sim_swap`, `reverse_sms`, `discovery`).

## Cutover strategies

Pick the strategy that matches the risk profile of your integration.

### Cold cutover

Small integrations (one product, low traffic, short cutover window): change every path and product name in a single deploy, ship, and watch. The safety net is that v0.x endpoints stay operational until the end of Q1 2027, so a rollback is just reverting the deploy.

### Dual-run

Any integration where you cannot afford a bad cutover: call both v0.x and v1 in parallel from your backend, compare results, and shift traffic to v1 in stages. Run for at least a week of production traffic before decommissioning v0.x.

### Per-product incremental

Multi-product integrations: migrate one product at a time (usually easiest ordering is Eligibility, then SIM Swap, then Reverse SMS, then Secure SNA, then Discovery). Deploy after each product is verified. This spreads the risk across multiple cutovers and lets you learn from each.

## Testing your migration

For each product you migrate:

* **Sandbox first.** Sandbox is available for Secure SNA, Reverse SMS, SIM Swap, and Eligibility. Use the per-product input suffixes documented on [Sandbox testing](/get-started/sandbox-testing.md) — MSISDN for the phone-number-input products, mobile IP for Eligibility by IP — to drive deterministic outcomes (match, no-match, error, expired). Discovery does not yet have a sandbox contract; if you're migrating Discovery, test against a real device in production mode.
* **Verify the callback shape.** Point the sandbox callback at a staging receiver and confirm the payload structure matches your parser before you switch production traffic.
* **Confirm error paths.** RFC 7807 problem detail is a shape change. Trigger an error deliberately (bad phone number format, unsupported MNO) and confirm your handler reads the new envelope.
* **Check terminal-state timing.** If you rely on the callback firing within a certain window, replay a sample sandbox check and measure end-to-end.

## Rolling back

If v1 misbehaves in production after cutover, revert the endpoint paths in your backend to their v0.x equivalents and redeploy. Your existing credentials continue to work against v0.x and your v0.x callback URLs continue to receive traffic; nothing on the platform side needs a "roll back" action. Contact your account team if you observe an outage or a consistent v1-specific bug so we can triage before you commit to a rollback.

## What isn't changing

Explicit reassurance on what stays the same:

* The OAuth2 grant type (client-credentials), and the `client_id` / `client_secret` you already use.
* The callback signing model (JWKS, HTTP Signatures over `(request-target) host date x-idlayr-callback digest`).
* The terminal-status enum values (`ACCEPTED`, `PENDING`, `COMPLETED`, `EXPIRED`, `ERROR`).
* The `check_id` UUID format and its role as your durable handle on a Check.
* Data residency: EU and US regions continue to operate independently.
* Sandbox behaviour (deterministic outcomes driven by input suffix — MSISDN or mobile IP depending on product).

## Support

If you'd like help planning the migration for your specific integration, contact your IDlayr account team; see [Support](https://docs.idlayr.com/help-center/support).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.idlayr.com/guides/migration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
