Aerix

Aerix SIM API: Integration Guide

Order, track and provision Aerix physical SIMs and eSIMs from your own systems.

What the API does

The Aerix SIM API gives your systems the same view of your SIM estate that our own team has. With it you can (i) list every SIM and eSIM we have issued to you and see where each one is in its lifecycle, (ii) fetch the credentials you need to provision a physical SIM on a core you run yourself, (iii) order new programmed SIMs against your credit account, and (iv) be told by webhook when an order is ready or a SIM changes state, rather than polling for it.

It is a JSON over HTTPS API at https://api.aerix.uk/v1, described by an OpenAPI 3.1 document at https://api.aerix.uk/v1/openapi.json. That document is the contract; this page explains how the pieces fit together. Most tooling (Postman, Insomnia, openapi-generator, Swagger UI) can import it directly to give you a typed client.

Getting access

Every request carries an API key belonging to your Aerix account. We will issue your first key and send it as a one-time 1Password link, never in an email. If you use the Aerix customer portal you can also create, scope and revoke keys yourself under Account → API keys. A key is shown once, when it is created; we keep only a hash of it, so store it in your secrets manager straight away.

Each key carries one or more scopes, so you can give a system only what it needs:

ScopeAllows
sims:readList and read SIMs, orders, profiles, events and your account
credentials:readRead K, OPc and the SUCI public key for your physical SIMs. Every call is logged.
orders:writePlace SIM orders on your credit account
sims:writeLabel, suspend, resume and retire SIMs
webhooks:writeManage webhook endpoints
esims:writeReserved for eSIM issuing, which opens once our SM-DP+ is live

Ordering also needs a credit account, which we set up with you: orders are invoiced on your payment terms rather than paid by card. Ask us at support@aerix.uk if you want to order through the API.

Quick start

Check the key works and see what it can do:

export AERIX_KEY=aerix_live_...

curl -s https://api.aerix.uk/v1/account -H "Authorization: Bearer $AERIX_KEY"
{
  "customer_id": "0b7e4d2a-...",
  "name": "Your Company Ltd",
  "credit": { "limit_pence": 200000, "currency": "gbp", "payment_terms_days": 30,
              "on_hold": false, "exposure_pence": 0, "headroom_pence": 200000, "overdue": false },
  "key": { "id": "88e3...", "scopes": ["sims:read", "credentials:read"] }
}

List your SIMs, newest first:

curl -s "https://api.aerix.uk/v1/sims?limit=50" -H "Authorization: Bearer $AERIX_KEY"
{
  "data": [
    {
      "iccid": "89440900100000012345",
      "imsi": "999700000000001",
      "form": "physical",
      "state": "ready",
      "site": null,
      "label": null,
      "created_at": "2026-08-28T18:02:11Z",
      "physical": { "batch_id": "6b1c...", "order_id": null, "msisdn": null, "profile": "99970" },
      "esim": null
    }
  ],
  "next_cursor": null
}

Filter with state, form, site, profile, iccid or imsi, and fetch one SIM with GET /sims/{iccid}.

SIMs and their lifecycle

Physical SIMs and eSIMs share one model. form says which it is, and the physical or esim object carries the detail that only applies to that form. Every SIM has a state:

StatePhysical SIMeSIM
provisioningQueued or being programmed on our SIM machineDownload order in progress at the SM-DP+
readyProgrammed; credentials available. On your own core, this is its normal state.Released; activation code available
activeA live subscriber on an Aerix-run coreInstalled on a device
suspendedBarred on an Aerix-run coreBarred on an Aerix-run core
retiredFinished with; its identity will not be reusedProfile deleted or retired at the SM-DP+
failedProgramming failedSM-DP+ refused the profile
unknownWe cannot tell yetNever synced with the SM-DP+

site names the Aerix-run network whose core serves the SIM. It is null for SIMs that live on a core you run, which is why a SIM on your own core stays ready rather than becoming active: we cannot see your core, so we do not guess. New states may be added over time; treat a state you do not recognise as you would unknown.

Credentials for your own core

If you run your own 5G core, you need each physical SIM's K and OPc to provision it as a subscriber, and the SUCI home-network public key that we wrote to your cards (the matching private key is yours, from the customer portal). With the credentials:read scope:

curl -s https://api.aerix.uk/v1/sims/89440900100000012345/credentials -H "Authorization: Bearer $AERIX_KEY"
{
  "iccid": "89440900100000012345",
  "imsi": "999700000000001",
  "k": "3F6A...32 hex characters...",
  "opc": "9B10...32 hex characters...",
  "suci": { "scheme": "profile_a", "key_id": 1, "public_key_hex": "5A8D...A650" }
}

Treat these as secrets: anyone with K and OPc can clone the subscriber. Fetch them from the system that provisions your core, write them straight into it, and avoid logging the response. We log every credentials call against the key that made it. eSIM keys never leave the SM-DP+, so this returns 409 not_physical for an eSIM.

Ordering SIMs on account

With a credit account and the orders:write scope you can order between 1 and 50 programmed SIMs per request. You choose the network name shown on handsets (SPN), your PLMN (MCC and a 2-digit MNC) and the first IMSI. IMSIs then run sequentially. Every POST needs an Idempotency-Key: if your request times out, send it again with the same key and you will get the original response back rather than a second order.

curl -s -X POST https://api.aerix.uk/v1/orders \
  -H "Authorization: Bearer $AERIX_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: po-4471-batch-1" \
  -d '{
    "form": "physical",
    "quantity": 20,
    "spn": "Example Networks",
    "mcc": "999",
    "mnc": "70",
    "start_imsi": "999700000000101",
    "suci_calculation": "sim",
    "reference": "PO-4471",
    "shipping": {
      "name": "Lab team", "line1": "1 Example Street", "city": "Bath",
      "postcode": "BA1 1AA", "country": "GB", "phone": "+44 1225 000000"
    }
  }'

A successful order returns 201 with the order, whose status then moves through accepted → provisioning → ready → dispatched. We raise an invoice for it straight away on your payment terms, and the order's invoice object tracks it. The order is refused, and nothing is programmed or charged, if:

ResponseWhy
402 credit_limit_exceededIt would take you over your credit limit. The body gives your headroom.
403 no_credit_accountYour account has no credit facility yet
403 account_on_holdAn invoice is overdue; ordering resumes once it is paid
400 invalid_requestThe SIM specification or address is invalid; detail says what
422 idempotency_conflictYou reused an Idempotency-Key for a different order

Once the order is ready, its SIMs appear in GET /sims?profile=… with credentials available. eSIM ordering through the API ("form": "esim") returns 409 esim_orders_unavailable until our SM-DP+ goes live; talk to us about eSIMs meanwhile.

Suspend, resume and retire

With sims:write, POST /sims/{iccid}/suspend bars a SIM and /resume restores it, on networks where we run the core (where site is set). For SIMs on your own core these return 409 core_not_managed: we have no access to your core, so bar the subscriber there. POST /sims/{iccid}/retire works everywhere: it bars the SIM on any Aerix core, retires an eSIM at the SM-DP+, and records the SIM as retired so its identity is never reissued. It is final. Like all POSTs these need an Idempotency-Key. PATCH /sims/{iccid} with {"label": "…"} sets your own label for a SIM.

Events and webhooks

Changes are recorded as events, each with an increasing seq. The types today are order.created, order.updated, sim.state_changed, sim.updated; more may be added, so ignore types you do not handle. You can poll for them:

curl -s "https://api.aerix.uk/v1/events?after=0" -H "Authorization: Bearer $AERIX_KEY"
# then pass the returned next_after as ?after= next time

Or register an HTTPS endpoint with the webhooks:write scope and we will POST each event to it. The response includes a signing secret, shown only once:

curl -s -X POST https://api.aerix.uk/v1/webhooks \
  -H "Authorization: Bearer $AERIX_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: webhook-prod-1" \
  -d '{"url": "https://provisioning.example.com/aerix/events"}'

Each delivery carries X-Aerix-Event and X-Aerix-Signature: t=<unix seconds>,v1=<hex>, where the hex is an HMAC-SHA256 of <t>.<raw request body> keyed with your secret. Check it before trusting the body, reject timestamps more than five minutes old, and reply with any 2xx within 10 seconds. Anything else is retried with backoff for 24 hours, and the same events stay available from /events, so you can catch up after an outage. Deliveries can arrive more than once; use the event id to ignore repeats.

Verifying a signature (Node.js)

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyAerix(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return expected.length === parts.v1?.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

Verifying a signature (Python)

import hmac, hashlib, time

def verify_aerix(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Errors

Errors are RFC 9457 application/problem+json. Branch on the stable code field; title and detail are for people and may be reworded.

{
  "type": "https://api.aerix.uk/problems/core_not_managed",
  "title": "This SIM is not on an Aerix-run core",
  "status": 409,
  "code": "core_not_managed",
  "detail": "Bar or restore the subscriber on your own core; Aerix has no access to it."
}
StatusCodes
400invalid_request, idempotency_key_required
401unauthorized
402credit_limit_exceeded
403insufficient_scope, no_credit_account, account_on_hold
404not_found (also for anything that is not yours)
409invalid_transition, core_not_managed, not_physical, not_supported, esim_orders_unavailable, idempotency_in_progress
422idempotency_conflict
429rate_limited, with Retry-After
5xxinternal_error, core_unavailable: nothing changed, retry with the same Idempotency-Key

Limits, paging and compatibility

Each key may make 600 read and 60 write requests a minute; beyond that you get 429 with a Retry-After header. Lists return {"data": [...], "next_cursor": ...}; pass next_cursor back as ?cursor= until it is null, with limit from 1 to 500 (default 100). ICCIDs and IMSIs are always strings, never numbers, and timestamps are RFC 3339 in UTC. Every response carries Aerix-Api-Version.

We make one promise about change: v1 only grows. We add fields, endpoints, query parameters, event types, states and error codes, and we never remove, rename or retype anything you already rely on. Ignore what you do not recognise and your integration will keep working. If we ever need an incompatible change it will ship as /v2, with v1 left running.

A suggested integration

For a team running its own core and provisioning system, a typical integration looks like this:

  1. Register a webhook for order.updated and sim.state_changed, and store the secret.
  2. Place an order with an Idempotency-Key derived from your own purchase order, so a retry can never double-order.
  3. When an order.updated event says the order is ready, list its SIMs with GET /sims?profile=….
  4. For each SIM, fetch /credentials and create the subscriber on your core (IMSI, K, OPc; install the SUCI private key once per profile).
  5. Label each SIM with your own asset or device reference using PATCH /sims/{iccid}.
  6. Run a nightly reconciliation with GET /sims and GET /events?after=…, so nothing is missed if a webhook was down.
  7. When hardware is decommissioned, remove the subscriber from your core and call /retire.

Questions, a test key, or something the API does not yet do: email support@aerix.uk. We would rather hear about a missing field early than have you work around it.