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

# Core Concepts

> The entities, relationships and patterns that repeat across every endpoint.

Every endpoint in the Partner API follows the same handful of patterns. Learning them once here means you won't have to re-learn them per-endpoint in the reference.

## Entities and terminology

| Term                                      | Meaning                                                                                                                                                                                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SIM**                                   | A physical or eSIM identified by its **ICCID**. The unit everything else attaches to.                                                                                                                                                   |
| **Subscription**                          | The data/voice/SMS plan currently active on a SIM, with a status and billing cycle.                                                                                                                                                     |
| **Plan** (product)                        | A catalog item you can purchase and attach to a SIM as a subscription — identified by `productId`/`productName`.                                                                                                                        |
| **SIM product**                           | A catalog item for the *physical or eSIM unit itself* (separate from the data plan) — what you purchase to provision a new SIM.                                                                                                         |
| **User**                                  | An end user on your account that SIMs and devices are assigned to. Identified by **email** — there is no separate user ID exposed today. Don't key persistent storage off email: it's a mutable lookup value, not a stable primary key. |
| **Team member**                           | An **admin** on your account (a different resource from User) — someone who manages the account itself, with a role tier (`Admin`, `RW Admin`, `RO Admin`, `Standard Admin`). Identified by email.                                      |
| **Device**                                | A phone/tablet in your device catalog, assignable to a User. Identified by its **IMEI**.                                                                                                                                                |
| **Order**                                 | A record of a purchase (a plan purchase, eSIM purchase, port-in, and so on).                                                                                                                                                            |
| **Transaction** (financial)               | A charge, refund or other financial movement tied to an order — **not** the same thing as the async `transactionId` below, despite the shared name.                                                                                     |
| **Invoice**                               | A billing document for a period, with line items and a usage summary.                                                                                                                                                                   |
| **Port-in**                               | A request to bring an existing phone number from another carrier onto a Spenza SIM.                                                                                                                                                     |
| **Device Group / SIM Group / User Group** | A named group (with an optional spend limit and data quota) that devices, SIMs, or users can be moved into and out of.                                                                                                                  |
| **Webhook registration**                  | Your configuration telling Spenza where and how to deliver operator SMS/voice events.                                                                                                                                                   |

<Note>
  **Two different things are both called "transaction."** `GET /api/v1.1/transactions` is your account's *financial* transaction ledger — charges, refunds, and their status. The `transactionId` returned by an async operation (`POST /api/v1.1/esim`, `POST /api/v3/plans/purchase`, and others) is unrelated — it's a handle for polling that one operation's progress via `GET /api/v3/transactions/{transactionId}`. They happen to share a name; nothing else connects them.
</Note>

## How resources relate

```text theme={null}
User (email)
  └─ assigned ─▶ SIM (iccid)
                   ├─ has ─▶ Subscription (plan/product)
                   └─ has ─▶ Usage (current billing cycle)

User (email)
  └─ assigned ─▶ Device

SIM / Device / User
  └─ organized into ─▶ SIM Group / Device Group / User Group

Order
  └─ produces ─▶ Transaction(s)   (financial ledger entries)

Invoice
  └─ summarizes ─▶ a billing period's Orders/Subscriptions/Usage
```

A SIM is the anchor: it's assigned to a user, has a subscription, accrues usage against that subscription's billing cycle, and its purchase/port-in/plan-change events show up as orders and financial transactions.

## The success/error envelope

Every response is JSON with a `success` boolean at the top:

```json theme={null}
{
  "success": true,
  "data": { "...": "..." },
  "meta": { "page": 1, "pageSize": 25, "total": 240, "totalPages": 10 }
}
```

* `data` is an **object** for a single-resource response, or an **array** for a list.
* `meta` is present only on list responses, and carries pagination totals.

```json theme={null}
{
  "success": false,
  "error": { "code": "SIM_NOT_FOUND", "message": "No SIM found.", "details": {} }
}
```

On failure, `success` is `false` and `error.code` is a stable, machine-readable string from a fixed taxonomy — see **[Errors](/errors)**.

<Note>
  **Two documented exceptions.** `GET /api/v1.1/invoices/{id}/pdf` returns a raw `302` redirect on success, not this envelope. `POST /api/v3/port-in` isn't on this envelope at all — see **[Environments & Versioning](/environments-versioning#one-documented-exception-port-in-submission)**.
</Note>

## Pagination

List endpoints accept:

| Parameter  | Default | Notes                                           |
| ---------- | ------- | ----------------------------------------------- |
| `page`     | `1`     | **1-indexed** — the first page is `1`, not `0`. |
| `pageSize` | `25`    | Max `100`.                                      |

The response's `meta` echoes `page`/`pageSize` back and adds `total` (matching records) and `totalPages`.

## Field conventions

Fields are camelCase throughout (the one exception is `POST /api/v1.1/auth/token`'s response, which is deliberately snake\_case to match OAuth2 conventions), with one spelling per concept across every endpoint that returns it — `iccid`, `operator`, `productName`, `phoneNumber` mean the same thing everywhere they appear.

## Idempotency

Many write endpoints (SIM assignment, plan/eSIM purchase, subscription cancel, group membership changes, webhook registration and more — check each operation's page) accept an optional `Idempotency-Key` request header:

* **Omit it** — the call behaves exactly as it always has; nothing changes.
* **Send it, and retry with the same key + same request body** — the original response is replayed verbatim, with an `Idempotency-Replayed: true` response header. No side effect runs twice.
* **Reuse the key with a different body, or against a different route** — `409 IDEMPOTENCY_KEY_REUSED`.
* **Retry while the original call is still in flight** — `409 CONFLICT`. Wait for the first call to finish and reuse the same key.

There's no fixed format required for the key — any string unique to that logical request (a UUID is the common choice) works.

## Async operations

A handful of endpoints don't return the finished resource directly — they return `202` (or, for `POST /api/v1.1/top-up`, `201`) with a `transactionId` and a `statusEndpoint`:

```json theme={null}
{
  "success": true,
  "data": {
    "transactionId": "64f1a2b3c4d5e6f7a8b9c0d1",
    "statusEndpoint": "/api/v3/transactions/64f1a2b3c4d5e6f7a8b9c0d1"
  }
}
```

Poll `statusEndpoint` — **no authentication required**, the `transactionId` itself is the credential — until `status` leaves the in-flight states:

```text theme={null}
PENDING → PROCESSING → COMPLETED | FAILED | SCHEDULED
```

`data.result` on a `COMPLETED` transaction holds the operation-specific payload (for example, the provisioned `iccid`/`qrCode` from an eSIM). A `FAILED` transaction is still a `200` from the status endpoint — check `data.status` and `data.errorCode`, not the HTTP status of the poll itself. See **[Guides → Handling async operations](/guides#handling-async-operations)** for a full polling example.

Endpoints that always return this shape: `POST /api/v1.1/esim`, `POST /api/v3/plans/purchase`, `POST /api/v1.1/top-up`, `POST /api/v3/port-in`. Every other endpoint blocks and returns its finished resource directly.

## Sandbox vs. production

<Warning>
  **No sandbox yet.** There's a single base URL for the Partner API today — `https://api.spenza.com` (see **[Environments & Versioning](/environments-versioning)**). Every request you make against it acts on real account data. A sandbox is planned, but until it ships, confirm with your Spenza account representative before assuming any environment other than production exists for your account.
</Warning>

## Next steps

* **[Authentication](/authentication)** — the token exchange in full detail.
* **[Quickstart](/quickstart)** — put these concepts together into a working integration.
* **API Reference** (sidebar) — every endpoint that implements this contract.
