API ReferenceeSIM

Create Order

Purchase an eSIM and receive its activation code (LPA string) and ICCID

POST /api/v1/esim/orders

Purchases an eSIM. Octopus Cards picks the matching variant for (product_id, amount), debits your wallet, dispatches the order for fulfilment, and returns the activation code (an LPA string the device parses) along with the ICCID.

Hybrid sync/async. Every response is 200 OK — branch on the status field in the body:

  • If fulfilment completes inside the 60-second handler deadline → status: DELIVERED with the full activation payload (activation_code, iccid).
  • If 60 seconds elapse → status: PENDING. A detached goroutine keeps trying in the background; poll GET /esim/orders/:id until terminal.
  • Synchronous user-fixable failures (invalid plan, etc.) come back as status: FAILED with failure_code / failure_reason / is_user_fixable populated. Wallet auto-refunded.

Quantity is fixed at 1 — each order produces exactly one eSIM. Send quantity: 1 or omit it; any other value is silently clamped.

Request

curl -X POST "https://api.octopuscards.io/api/v1/esim/orders" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": 712,
    "amount": 4.50,
    "quantity": 1,
    "client_reference": "TRIP_TOKYO_OCT"
  }'

Request Parameters

KeyTypeRequiredDescription
product_idintegerYeseSIM product to purchase. Must be > 0.
amountnumberYesMust match an existing variant's amount for this product. Use this as the variant selector — Octopus Cards resolves (product_id, amount) to a single variant.
quantityintegerNoAlways coerced to 1. eSIM orders are one-eSIM-per-order.
client_referencestringNoYour dedup + lookup key (printable ASCII, max 255 characters). Unique per client — a duplicate (client_id, client_reference) returns 400 "Duplicate client_reference". Optional, but required for retry-safe order creation. See Idempotency.
wallet_idintegerNoOptional. The wallet to debit. When supplied and its currency differs from the variant's, Octopus Cards looks up an admin-managed forex rate and bills the wallet in its own currency. If no rate exists for the pair, the request rejects with 400 "Exchange rate not available for the wallet currency". Omit to use the variant-currency wallet, falling back to your default-currency wallet.
categorystringNoReserved for future variant categorisation. Leave empty unless instructed.
redeem_voucher_codestringNoFund the order by redeeming a previously-delivered voucher code instead of debiting the wallet. The voucher must be in (DELIVERED, COMPLETED) and unredeemed.

Response (fulfilled in time — status: DELIVERED)

200 OK

{
  "id": 9302481,
  "client_reference": "TRIP_TOKYO_OCT",
  "status": "DELIVERED",
  "status_text": "eSIM ready to install",
  "product_name": "Japan eSIM",
  "country_code": "JPN",
  "data_amount_gb": 1.0,
  "validity_days": 7,
  "amount": 4.275,
  "currency": "USD",
  "activation_code": "LPA:1$rsp.example.com$ACTIVATION-TOKEN-XYZ",
  "iccid": "8910300000123456789",
  "activation_status": "NOT_INSTALLED",
  "created_at": "2026-05-15T14:30:00Z"
}

The activation_code is the LPA Activation Code — the string the customer scans (via QR) or pastes into their device's eSIM setup screen. Once installed, the activation_code field is masked from subsequent responses (see Lifecycle).

Response (still in flight after 60 seconds — status: PENDING)

200 OK

{
  "id": 9302482,
  "client_reference": "TRIP_TOKYO_OCT",
  "status": "PENDING",
  "status_text": "Awaiting fulfilment",
  "product_name": "Japan eSIM",
  "country_code": "JPN",
  "data_amount_gb": 1.0,
  "validity_days": 7,
  "amount": 4.275,
  "currency": "USD",
  "activation_status": "NOT_INSTALLED",
  "created_at": "2026-05-15T14:30:00Z"
}

Poll GET /esim/orders/:id — the activation code lands once fulfilment completes. A detached dispatch goroutine keeps trying in parallel; the retry cron handles any remaining slow cases.

Response (rejected with user-fixable failure — status: FAILED)

200 OK

{
  "id": 9302483,
  "status": "FAILED",
  "status_text": "Plan unavailable in this region",
  "amount": 4.275,
  "currency": "USD",
  "activation_status": "NOT_INSTALLED",
  "failure_code": "PRODUCT_UNAVAILABLE",
  "failure_reason": "This plan is not currently available for new purchases. Try a different plan.",
  "is_user_fixable": true,
  "created_at": "2026-05-15T14:30:00Z"
}

The wallet is automatically refunded for failed orders.

Enumerations

status:

ValueMeaning
PENDINGOrder created, fulfilment in flight. Returned when the 60s handler deadline fires without fulfilment landing. Not terminal — poll the detail endpoint.
DELIVEREDActivation code issued. Terminal for the order; the eSIM may still need to be installed (see activation_status).
FAILEDOrder could not be fulfilled. Wallet auto-refunded. Terminal.
CANCELLEDOrder cancelled administratively. Wallet refunded. Terminal.

activation_status (lifecycle on the device; separate from order status):

ValueMeaning
NOT_INSTALLEDActivation code returned but not yet installed on a device.
ACTIVEeSIM installed and currently active on a device.
EXPIREDValidity window has passed.

Response Fields

KeyTypeDescription
idintegerServer-issued order ID. Pass to /esim/orders/:id.
client_referencestringEcho of the value you supplied on create (omitted if you didn't supply one).
statusstringOrder status (see enum).
status_textstringHuman-readable status detail.
product_namestringDisplay name of the product.
country_codestringISO 3166-1 alpha-3 code (or GLO).
data_amount_gbnumberIncluded data in GB.
validity_daysintegerPlan validity in days, from activation.
amountnumberWallet debit amount (after client discount).
currencystringISO 4217.
activation_codestringLPA string. Only present when delivered AND not yet installed. The customer scans this (as a QR) or pastes it into their device's eSIM setup.
iccidstringSIM serial number. Use this to look up installation status later via the lifecycle endpoints.
activation_statusstringOne of NOT_INSTALLED, ACTIVE, EXPIRED.
is_installedbooleantrue once the device has installed the eSIM. The activation code is masked from then on.
installed_atstringRFC 3339 timestamp of installation. Present once is_installed is true.
activated_atstringRFC 3339 timestamp of activation (first data usage).
failure_codestringCanonical machine code when status == FAILED. Shares the Failure Codes enum with topups — same routing logic applies.
failure_reasonstringUser-facing failure message when status == FAILED.
is_user_fixablebooleantrue when the customer can retry with different inputs.
transaction_idintegerWallet transaction ID after debit.
created_atstringRFC 3339.

HTTP Status Summary

StatusWhenBody
200 OKOrder accepted. Branch on status in the body: DELIVERED (with activation code), PENDING (60s deadline elapsed — poll for terminal state), or FAILED (synchronous user-fixable failure, wallet refunded).
400Validation, balance, voucher errorserror envelope.
404No matching variant or walleterror envelope.
401 / 403 / 500Auth / IP / servererror envelope.

Errors

400 Bad Request — required fields missing or invalid.

{
  "error": {
    "name": "ValidationException",
    "code": "VALIDATION_FAILURE",
    "message": "Product ID is required"
  }
}

Other messages: "Amount is required", "Invalid request body".

On this page