# API reference

> Every endpoint, generated from the API's OpenAPI documents. Authenticate with `Authorization: Bearer <key>`.

## IoT API

Base URL `https://partners.netavo.com/api`. OpenAPI document: https://thingsim.com/developers/api/v1-iot.openapi.json

### List plans

`GET https://partners.netavo.com/api/v1/iot/plans` · scope `iot.plans:read`

The plans your account can put a SIM on, with your prices (ex VAT). Use a plan's `id` with
the change-plan endpoint. A SIM can only move to a plan on the coverage it is already on.

Parameters:

- `coverage` (query, string): Only plans with this coverage key. Example: `3e8d1c5a-7f2b-4a90-b6e4-1d0c9f8a2b37`.
- `organisationId` (query, string (uuid)): Partners: the plans and prices of a customer organisation below you. Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.
- `iccid` (query, string): Only the plans this SIM can move to (its coverage, at its holder's prices). Other filters are ignored. Example: `8944110068212345678`.

```bash
curl "https://partners.netavo.com/api/v1/iot/plans" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: Every plan available to you.

```json
{
  "data": [
    {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "coverage": {
        "key": "3e8d1c5a-7f2b-4a90-b6e4-1d0c9f8a2b37",
        "label": "UK 4-network",
        "networks": [
          "EE",
          "O2",
          "Three",
          "Vodafone"
        ]
      },
      "allowanceBytes": 1073741824,
      "monthlyPrice": {
        "amount": "2.40",
        "currency": "GBP",
        "includesVat": false
      },
      "billingIntervals": [
        "monthly"
      ]
    }
  ]
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.plans:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List SIMs

`GET https://partners.netavo.com/api/v1/iot/sims` · scope `iot.sims:read`

Every SIM in your account, ordered by ICCID. Partners see SIMs across their customer
organisations; `organisationId` narrows to one of them.

Parameters:

- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.
- `status` (query, string): Only SIMs in this status, e.g. `active` or `paused`. Example: `active`.
- `tag` (query, string): Only SIMs carrying this tag. Example: `site:leeds`.
- `organisationId` (query, string (uuid)): Partners: only SIMs held by this organisation (one of yours, or a customer below you). Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.

```bash
curl "https://partners.netavo.com/api/v1/iot/sims?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of SIMs.

```json
{
  "data": [
    {
      "iccid": "8944110068212345678",
      "label": "Car park barrier 3",
      "tags": [
        "site:leeds",
        "barrier"
      ],
      "status": "active",
      "suspendedReason": null,
      "canResume": false,
      "formFactor": "triSim",
      "plan": {
        "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
        "name": "1 GB, UK 4-network",
        "allowanceBytes": 1073741824
      },
      "msisdn": "447700900123",
      "imsi": "234500012345678",
      "ipAddress": "10.64.12.34",
      "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
      "activatedAt": "2026-09-01T08:15:00Z",
      "activationScheduledFor": null,
      "stopsAt": null,
      "createdAt": "2026-08-20T14:02:11Z",
      "updatedAt": "2026-09-24T17:40:03Z",
      "canActivate": false,
      "planStartedAt": "2026-09-01T00:00:00Z",
      "scheduledPlanChange": {
        "plan": null,
        "effectiveAt": "2026-10-01T00:00:00Z"
      }
    }
  ],
  "hasMore": true,
  "nextCursor": "c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get a SIM

`GET https://partners.netavo.com/api/v1/iot/sims/{iccid}` · scope `iot.sims:read`

One SIM by ICCID.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.

```bash
curl "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The SIM.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Update a SIM

`PATCH https://partners.netavo.com/api/v1/iot/sims/{iccid}` · scope `iot.sims:write`

Sets the SIM's label and tags. Leave a field out (or send null) to keep it; `"label": ""`
clears the label and `"tags": []` removes every tag. Tags are de-duplicated ignoring case;
at most 20 of up to 50 characters each.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.

Body (SimUpdate):

- `label` (string | null): New label; `""` clears it. Null or left out keeps it (so a client that always sends every
field cannot wipe a label by accident). Example: `Car park barrier 3`.
- `tags` (string[] | null): Replaces all tags; `[]` removes them. Null or left out keeps them. Example: `["site:leeds","barrier"]`.

```bash
curl -X PATCH "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ]
}'
```

Response 200: The updated SIM.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:write` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Activate a SIM

`POST https://partners.netavo.com/api/v1/iot/sims/{iccid}/activate` · scope `iot.sims:write`

Starts service on an inactive SIM that has a plan. Activation is scheduled for the next
working day (`activationScheduledFor`); billing starts when it takes effect and
`iot.sim.activated` is sent then.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/activate" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 202: Accepted. The SIM is returned in its pending state; a notification follows when the change takes effect.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:write` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The SIM is not in a state this action applies to); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Cease a SIM

`POST https://partners.netavo.com/api/v1/iot/sims/{iccid}/cease` · scope `iot.sims:cease`

Permanently ends service on a SIM. The SIM keeps working, and billing, until the end of the
period already paid for (`stopsAt`); nothing is refunded, and an annual SIM stays billed to
the end of its paid year. `iot.sim.ceased` is sent when it stops. A SIM that has never
started billing is withdrawn at once. Needs its own scope because it cannot be undone.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

Body (SimCeaseRequest):

- `confirmIccid` (string, required): Must equal the ICCID in the path. Example: `8944110068212345678`.
- `reason` (string | null): Your note, kept in our logs of the SIM's history. Example: `Barrier removed`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/cease" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14" \
  -H "Content-Type: application/json" \
  -d '{
  "confirmIccid": "8944110068212345678",
  "reason": "Barrier removed"
}'
```

Response 202: Accepted. The SIM is returned as `pendingCease` with its `stopsAt`.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:cease` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The SIM is not in a state this action applies to); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Pause a SIM

`POST https://partners.netavo.com/api/v1/iot/sims/{iccid}/pause` · scope `iot.sims:write`

Stops the SIM passing data until you resume it. The plan keeps running and billing, and
`iot.sim.paused` is sent when the network has applied it. Pausing a SIM that is already
paused or suspended changes nothing.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/pause" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 202: Accepted. The SIM is returned in its pending state; a notification follows when the change takes effect.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:write` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The SIM is not in a state this action applies to); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Change a SIM's plan

`POST https://partners.netavo.com/api/v1/iot/sims/{iccid}/plan` · scope `iot.sims:write`

Moves the SIM to another plan your account can use (`GET /v1/iot/plans`). A plan with at
least as much data is an upgrade: it applies now and the difference for the rest of the
period is charged now. A smaller plan is a downgrade and applies at the next renewal
(`effectiveAt`). `iot.sim.plan_changed` is sent when it takes effect.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

Body (PlanChangeRequest):

- `planId` (string, required): A plan id from `GET /v1/iot/plans`. Example: `9c7a2e14-3b5d-4f60-8e21-6a4b0d9c3f75`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/plan" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14" \
  -H "Content-Type: application/json" \
  -d '{
  "planId": "9c7a2e14-3b5d-4f60-8e21-6a4b0d9c3f75"
}'
```

Response 200: The plan change.

```json
{
  "iccid": "8944110068212345678",
  "fromPlan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "toPlan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "direction": "upgrade",
  "effectiveAt": "2026-09-25T00:00:00Z",
  "message": "Upgraded to 5 GB, UK 4-network; the difference for the rest of the period is charged now."
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:write` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The SIM is not in a state this action applies to); 422 plan-not-eligible (The plan is not one your account can put this SIM on. Or: The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Resume a SIM

`POST https://partners.netavo.com/api/v1/iot/sims/{iccid}/resume` · scope `iot.sims:write`

Restarts data on a SIM you paused. SIMs suspended for another reason (non-payment, or by
our operations team) cannot be resumed here: `409` says why. `iot.sim.resumed` is sent when
the network has applied it.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/resume" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 202: Accepted. The SIM is returned in its pending state; a notification follows when the change takes effect.

```json
{
  "iccid": "8944110068212345678",
  "label": "Car park barrier 3",
  "tags": [
    "site:leeds",
    "barrier"
  ],
  "status": "active",
  "suspendedReason": null,
  "canResume": false,
  "formFactor": "triSim",
  "plan": {
    "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "name": "1 GB, UK 4-network",
    "allowanceBytes": 1073741824
  },
  "msisdn": "447700900123",
  "imsi": "234500012345678",
  "ipAddress": "10.64.12.34",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "activatedAt": "2026-09-01T08:15:00Z",
  "activationScheduledFor": null,
  "stopsAt": null,
  "createdAt": "2026-08-20T14:02:11Z",
  "updatedAt": "2026-09-24T17:40:03Z",
  "canActivate": false,
  "planStartedAt": "2026-09-01T00:00:00Z",
  "scheduledPlanChange": {
    "plan": {
      "id": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
      "name": "1 GB, UK 4-network",
      "allowanceBytes": 1073741824
    },
    "effectiveAt": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.sims:write` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The SIM is not paused by you); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List a SIM's sessions

`GET https://partners.netavo.com/api/v1/iot/sims/{iccid}/sessions` · scope `iot.usage:read`

Data sessions, newest first: the session still open (if any), then those that ended in the
window (default the last 30 days; at most 93 days).

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.
- `from` (query, string (date-time)): Only sessions that ended at or after this time. Default: 30 days ago. Example: `2026-09-01T00:00:00Z`.
- `to` (query, string (date-time)): Only sessions that ended before this time. Default: now. Example: `2026-09-25T00:00:00Z`.

```bash
curl "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/sessions?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of sessions.

```json
{
  "data": [
    {
      "id": "ses_4f1c2a9e7b3d",
      "iccid": "8944110068212345678",
      "startedAt": "2026-09-25T06:02:41Z",
      "endedAt": "2026-09-25T08:47:12Z",
      "bytesUp": 1843200,
      "bytesDown": 7340032,
      "network": {
        "mcc": "234",
        "mnc": "15",
        "name": "Vodafone UK"
      },
      "radioAccess": "4G",
      "ipAddress": "10.64.12.34",
      "endReason": "userRequest"
    }
  ],
  "hasMore": true,
  "nextCursor": "c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.usage:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get a SIM's usage

`GET https://partners.netavo.com/api/v1/iot/sims/{iccid}/usage` · scope `iot.usage:read`

Data used in the current allowance period (a calendar month), including the session still
open, with up to twelve earlier periods in `history` — or one past month with `period`.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068212345678`.
- `period` (query, string): A past month as `yyyy-mm`. Leave out for the current period. Example: `2026-08`.

```bash
curl "https://partners.netavo.com/api/v1/iot/sims/8944110068212345678/usage" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: Usage for the period.

```json
{
  "iccid": "8944110068212345678",
  "periodStart": "2026-09-01T00:00:00Z",
  "periodEnd": "2026-10-01T00:00:00Z",
  "allowanceBytes": 1073741824,
  "usedBytes": 612368384,
  "remainingBytes": 461373440,
  "percentUsed": 57,
  "allowanceReached": false,
  "updatedAt": "2026-09-25T09:15:00Z",
  "history": [
    {
      "periodStart": "2026-08-01T00:00:00Z",
      "periodEnd": "2026-09-01T00:00:00Z",
      "allowanceBytes": 1073741824,
      "usedBytes": 803209216
    }
  ]
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.usage:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List fleet usage

`GET https://partners.netavo.com/api/v1/iot/usage` · scope `iot.usage:read`

Each SIM's data use in its current allowance period, ordered by ICCID: allowance, data used
(including the session still open), whether it is online now, and — while it is stopped for
having used its allowance — when it starts working again.

Parameters:

- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.
- `iccid` (query, string): Only this SIM. Example: `8944110068212345678`.
- `tag` (query, string): Only SIMs with this tag. Example: `fleet-north`.
- `organisationId` (query, string (uuid)): Partners: only this customer organisation's SIMs. Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.

```bash
curl "https://partners.netavo.com/api/v1/iot/usage?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of usage summaries.

```json
{
  "data": [
    {
      "iccid": "8944110068212345678",
      "allowanceBytes": 1073741824,
      "usedBytes": 52428800,
      "barredUntil": null,
      "online": true
    }
  ],
  "hasMore": true,
  "nextCursor": "c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.usage:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get an eSIM activation code

`GET https://partners.netavo.com/api/v1/iot/sims/{iccid}/esim` · scope `iot.esim:read`

The activation code and QR code for an eSIM profile, read from the network each time. Treat
the code like a password: anyone with it can download the profile until it is installed.

Parameters:

- `iccid` (path, string, required): The SIM's ICCID, 19 or 20 digits, as printed on the card. Example: `8944110068298765432`.

```bash
curl "https://partners.netavo.com/api/v1/iot/sims/8944110068298765432/esim" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The activation details.

```json
{
  "iccid": "8944110068298765432",
  "activationCode": "LPA:1$smdp.example.net$K2-4N7Q2-1XZ9PQ",
  "smdpAddress": "smdp.example.net",
  "matchingId": "K2-4N7Q2-1XZ9PQ",
  "qrCodeSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 41 41\">…</svg>"
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `iot.esim:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 not-an-esim (The SIM is a physical SIM, not an eSIM); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds); 502 carrier-error (The network did not return an activation code. Retry later).

### IoT schemas

#### BillingInterval

How often a plan can be billed.

One of: `monthly`, `annual`


#### Coverage

Which networks a plan can use.

- `key` (string, required): The coverage's id; filter plans by it with `?coverage=`. Example: `3e8d1c5a-7f2b-4a90-b6e4-1d0c9f8a2b37`.
- `label` (string, required) Example: `UK 4-network`.
- `networks` (string[], required): The UK networks it permits. Example: `["EE","O2","Three","Vodafone"]`.

#### EsimActivation

What a device needs to download its eSIM profile.

- `iccid` (string, required) Example: `8944110068298765432`.
- `activationCode` (string, required): The full LPA string, as encoded in the QR code. Example: `LPA:1$smdp.example.net$K2-4N7Q2-1XZ9PQ`.
- `smdpAddress` (string, required) Example: `smdp.example.net`.
- `matchingId` (string, required) Example: `K2-4N7Q2-1XZ9PQ`.
- `qrCodeSvg` (string, required): The QR code as an SVG document, ready to show or print. Example: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 41 41">…</svg>`.

#### FormFactor

Physical form of the SIM.

One of: `triSim`, `mff2`, `esim`


#### Money

An amount of money. `amount` is a decimal string so it never loses pennies.

- `amount` (string, required): Decimal amount, two places. Example: `2.40`.
- `currency` (string, required): ISO 4217 currency code. Example: `GBP`.
- `includesVat` (boolean, required): Whether `amount` includes VAT. Prices are quoted ex VAT. Example: `false`.

#### Network

A mobile network.

- `mcc` (string | null): Mobile country code. Example: `234`.
- `mnc` (string | null): Mobile network code. Example: `15`.
- `name` (string | null) Example: `Vodafone UK`.

#### Plan

A plan your account can use, at your price.

- `id` (string, required): Pass as `planId` to change a SIM's plan. Example: `5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61`.
- `name` (string, required) Example: `1 GB, UK 4-network`.
- `coverage` (Coverage | null): The networks it can use; null when the plan names none.
- `allowanceBytes` (integer, required) Example: `1073741824`.
- `monthlyPrice` (Money, required): Your price per month, ex VAT.
- `billingIntervals` (BillingInterval[], required) One of `monthly`, `annual`. Example: `["monthly"]`.

#### PlanChange

The outcome of a plan change. Upgrades apply now and are prorated; downgrades apply at the next
renewal.

- `iccid` (string, required) Example: `8944110068212345678`.
- `fromPlan` (PlanSummary | null): The plan before the change; null for a SIM that had none.
- `toPlan` (PlanSummary, required): The plan a SIM is on.
- `direction` (PlanChangeDirection, required): Whether a plan change is an upgrade (now) or a downgrade (at renewal). One of `upgrade`, `downgrade`. Example: `upgrade`.
- `effectiveAt` (string (date-time), required): When the new plan takes effect: now for an upgrade, the next renewal for a downgrade. Example: `2026-09-25T00:00:00Z`.
- `message` (string | null): What we did, in words. Example: `Upgraded to 5 GB, UK 4-network; the difference for the rest of the period is charged now.`.

#### PlanChangeDirection

Whether a plan change is an upgrade (now) or a downgrade (at renewal).

One of: `upgrade`, `downgrade`


#### PlanChangeRequest

The plan to move the SIM to.

- `planId` (string, required): A plan id from `GET /v1/iot/plans`. Example: `9c7a2e14-3b5d-4f60-8e21-6a4b0d9c3f75`.

#### PlanList

Every plan available to your account. Not paginated.

- `data` (Plan[], required)

#### PlanSummary

The plan a SIM is on.

- `id` (string, required): Plan id, as listed by `GET /v1/iot/plans`. Example: `5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61`.
- `name` (string, required) Example: `1 GB, UK 4-network`.
- `allowanceBytes` (integer, required): Data included each month, in bytes. Example: `1073741824`.

#### Problem

An RFC 9457 problem document. `type` and `code` are stable; `title` and
`detail` are for people.

- `type` (string): Identifies the kind of problem. Example: `https://partners.netavo.com/api/problems/not-found`.
- `title` (string): Short, human-readable summary of the problem type. Example: `Not found`.
- `status` (integer): The HTTP status code. Example: `404`.
- `detail` (string | null): What went wrong with this request. Example: `No SIM with ICCID 8944110068212345678 in your account.`.
- `instance` (string | null): The request path. Example: `/api/v1/iot/sims/8944110068212345678`.
- `code` (string): Stable machine-readable code, the last segment of `type`. Example: `not-found`.
- `traceId` (string | null): Quote this if you contact support. Example: `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`.
- `errors` (map of string[] | null): Per-field messages when `code` is `validation-failed`.

#### ScheduledPlanChange

A plan change that takes effect later — a downgrade waits for the next renewal.

- `plan` (PlanSummary, required): The plan a SIM is on.
- `effectiveAt` (string (date-time), required) Example: `2026-10-01T00:00:00Z`.

#### Session

One data session: from the device attaching to the network to it detaching.

- `id` (string, required) Example: `ses_4f1c2a9e7b3d`.
- `iccid` (string, required) Example: `8944110068212345678`.
- `startedAt` (string (date-time) | null) Example: `2026-09-25T06:02:41Z`.
- `endedAt` (string (date-time) | null): Null while the session is open. Example: `2026-09-25T08:47:12Z`.
- `bytesUp` (integer, required): Bytes sent by the device. Example: `1843200`.
- `bytesDown` (integer, required): Bytes received by the device. Example: `7340032`.
- `network` (Network | null): The network the device was on, when the network reported it.
- `radioAccess` (string | null): `2G`, `3G`, `4G`, `5G`, `LTE-M` or `NB-IoT`, when the network reported it. Example: `4G`.
- `ipAddress` (string | null) Example: `10.64.12.34`.
- `endReason` (string | null): Why the session ended, when the network says. Example: `userRequest`.

#### SessionList

A page of results. Follow `nextCursor` for the next one.

- `data` (Session[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `true`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.

#### Sim

A SIM in your account.

- `iccid` (string, required): The SIM's ICCID. Its identifier everywhere in the API. Example: `8944110068212345678`.
- `label` (string | null): Your name for the SIM or the device it is in. Example: `Car park barrier 3`.
- `tags` (string[], required): Your tags, for filtering. Example: `["site:leeds","barrier"]`.
- `status` (SimStatus, required): Where the SIM is in its lifecycle. `pending…` states mean the network is still applying a
change; a notification follows when it takes effect. One of `inactive`, `pendingActivation`, `active`, `pendingPause`, `paused`, `pendingResume`, `suspended`, `pendingCease`, `ceased`. Example: `active`.
- `suspendedReason` (SuspendedReason | null): Set when `status` is `paused`, `pendingPause` or `suspended`. One of `customerPause`, `allowanceReached`, `nonPayment`, `provider`.
- `canResume` (boolean, required): Whether `POST …/resume` would succeed: the SIM is paused by you. False for every other
state, including a suspension only we can lift. Example: `false`.
- `formFactor` (FormFactor, required): Physical form of the SIM. One of `triSim`, `mff2`, `esim`. Example: `triSim`.
- `plan` (PlanSummary | null): The plan the SIM is on; null for a SIM with no plan yet.
- `msisdn` (string | null): The SIM's number, in international format without `+`. Example: `447700900123`.
- `imsi` (string | null) Example: `234500012345678`.
- `ipAddress` (string | null): The SIM's private IP address on the network. Example: `10.64.12.34`.
- `organisationId` (string (uuid), required): The organisation that owns the SIM. Partners see SIMs across their customers. Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.
- `activatedAt` (string (date-time) | null): When the SIM was first activated. Billing starts here. Example: `2026-09-01T08:15:00Z`.
- `activationScheduledFor` (string (date-time) | null): For a SIM awaiting activation: the day the network activates it. Activation runs on the
next working day after it is requested.
- `stopsAt` (string (date-time) | null): When a ceased SIM stops passing data: the end of the period already paid for. Null unless
a cease is pending.
- `createdAt` (string (date-time), required) Example: `2026-08-20T14:02:11Z`.
- `updatedAt` (string (date-time), required) Example: `2026-09-24T17:40:03Z`.
- `canActivate` (boolean, required): Whether the SIM can be activated now: it has never been started and has a plan. Example: `false`.
- `planStartedAt` (string (date-time) | null): When the SIM's current plan started; null before activation. Example: `2026-09-01T00:00:00Z`.
- `scheduledPlanChange` (ScheduledPlanChange | null): A downgrade waiting for the next renewal: the plan it moves to and when. Null when none is
scheduled.

#### SimCeaseRequest

Confirms which SIM to cease, because ceasing cannot be undone.

- `confirmIccid` (string, required): Must equal the ICCID in the path. Example: `8944110068212345678`.
- `reason` (string | null): Your note, kept in our logs of the SIM's history. Example: `Barrier removed`.

#### SimList

A page of results. Follow `nextCursor` for the next one.

- `data` (Sim[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `true`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.

#### SimStatus

Where the SIM is in its lifecycle. `pending…` states mean the network is still applying a
change; a notification follows when it takes effect.

One of: `inactive`, `pendingActivation`, `active`, `pendingPause`, `paused`, `pendingResume`, `suspended`, `pendingCease`, `ceased`


#### SimUpdate

Fields to change. Leave a field out (or null) to keep it.

- `label` (string | null): New label; `""` clears it. Null or left out keeps it (so a client that always sends every
field cannot wipe a label by accident). Example: `Car park barrier 3`.
- `tags` (string[] | null): Replaces all tags; `[]` removes them. Null or left out keeps them. Example: `["site:leeds","barrier"]`.

#### SimUsage

Data used by one SIM in one allowance period.

- `iccid` (string, required) Example: `8944110068212345678`.
- `periodStart` (string (date-time), required) Example: `2026-09-01T00:00:00Z`.
- `periodEnd` (string (date-time), required) Example: `2026-10-01T00:00:00Z`.
- `allowanceBytes` (integer, required) Example: `1073741824`.
- `usedBytes` (integer, required): For the current period, includes sessions still open. Example: `612368384`.
- `remainingBytes` (integer, required) Example: `461373440`.
- `percentUsed` (number, required) Example: `57`.
- `allowanceReached` (boolean, required): True once the SIM has paused for using its whole allowance this period. Example: `false`.
- `updatedAt` (string (date-time), required): When usage was last counted. Up to 15 minutes behind live traffic. Example: `2026-09-25T09:15:00Z`.
- `history` (SimUsagePeriod[], required): Earlier periods, newest first (up to twelve). Only on the current period's response.

#### SimUsagePeriod

A past allowance period's totals.

- `periodStart` (string (date-time), required) Example: `2026-08-01T00:00:00Z`.
- `periodEnd` (string (date-time), required) Example: `2026-09-01T00:00:00Z`.
- `allowanceBytes` (integer, required) Example: `1073741824`.
- `usedBytes` (integer, required) Example: `803209216`.

#### SimUsageSummary

One SIM's data use in its current allowance period, as the fleet usage list reports it.

- `iccid` (string, required) Example: `8944110068212345678`.
- `allowanceBytes` (integer, required): The current allowance period's data allowance; 0 when the SIM has no plan. Example: `1073741824`.
- `usedBytes` (integer, required): Data used so far this period, including the session still open. Example: `52428800`.
- `barredUntil` (string (date-time) | null): Set while the SIM is stopped for having used its allowance: when it starts working again
(the allowance resets). Null when it is not barred.
- `online` (boolean, required): Whether the SIM has a data session open now. Example: `true`.

#### SimUsageSummaryList

A page of results. Follow `nextCursor` for the next one.

- `data` (SimUsageSummary[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `true`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.

#### SuspendedReason

Why a paused or suspended SIM is not passing data. Only `customerPause` can be resumed through
the API.

One of: `customerPause`, `allowanceReached`, `nonPayment`, `provider`


## Platform API

Base URL `https://partners.netavo.com/api`. OpenAPI document: https://thingsim.com/developers/api/v1-platform.openapi.json

### List API keys

`GET https://partners.netavo.com/api/v1/platform/api-keys` · scope `platform.keys:read`

Every API key of your organisation, newest first, active or not. Secrets are never
returned.

```bash
curl "https://partners.netavo.com/api/v1/platform/api-keys" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The keys.

```json
{
  "data": [
    {
      "id": "key_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
      "name": "Fleet dashboard",
      "scopes": [
        "iot.sims:read",
        "iot.usage:read"
      ],
      "status": "active",
      "allowedIpRanges": [
        "203.0.113.0/24"
      ],
      "createdAt": "2026-09-25T10:00:00Z",
      "expiresAt": "2027-09-25T10:00:00Z",
      "lastUsedAt": "2026-09-25T11:30:00Z",
      "revokedAt": null
    }
  ]
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (API keys are managed with an API key that has explicit scopes); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Create an API key

`POST https://partners.netavo.com/api/v1/platform/api-keys` · scope `platform.keys:manage`

Creates a key for your organisation. It can have only scopes the calling key holds, cannot
outlive it, and inherits its address limits (which it may narrow). The secret is in this
response only — store it now.

Parameters:

- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

Body (ApiKeyCreate):

- `name` (string, required): What the key is for, so you can recognise it later. Example: `Fleet dashboard`.
- `scopes` (string[], required): The scopes the key may use. Each must be one the calling credential holds itself: a key
can never do more than the key that created it. Example: `["iot.sims:read","iot.usage:read"]`.
- `expiresAt` (string (date-time) | null): When the key stops working, at most a year ahead (and no later than the calling key's own
expiry). Leave out for a year. Example: `2027-03-31T00:00:00Z`.
- `allowedIpRanges` (string[] | null): Limit the key to these source addresses or CIDR ranges. Leave out for anywhere — unless the
calling key is itself limited, in which case the new key inherits its limits and may only
narrow them. Example: `["203.0.113.0/24"]`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/platform/api-keys" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Fleet dashboard",
  "scopes": [
    "iot.sims:read",
    "iot.usage:read"
  ],
  "expiresAt": "2027-03-31T00:00:00Z",
  "allowedIpRanges": [
    "203.0.113.0/24"
  ]
}'
```

Response 201: The key and its secret.

```json
{
  "apiKey": {
    "id": "key_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
    "name": "Fleet dashboard",
    "scopes": [
      "iot.sims:read",
      "iot.usage:read"
    ],
    "status": "active",
    "allowedIpRanges": [
      "203.0.113.0/24"
    ],
    "createdAt": "2026-09-25T10:00:00Z",
    "expiresAt": "2027-09-25T10:00:00Z",
    "lastUsedAt": "2026-09-25T11:30:00Z",
    "revokedAt": null
  },
  "secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key would have more access than the key creating it); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Revoke an API key

`DELETE https://partners.netavo.com/api/v1/platform/api-keys/{keyId}` · scope `platform.keys:manage`

Stops the key working at once. Revoking a key that is already revoked succeeds.

Parameters:

- `keyId` (path, string, required): The key's id. Example: `key_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.

```bash
curl -X DELETE "https://partners.netavo.com/api/v1/platform/api-keys/key_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 204: Revoked.

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.keys:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get branding information for the authenticated user or extension
Walks up the organisation hierarchy to find custom branding

`GET https://partners.netavo.com/api/v1/platform/branding` · scope `platform.branding:read`

```bash
curl "https://partners.netavo.com/api/v1/platform/branding" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: Branding information found and returned

```json
{
  "success": false,
  "data": {
    "organisationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "organisationName": "string",
    "primaryColor": "string",
    "secondaryColor": "string",
    "supportPhone": "string",
    "supportEmail": "string",
    "logoSvg": "string",
    "isInherited": false
  },
  "message": "string"
}
```

Errors: 401 (Not authenticated or invalid token); 403 insufficient-scope (The key is valid but lacks the `platform.branding:read` scope); 404 (No custom branding found for this organisation hierarchy); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List events

`GET https://partners.netavo.com/api/v1/platform/events` · scope `platform.events:read`

Notifications raised for your account in the last 30 days, newest first — for partners,
those of your customer organisations too. Use it to catch up after downtime.

Parameters:

- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.
- `type` (query, string): Only events of this type. Example: `iot.sim.allowance_reached`.
- `since` (query, string (date-time)): Only events at or after this time. Example: `2026-09-24T00:00:00Z`.

```bash
curl "https://partners.netavo.com/api/v1/platform/events?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of events.

```json
{
  "data": [
    {
      "id": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72",
      "type": "iot.sim.allowance_reached",
      "serviceArea": "iot",
      "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
      "occurredAt": "2026-09-25T09:12:44Z",
      "data": {
        "iccid": "8944110068212345678",
        "label": "Car park barrier 3",
        "planId": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
        "allowanceBytes": 1073741824,
        "usedBytes": 1073741824,
        "periodEnd": "2026-10-01T00:00:00Z"
      }
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.events:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get an event

`GET https://partners.netavo.com/api/v1/platform/events/{eventId}` · scope `platform.events:read`

One event by id, exactly as it was delivered.

Parameters:

- `eventId` (path, string, required): The event's id. Example: `evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72`.

```bash
curl "https://partners.netavo.com/api/v1/platform/events/evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The event.

```json
{
  "id": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72",
  "type": "iot.sim.allowance_reached",
  "serviceArea": "iot",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "occurredAt": "2026-09-25T09:12:44Z",
  "data": {
    "iccid": "8944110068212345678",
    "label": "Car park barrier 3",
    "planId": "5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61",
    "allowanceBytes": 1073741824,
    "usedBytes": 1073741824,
    "periodEnd": "2026-10-01T00:00:00Z"
  }
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.events:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List notification types

`GET https://partners.netavo.com/api/v1/platform/notification-types` · scope `platform.events:read`

The notification catalogue, filtered to your account's service areas. `available: false`
marks a type that is catalogued but not yet sent; you can subscribe to it now.

Parameters:

- `serviceArea` (query, string): Only this service area: `iot`, `connectivity`, `voice`, `hosting` or `platform`. Example: `iot`.

```bash
curl "https://partners.netavo.com/api/v1/platform/notification-types" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The catalogue.

```json
{
  "data": [
    {
      "type": "iot.sim.allowance_reached",
      "serviceArea": "iot",
      "description": "A SIM used its whole data allowance for the period and has stopped passing data.",
      "optIn": false,
      "available": true,
      "channels": [
        "webhook"
      ],
      "examplePayload": {}
    }
  ]
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.events:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List webhooks

`GET https://partners.netavo.com/api/v1/platform/webhooks` · scope `platform.webhooks:read`

Every webhook endpoint on your account, newest first — for partners, those of your
customer organisations too.

Parameters:

- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.

```bash
curl "https://partners.netavo.com/api/v1/platform/webhooks?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of webhooks.

```json
{
  "data": [
    {
      "id": "wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
      "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
      "url": "https://example.com/hooks/thingsim",
      "description": "Ops alerts",
      "types": [
        "iot.sim.allowance_reached",
        "iot.sim.usage_threshold"
      ],
      "serviceAreas": [
        "voice"
      ],
      "includeSubtree": false,
      "enabled": true,
      "status": "active",
      "disabledReason": null,
      "createdAt": "2026-09-20T10:00:00Z",
      "updatedAt": "2026-09-20T10:00:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:read` scope); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Create a webhook

`POST https://partners.netavo.com/api/v1/platform/webhooks` · scope `platform.webhooks:manage`

Adds an endpoint. The response holds the signing secret, which is not shown again. The URL
must be HTTPS on a public address. Send a test event afterwards to check your endpoint.
Partners can create one for a customer organisation with `organisationId`.

Parameters:

- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

Body (WebhookCreate):

- `url` (string, required): Where we POST events. HTTPS on a public address; redirects are not followed. Example: `https://example.com/hooks/thingsim`.
- `description` (string | null) Example: `Ops alerts`.
- `types` (string[] | null): Notification types to send. Leave out (with `serviceAreas`) for every type your account can
receive, except opt-in types. Example: `["iot.sim.allowance_reached","iot.sim.usage_threshold"]`.
- `serviceAreas` (ServiceArea[] | null): Send every type in these service areas. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`.
- `includeSubtree` (boolean | null): Partners: also send events for customer organisations below you. Null or left out is false. Example: `false`.
- `organisationId` (string (uuid) | null): Partners: create the webhook for a customer organisation below you. Defaults to your own
organisation.

```bash
curl -X POST "https://partners.netavo.com/api/v1/platform/webhooks" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/hooks/thingsim",
  "description": "Ops alerts",
  "types": [
    "iot.sim.allowance_reached",
    "iot.sim.usage_threshold"
  ],
  "serviceAreas": [
    "voice"
  ],
  "includeSubtree": false,
  "organisationId": null
}'
```

Response 201: The webhook and its signing secret.

```json
{
  "webhook": {
    "id": "wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
    "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
    "url": "https://example.com/hooks/thingsim",
    "description": "Ops alerts",
    "types": [
      "iot.sim.allowance_reached",
      "iot.sim.usage_threshold"
    ],
    "serviceAreas": [
      "voice"
    ],
    "includeSubtree": false,
    "enabled": true,
    "status": "active",
    "disabledReason": null,
    "createdAt": "2026-09-20T10:00:00Z",
    "updatedAt": "2026-09-20T10:00:00Z"
  },
  "signingSecret": "whsec_3yJ8mQ2vN6pR9tW1zC4fH7kL0sX5bE8g"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (`organisationId` is not an organisation in your account); 409 invalid-state (The organisation already has the most webhooks it may have (20)); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Get a webhook

`GET https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}` · scope `platform.webhooks:read`

One webhook endpoint. The signing secret is never returned here.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.

```bash
curl "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: The webhook.

```json
{
  "id": "wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "url": "https://example.com/hooks/thingsim",
  "description": "Ops alerts",
  "types": [
    "iot.sim.allowance_reached",
    "iot.sim.usage_threshold"
  ],
  "serviceAreas": [
    "voice"
  ],
  "includeSubtree": false,
  "enabled": true,
  "status": "active",
  "disabledReason": null,
  "createdAt": "2026-09-20T10:00:00Z",
  "updatedAt": "2026-09-20T10:00:00Z"
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Update a webhook

`PATCH https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}` · scope `platform.webhooks:manage`

Changes the URL, the types it receives, or turns it on and off. Leave a field out to keep
it. Re-enabling a disabled webhook resets its failure count.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.

Body (WebhookUpdate):

- `url` (string | null): Where we POST events. HTTPS on a public address; redirects are not followed. Example: `https://example.com/hooks/thingsim`.
- `description` (string | null) Example: `Ops alerts`.
- `types` (string[] | null): Replaces the types sent. Example: `["iot.sim.allowance_reached"]`.
- `serviceAreas` (ServiceArea[] | null): Replaces the service areas sent. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`.
- `includeSubtree` (boolean | null) Example: `false`.
- `enabled` (boolean | null): Re-enabling a disabled webhook resets its failure count. Example: `true`.

```bash
curl -X PATCH "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/hooks/thingsim",
  "description": "Ops alerts",
  "types": [
    "iot.sim.allowance_reached"
  ],
  "serviceAreas": [
    "voice"
  ],
  "includeSubtree": false,
  "enabled": true
}'
```

Response 200: The updated webhook.

```json
{
  "id": "wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f",
  "organisationId": "0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90",
  "url": "https://example.com/hooks/thingsim",
  "description": "Ops alerts",
  "types": [
    "iot.sim.allowance_reached",
    "iot.sim.usage_threshold"
  ],
  "serviceAreas": [
    "voice"
  ],
  "includeSubtree": false,
  "enabled": true,
  "status": "active",
  "disabledReason": null,
  "createdAt": "2026-09-20T10:00:00Z",
  "updatedAt": "2026-09-20T10:00:00Z"
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Delete a webhook

`DELETE https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}` · scope `platform.webhooks:manage`

Removes the endpoint and its delivery log. Deliveries in flight are abandoned.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.

```bash
curl -X DELETE "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 204: Deleted.

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### List deliveries

`GET https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}/deliveries` · scope `platform.webhooks:read`

Delivery attempts to this endpoint in the last 30 days, newest first. Failed deliveries are
retried with back-off for about 24 hours.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `limit` (query, integer): How many items to return, 1 to 100. Example: `50`.
- `startingAfter` (query, string): Cursor from the previous page's `nextCursor`. Leave out for the first page. Example: `c2ltXzg5NDQxMTAwNjgyMTIzNDU2Nzg`.
- `outcome` (query, string): Only attempts with this outcome: `succeeded`, `failed` or `pending`. Example: `failed`.

```bash
curl "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f/deliveries?limit=50" \
  -H "Authorization: Bearer $THINGSIM_API_KEY"
```

Response 200: A page of deliveries.

```json
{
  "data": [
    {
      "id": "dlv_0192a4c3e5f67b8d9e0f1a2b3c4d5e71",
      "eventId": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72",
      "eventType": "iot.sim.allowance_reached",
      "attempt": 1,
      "outcome": "succeeded",
      "responseStatus": 200,
      "durationMs": 184,
      "error": null,
      "attemptedAt": "2026-09-25T09:12:45Z",
      "nextAttemptAt": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

Errors: 400 validation-failed (Validation failed); 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:read` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Redeliver an event

`POST https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` · scope `platform.webhooks:manage`

Sends the event of a delivery attempt to the webhook again, now, whatever its outcome was.
Refused while the webhook is switched off or the delivery already has a retry pending.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `deliveryId` (path, string, required): A delivery attempt's id, from the delivery log. Example: `dlv_0192a4c3e5f67b8d9e0f1a2b3c4d5e71`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f/deliveries/dlv_0192a4c3e5f67b8d9e0f1a2b3c4d5e71/redeliver" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 202: Queued. The new attempt appears in the delivery log.

```json
{
  "id": "dlv_0192a4c3e5f67b8d9e0f1a2b3c4d5e71",
  "eventId": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72",
  "eventType": "iot.sim.allowance_reached",
  "attempt": 1,
  "outcome": "succeeded",
  "responseStatus": 200,
  "durationMs": 184,
  "error": null,
  "attemptedAt": "2026-09-25T09:12:45Z",
  "nextAttemptAt": null
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 409 invalid-state (The webhook is switched off, or the delivery is already queued); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds); 503 service-unavailable (Deliveries cannot be queued right now. Retry shortly).

### Rotate a webhook's signing secret

`POST https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}/rotate-secret` · scope `platform.webhooks:manage`

Issues a new secret. For 24 hours events carry signatures from both secrets, so you can
deploy the new one without dropping events.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f/rotate-secret" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 200: The new secret.

```json
{
  "signingSecret": "whsec_9pD2kR5tV8xA1cF4hJ7mN0qS3uW6yZ9b",
  "previousSecretExpiresAt": "2026-09-26T09:30:00Z"
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds).

### Send a test event

`POST https://partners.netavo.com/api/v1/platform/webhooks/{webhookId}/test` · scope `platform.webhooks:manage`

Queues a `platform.webhook.test` event to this endpoint only. Check the delivery log for the
result.

Parameters:

- `webhookId` (path, string, required): The webhook's id. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `Idempotency-Key` (header, string): Any unique string (a UUID is ideal). Retrying with the same key returns the first result instead of acting twice. Keys are kept for 24 hours; while the first request is still running a retry gets 409 `idempotency-key-in-use`. Example: `5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14`.

```bash
curl -X POST "https://partners.netavo.com/api/v1/platform/webhooks/wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f/test" \
  -H "Authorization: Bearer $THINGSIM_API_KEY" \
  -H "Idempotency-Key: 5d1c7a0e-2f4b-4e8a-9c61-3b7f0d2e8a14"
```

Response 202: Queued.

```json
{
  "eventId": "evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e70"
}
```

Errors: 401 unauthorized (The bearer token is missing, expired or revoked); 403 insufficient-scope (The key is valid but lacks the `platform.webhooks:manage` scope); 404 not-found (Not found, or not in your account. Resources outside your organisation are always 404, never 403); 422 idempotency-key-reused (The `Idempotency-Key` was already used for a different request); 429 rate-limited (Rate limit exceeded. Wait for `Retry-After` seconds); 503 service-unavailable (The event could not be queued. Retry shortly).

### Platform schemas

#### ApiKey

An API key. The secret is never returned after creation.

- `id` (string): The key's id, `key_…`. Example: `key_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `name` (string): What the key is for. Example: `Fleet dashboard`.
- `scopes` (string[]): The scopes the key may use. Example: `["iot.sims:read","iot.usage:read"]`.
- `status` (string): `active`, `expired` or `revoked`. Example: `active`.
- `allowedIpRanges` (string[]): Source addresses and CIDR ranges the key is limited to; empty for anywhere. Example: `["203.0.113.0/24"]`.
- `createdAt` (string (date-time)) Example: `2026-09-25T10:00:00Z`.
- `expiresAt` (string (date-time)) Example: `2027-09-25T10:00:00Z`.
- `lastUsedAt` (string (date-time) | null): When the key was last used, to the nearest few minutes; null if never. Example: `2026-09-25T11:30:00Z`.
- `revokedAt` (string (date-time) | null): When the key was revoked; null unless it has been.

#### ApiKeyCreate

A new API key.

- `name` (string, required): What the key is for, so you can recognise it later. Example: `Fleet dashboard`.
- `scopes` (string[], required): The scopes the key may use. Each must be one the calling credential holds itself: a key
can never do more than the key that created it. Example: `["iot.sims:read","iot.usage:read"]`.
- `expiresAt` (string (date-time) | null): When the key stops working, at most a year ahead (and no later than the calling key's own
expiry). Leave out for a year. Example: `2027-03-31T00:00:00Z`.
- `allowedIpRanges` (string[] | null): Limit the key to these source addresses or CIDR ranges. Leave out for anywhere — unless the
calling key is itself limited, in which case the new key inherits its limits and may only
narrow them. Example: `["203.0.113.0/24"]`.

#### ApiKeyCreated

A created key and its secret.

- `apiKey` (ApiKey): An API key. The secret is never returned after creation.
- `secret` (string): The key itself — send it as `Authorization: Bearer …`. Shown only in this response: store
it now; it cannot be read back. Example: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…`.

#### ApiKeyList

The organisation's API keys.

- `data` (ApiKey[]): Newest first.

#### BrandingResponse

API response model for organisation branding information
Includes inline SVG logo content for authenticated users and extensions

- `organisationId` (string (uuid)): Organisation ID that owns this branding
- `organisationName` (string | null): Organisation name to display in the application
- `primaryColor` (string | null): Primary brand color (hex format, e.g., #5A66F1)
- `secondaryColor` (string | null): Secondary brand color (hex format, e.g., #60A5FA)
- `supportPhone` (string | null): Support contact phone number
- `supportEmail` (string | null): Support contact email address
- `logoSvg` (string | null): SVG logo content inline (full SVG markup)
- `isInherited` (boolean): Indicates if this branding is inherited from a parent organisation

#### BrandingResponseApiResponse

Standard API response envelope for success responses

- `success` (boolean)
- `data` (BrandingResponse): API response model for organisation branding information
Includes inline SVG logo content for authenticated users and extensions
- `message` (string | null)

#### ErrorDetail

- `code` (string)
- `message` (string)
- `details` (map of string[] | null)

#### ErrorResponse

Standard API response for error cases

- `success` (boolean)
- `error` (ErrorDetail)

#### EventList

A page of results. Follow `nextCursor` for the next one.

- `data` (WebhookEvent[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `false`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page.

#### NotificationChannel

Ways a notification can reach a customer.

One of: `webhook`, `email`, `portal`


#### NotificationType

One entry in the notification catalogue.

- `type` (string, required) Example: `iot.sim.allowance_reached`.
- `serviceArea` (ServiceArea, required): The product area a notification belongs to; `platform` is the account itself. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`. Example: `iot`.
- `description` (string, required) Example: `A SIM used its whole data allowance for the period and has stopped passing data.`.
- `optIn` (boolean, required): High-volume types are only sent to webhooks that name them in `types`. Example: `false`.
- `available` (boolean, required): False for a type that is catalogued (so it can be subscribed to and documented) but not yet
raised by anything. Subscribing to it is allowed; nothing arrives until it is. Example: `true`.
- `channels` (NotificationChannel[], required) One of `webhook`, `email`, `portal`. Example: `["webhook"]`.
- `examplePayload` (object, required): An example of the type's `data` object.

#### NotificationTypeList

The notification catalogue, filtered to the service areas your account has.

- `data` (NotificationType[], required)

#### Problem

An RFC 9457 problem document. `type` and `code` are stable; `title` and
`detail` are for people.

- `type` (string): Identifies the kind of problem. Example: `https://partners.netavo.com/api/problems/not-found`.
- `title` (string): Short, human-readable summary of the problem type. Example: `Not found`.
- `status` (integer): The HTTP status code. Example: `404`.
- `detail` (string | null): What went wrong with this request. Example: `No SIM with ICCID 8944110068212345678 in your account.`.
- `instance` (string | null): The request path. Example: `/api/v1/iot/sims/8944110068212345678`.
- `code` (string): Stable machine-readable code, the last segment of `type`. Example: `not-found`.
- `traceId` (string | null): Quote this if you contact support. Example: `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`.
- `errors` (map of string[] | null): Per-field messages when `code` is `validation-failed`.

#### SecretRotation

A new signing secret. Events are signed with both secrets until `previousSecretExpiresAt`.

- `signingSecret` (string, required) Example: `whsec_9pD2kR5tV8xA1cF4hJ7mN0qS3uW6yZ9b`.
- `previousSecretExpiresAt` (string (date-time), required): Events are signed with both secrets until then. Example: `2026-09-26T09:30:00Z`.

#### ServiceArea

The product area a notification belongs to; `platform` is the account itself.

One of: `voice`, `connectivity`, `hosting`, `iot`, `platform`


#### TestEventResult

The test event that was queued.

- `eventId` (string, required): The event's id; find its delivery in the webhook's delivery log. Example: `evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e70`.

#### Webhook

A webhook endpoint.

- `id` (string, required): The webhook's id, `wh_…`. Example: `wh_0192a4c3e5f67b8d9e0f1a2b3c4d5e6f`.
- `organisationId` (string (uuid), required): The organisation that owns the webhook. Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.
- `url` (string, required): Where we POST events. HTTPS on a public address; redirects are not followed. Example: `https://example.com/hooks/thingsim`.
- `description` (string | null) Example: `Ops alerts`.
- `types` (string[], required): Notification types to send. Empty with empty `serviceAreas` means every type your account
can receive, except opt-in types. Example: `["iot.sim.allowance_reached","iot.sim.usage_threshold"]`.
- `serviceAreas` (ServiceArea[], required): Send every type in these service areas. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`.
- `includeSubtree` (boolean, required): Partners: also send events for customer organisations below you. Example: `false`.
- `enabled` (boolean, required) Example: `true`.
- `status` (string, required): `active`, `failing` while deliveries are being retried, or `disabled` (switched off, or
after a day of failures). Example: `active`.
- `disabledReason` (string | null): Why a disabled webhook was switched off.
- `createdAt` (string (date-time), required) Example: `2026-09-20T10:00:00Z`.
- `updatedAt` (string (date-time), required) Example: `2026-09-20T10:00:00Z`.

#### WebhookCreate

A new webhook endpoint.

- `url` (string, required): Where we POST events. HTTPS on a public address; redirects are not followed. Example: `https://example.com/hooks/thingsim`.
- `description` (string | null) Example: `Ops alerts`.
- `types` (string[] | null): Notification types to send. Leave out (with `serviceAreas`) for every type your account can
receive, except opt-in types. Example: `["iot.sim.allowance_reached","iot.sim.usage_threshold"]`.
- `serviceAreas` (ServiceArea[] | null): Send every type in these service areas. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`.
- `includeSubtree` (boolean | null): Partners: also send events for customer organisations below you. Null or left out is false. Example: `false`.
- `organisationId` (string (uuid) | null): Partners: create the webhook for a customer organisation below you. Defaults to your own
organisation.

#### WebhookCreated

The new webhook and its signing secret. The secret is only ever returned here and by
rotate-secret: store it now.

- `webhook` (Webhook, required): A webhook endpoint.
- `signingSecret` (string, required): Verifies the `Webhook-Signature` header. Example: `whsec_3yJ8mQ2vN6pR9tW1zC4fH7kL0sX5bE8g`.

#### WebhookDelivery

One attempt to deliver an event to a webhook.

- `id` (string, required): This attempt, `dlv_…`. Pass it to redeliver the event. Example: `dlv_0192a4c3e5f67b8d9e0f1a2b3c4d5e71`.
- `eventId` (string, required) Example: `evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72`.
- `eventType` (string, required) Example: `iot.sim.allowance_reached`.
- `attempt` (integer, required) Example: `1`.
- `outcome` (string, required): `succeeded`, `failed` or `pending` (queued, not yet attempted). Example: `succeeded`.
- `responseStatus` (integer | null): Your endpoint's HTTP status; null if it could not be reached. Example: `200`.
- `durationMs` (integer, required) Example: `184`.
- `error` (string | null)
- `attemptedAt` (string (date-time), required) Example: `2026-09-25T09:12:45Z`.
- `nextAttemptAt` (string (date-time) | null): When a failed delivery will be retried.

#### WebhookDeliveryList

A page of results. Follow `nextCursor` for the next one.

- `data` (WebhookDelivery[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `false`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page.

#### WebhookEvent

The body of every webhook POST, and an item in the event log. `data` depends on `type`: see the
notification catalogue.

- `id` (string, required): `evt_…`. Unique per event; deliveries can repeat, so de-duplicate on it. Example: `evt_0192a4c3e5f67b8d9e0f1a2b3c4d5e72`.
- `type` (string, required): The notification type. Example: `iot.sim.allowance_reached`.
- `serviceArea` (ServiceArea, required): The product area a notification belongs to; `platform` is the account itself. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`. Example: `iot`.
- `organisationId` (string (uuid), required): The organisation the event is about. Example: `0f8b6c1e-5a2d-4c7e-9b3a-2d1e6f4a7c90`.
- `occurredAt` (string (date-time), required): When the change took effect. Example: `2026-09-25T09:12:44Z`.
- `data` (object, required): The type's payload; see the notification catalogue. Example: `{"iccid":"8944110068212345678","label":"Car park barrier 3","planId":"5b0e4f2a-8c1d-4e6f-9a3b-7d2c1e0f4a61","allowanceBytes":1073741824,"usedBytes":1073741824,"periodEnd":"2026-10-01T00:00:00Z"}`.

#### WebhookList

A page of results. Follow `nextCursor` for the next one.

- `data` (Webhook[], required): This page of results.
- `hasMore` (boolean, required): Whether another page follows. Example: `false`.
- `nextCursor` (string | null): Pass as `startingAfter` to get the next page. Null on the last page.

#### WebhookUpdate

Fields to change. Leave a field out to keep it.

- `url` (string | null): Where we POST events. HTTPS on a public address; redirects are not followed. Example: `https://example.com/hooks/thingsim`.
- `description` (string | null) Example: `Ops alerts`.
- `types` (string[] | null): Replaces the types sent. Example: `["iot.sim.allowance_reached"]`.
- `serviceAreas` (ServiceArea[] | null): Replaces the service areas sent. One of `voice`, `connectivity`, `hosting`, `iot`, `platform`.
- `includeSubtree` (boolean | null) Example: `false`.
- `enabled` (boolean | null): Re-enabling a disabled webhook resets its failure count. Example: `true`.

Source: https://thingsim.com/developers/api
