> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Provision and operate eSIM / IoT (M2M) SIMs

> Order, activate, meter, and retire cellular-data SIMs end to end — from plan selection through fleet network-access rules, OTA profiles, quota webhooks, and fleet rollups.

# Provision and operate eSIM / IoT (M2M) SIMs

Programmable wireless brings cellular-data SIMs under API control alongside Orbit's phone numbers. This guide walks the full lifecycle — **order → activate → configure → meter → suspend/resume → terminate** — plus fleets, quotas, and the webhook events that let your backend react to every transition.

Connectivity SIMs are **data-only**. They carry internet traffic; outbound voice and SMS terminate on Orbit's own network, never on a cellular bearer. A SIM can only be ordered against a data-only catalog plan — the order API rejects anything else.

Reads need the `numbers:read` scope; provisioning and lifecycle writes need `numbers:write`. The dashboard surface for everything below is **Numbers → Connectivity**.

## 1. Choose the right line type

|                    | Programmable wireless (SIM)                           | Purchase a DID                            | Port a number                       |
| ------------------ | ----------------------------------------------------- | ----------------------------------------- | ----------------------------------- |
| **What you get**   | A cellular-data SIM identified by an ICCID            | A voice/SMS phone number (E.164)          | An existing number moved onto Orbit |
| **Use case**       | IoT sensors, trackers, POS terminals, eSIM for travel | Voice calls, SMS, fax, WhatsApp           | Keeping a customer-facing number    |
| **Lifecycle**      | `ordered → active ⇄ suspended → terminated`           | Search → purchase → configured            | Submit → port → activate            |
| **Billing driver** | Plan + metered data bytes                             | Monthly line + usage                      | Monthly line + usage                |
| **Endpoint base**  | `/numbers/connectivity/*`                             | `/numbers/available`, `/numbers/purchase` | `/numbers/porting`                  |

If your device needs only data, a SIM is the right object. If you need a reachable phone number for a device (for M2M SMS commands, for example), pair the SIM with a purchased DID.

## 2. Prerequisites

Before ordering a SIM:

* **Funded wallet.** Provisioning charges hit your balance, the same as number purchases.
* **A catalog plan.** Browse `GET /numbers/connectivity/plans` and keep the `id` of an approved plan — e.g. `esim-global-payg` for consumer eSIM lines or `iot-fleet-pooled` for pooled IoT fleets.
* **A webhook endpoint** subscribed to `connectivity_sim.ordered` and `connectivity_sim.activated` (plus the quota events — see section 7). Register it under **Settings → Webhooks**; without it your backend learns about lifecycle transitions only by polling.

## 3. Order a SIM

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/connectivity/sims \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "planId": "iot-fleet-pooled",
    "label": "tracker-nordic-014",
    "fleetId": "fleet-nordic"
  }'
```

| Field      | Required | Description                                                                                                                  |
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `planId`   | yes      | Catalog plan id from `GET /numbers/connectivity/plans`.                                                                      |
| `iccid`    | no       | Provider-assigned ICCID. Omit it and a synthetic 19-digit ICCID is minted — useful before the aggregator assigns a real one. |
| `label`    | no       | Human label, up to 120 characters.                                                                                           |
| `fleetId`  | no       | Fleet to file the SIM under. SIMs in a fleet roll up together (section 9).                                                   |
| `cmpSimId` | no       | Inventory id for automated eSIM provisioning via the CMP connector.                                                          |

The response is `201` with the SIM record:

```json theme={null}
{
  "data": {
    "iccid": "8942790014173625180",
    "planId": "iot-fleet-pooled",
    "state": "ordered",
    "fleetId": "fleet-nordic",
    "usageBytes": 0,
    "otaProfileId": null
  }
}
```

A `connectivity_sim.ordered` webhook fires on success. Ordering the same ICCID twice returns `409` — fetch the existing SIM instead.

## 4. Activate

New SIMs wait in `ordered` until you activate them:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/connectivity/sims/8942790014173625180/activate \
  -H "X-API-Key: dv_live_sk_..."
```

The SIM moves `ordered → active`, a `connectivity_sim.activated` webhook fires, and data sessions start accruing. For eSIM plans you can also issue an **SGP.22 activation** (LPA code + QR payload) for customer self-install — `POST /numbers/connectivity/sims/:iccid/esim/activation` with the aggregator's SM-DP+ address, then read it back with `GET .../esim/activation`.

## 5. Configure fleet network-access rules

Fleets can carry a **Network Access Profile**: an allow/block policy over countries and carrier networks the fleet's SIMs may attach to, enforced on every recorded data session — a denied attach is rejected with `403` and never accrues usage. This is your roaming bill-shock guardrail.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/numbers/connectivity/fleets/fleet-nordic/network-access \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "allowedCountries": ["SE", "NO", "DK", "FI"],
    "blockedCountries": ["US"]
  }'
```

| Field                                   | Description                                                                                             |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `allowedCountries` / `blockedCountries` | ISO 3166-1 alpha-2 lists. An allowlist means only those codes attach; a blocklist is denied regardless. |
| `allowedCarriers` / `blockedCarriers`   | Carrier/network identifiers (names or MCC-MNC pairs), same semantics as the country lists.              |

`GET .../network-access` reads the profile back (`null` when unset — unrestricted attaches), `DELETE .../network-access` clears it. Keep one profile per fleet; SIMs outside any fleet stay unrestricted.

## 6. Apply an OTA profile

An **OTA (over-the-air) profile** pushes a configuration — most often an APN change — to the SIM at runtime, without reissuing hardware. Apply it on an active SIM:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/connectivity/sims/8942790014173625180/ota \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"otaProfileId": "apn-orbital-data"}'
```

Pass `"otaProfileId": null` to clear the applied profile. The SIM record's `otaProfileId` field shows what is currently queued or applied.

## 7. Meter usage and enforce limits

Record each data session against the SIM. Usage accumulates on a monotonic per-SIM counter:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/numbers/connectivity/sims/8942790014173625180/usage \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"bytes": 1048576, "sessionId": "sess_9f2", "networkIso2": "SE"}'
```

The session is rejected (`403`) if it names a country/carrier denied by the fleet's Network Access Profile, and rejected (`422`) on a malformed body.

Set a hard cap and a warning point:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/numbers/connectivity/sims/8942790014173625180/quota \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"dataLimitBytes": 5368709120, "warningThresholdPct": 80}'
```

* `dataLimitBytes` — hard cap. Crossing it suspends the SIM for quota, pending your intervention. `null` clears the cap.
* `warningThresholdPct` — 1–99 percent of the cap where the warning fires. `null` clears it. A threshold without a cap is rejected.

Read the counter back with `GET /numbers/connectivity/sims/:iccid/usage` — it returns cumulative bytes, the active quota, and recent sessions.

Wire the two quota webhook events into your handler:

```javascript theme={null}
app.post('/webhooks/orbit', (req, res) => {
  const event = req.body
  if (event.type === 'connectivity_sim.usage_warning') {
    // event.data: iccid, plan_id, usage_bytes, data_limit_bytes, warning_threshold_bytes
    notifyOps(`SIM ${event.data.iccid} crossed its warning threshold`)
  }
  if (event.type === 'connectivity_sim.limit_exceeded') {
    // Top-up automation: raise the cap, then resume the SIM.
    queueJob(() => topUpAndResume(event.data.iccid))
  }
  res.sendStatus(204)
})
```

The warning event fires once per threshold crossing; the limit event fires when cumulative usage meets or passes the cap and the SIM suspends. Handlers that raise the cap and POST to `/resume` close the loop.

## 8. Suspend, resume, terminate

```bash theme={null}
# Pause an active SIM (e.g. a lost device)
curl -X POST .../numbers/connectivity/sims/8942790014173625180/suspend

# Bring a suspended SIM back
curl -X POST .../numbers/connectivity/sims/8942790014173625180/resume

# Retire the SIM permanently
curl -X POST .../numbers/connectivity/sims/8942790014173625180/terminate
```

`active ⇄ suspended` is reversible; `terminated` is the terminal sink — nothing else can be run against the SIM afterward. Each transition fires its lifecycle webhook (`connectivity_sim.suspended` / `.resumed` / `.terminated`) with the same payload shape: `iccid`, `plan_id`, `state`, `fleet_id`.

## 9. Fleet rollup

SIMs ordered with the same `fleetId` aggregate as a fleet. The bulk read:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/numbers/connectivity/fleets" \
  -H "X-API-Key: dv_live_sk_..."
```

returns one entry per fleet with its member count, cumulative usage, and — for fleets on a pooled plan — the shared-pool health (allowance drawn, remaining, ok/warning/exceeded status). In the dashboard, **Numbers → Connectivity** shows the same fleet cards plus the per-SIM table and the fleet-aggregated usage panel.

Pagination: the SIM list endpoint accepts `?limit=N` (max 1000) and always reports the full filtered `count`, so page by growing `limit` (the dashboard's "Load more" pattern) instead of drifting offsets. Keep `limit` small for the first render and widen in chunks.

Example 1 — Node.js SDK, full lifecycle:

```javascript theme={null}
import { Orbit } from '@devotel/sdk-node'

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY })

async function provisionSim() {
  // Order
  const created = await orbit.request('POST', '/api/v1/numbers/connectivity/sims', {
    data: { planId: 'esim-global-payg', label: 'employee-travel-esim' },
  })
  const iccid = created.data.iccid

  // Activate
  await orbit.request('POST', `/api/v1/numbers/connectivity/sims/${iccid}/activate`)

  // Set quota — 2 GB cap, warn at 85%
  await orbit.request('PATCH', `/api/v1/numbers/connectivity/sims/${iccid}/quota`, {
    data: { dataLimitBytes: 2 * 1024 * 1024 * 1024, warningThresholdPct: 85 },
  })
  return iccid
}
```

Example 2 — Python, mirroring the curl flow:

```python theme={null}
import os, requests

BASE = "https://api.orbit.devotel.io"
HEADERS = {"X-API-Key": os.environ["ORBIT_API_KEY"]}

def provision_sim():
    created = requests.post(
        f"{BASE}/api/v1/numbers/connectivity/sims",
        headers=HEADERS,
        json={"planId": "iot-fleet-pooled", "fleetId": "fleet-nordic"},
    ).json()["data"]
    iccid = created["iccid"]

    requests.post(
        f"{BASE}/api/v1/numbers/connectivity/sims/{iccid}/activate",
        headers=HEADERS,
    )
    return iccid
```

## 10. Troubleshooting

| Symptom                                    | Cause                                                                                                                                        | Fix                                                                                         |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `409` on order — ICCID already provisioned | The ICCID was ordered earlier (re-run, or an aggregator re-assignment)                                                                       | `GET /numbers/connectivity/sims/:iccid` to reuse the existing record, or pick a fresh ICCID |
| `422` on order                             | Malformed plan id / ICCID / fleet id, or a threshold without a cap on `PATCH /quota`                                                         | Fix the invalid field named in the `details` array of the error envelope                    |
| `403` on usage recording                   | Session named a country/carrier denied by the fleet's Network Access Profile                                                                 | Amend `PUT .../fleets/:fleetId/network-access`, or report sessions only on allowed networks |
| `409` on suspend/resume/terminate or quota | SIM is in the wrong state (e.g. already terminated)                                                                                          | Check `GET /numbers/connectivity/sims/:iccid` for the current `state`                       |
| OTA profile stuck queued                   | The SIM must attach for the profile to land                                                                                                  | Verify the SIM is `active` and its fleet profile allows the visit network                   |
| Fleet rollup empty                         | SIMs were ordered without a `fleetId`                                                                                                        | Re-order under a fleet, or treat each orphan SIM individually                               |
| Dashboard fallback                         | The **Numbers → Connectivity** page runs one overseer pass — fleet cards, per-SIM table, and the fleet usage panel — over the same endpoints | Use it to confirm state before writing to the API                                           |

Only tenant-scoped endpoints appear in this guide; internal administration surfaces stay out of scope by design. For the event payloads of every webhook shown here, see [Webhook events](/reference/webhook-events).
