Orders

Idempotent, asynchronous purchasing — the Idempotency-Key contract and the full status lifecycle your integration should be built around.

An order turns a quote into delivered digital goods, paid from your prefunded wallet. Creation is asynchronous and idempotent; the status lifecycle below is the contract to build against. Creating orders requires orders.create; reading them requires orders.read.

Create an order

POST /api/v1/orders
Idempotency-Key: <your unique key, 1–128 printable characters>
{
  "quoteId": "0198d72d-a6ce-77c6-9586-fc36708731d0",
  "clientReference": "your-order-1001",
  "purchaseInput": { "phoneNumber": "+15551234567" }
}
FieldRules
quoteIdRequired. An unexpired, unused quote created by your workspace.
clientReferenceRequired, ≤ 200 printable characters. Your own identifier — echoed on the order, order lists, and webhooks so you can correlate without storing our ids.
purchaseInput.phoneNumberRequired for phone-top-up products (E.164 recommended); omit purchaseInput otherwise.

A successful create returns 202 Accepted with a Location header and the order body in state Reserved — your wallet has reserved the quote's total, and fulfillment proceeds in the background (typically seconds).

Idempotency — the retry contract

The Idempotency-Key header is mandatory. Its guarantee: for a given key, the order is created at most once. Send the same key again — after a timeout, a crash, a network error, or a 5xx — and you get the same order back, never a duplicate purchase.

Rules of thumb:

  • Derive the key from your own order identity (e.g. order-{yourOrderId}), not from a random value per attempt — randomness defeats the whole point.
  • Keep the key stable across process restarts (persist it with your order row).
  • A new purchase must use a new key and a new quote.

The status lifecycle

status is a string; treat unknown future values as non-terminal and keep polling.

StatusMeaningTerminal?
PendingAdmitted, not yet funded/validated.no
ReservedWallet funds reserved; queued for fulfillment.no
ProcessingBeing fulfilled.no
FulfilledDelivered — artifacts are ready to reveal. Funds are captured.yes
FailedCould not be fulfilled; the reservation is released back to your wallet.yes
CancelledCancelled before fulfillment; reservation released.yes
RefundedA previously captured order was refunded to your wallet.yes
RefundPendingA refund is in flight.no
ReconciliationRequiredNeeds operator review (an ambiguous fulfillment outcome). It will resolve to a terminal state — keep it visible in your back office, and contact support with the order id if it lingers.no

State machine, simplified:

Pending → Reserved → Processing → Fulfilled
                   ↘ Failed / Cancelled
Fulfilled → RefundPending → Refunded
(any in-flight anomaly) → ReconciliationRequired → (terminal)

Read an order

GET /api/v1/orders/{orderId}

Returns the order or 404 (code: "not_found") — including for orders that belong to another workspace, which are indistinguishable from nonexistent ones by design.

{
  "id": "0198e000-1111-7abc-9def-222233334444",
  "clientReference": "your-order-1001",
  "status": "Fulfilled",
  "total": 52.50,
  "currency": "USD",
  "createdAtUtc": "2026-09-05T12:01:02Z",
  "updatedAtUtc": "2026-09-05T12:01:09Z"
}

List orders

GET /api/v1/orders?offset=0&limit=50&status=fulfilled

Newest-first, offset-paginated (limit 1–100, default 50), optional status filter (case-insensitive status name). Response: { items, offset, limit, hasMore }. An unknown status value returns 400 invalid_request.

The list is your reconciliation backbone: a periodic sweep of recent orders catches anything a missed webhook or crashed worker left behind.

Tracking progress: webhooks first, polling as backup

  • Webhooks push order.processing, order.fulfilled, order.failed, and order.refunded transitions to your endpoint within moments — see Webhooks.
  • Polling: GET /orders/{id} every 3–6 seconds is fine while an order is in flight (well within the rate limit for reasonable volumes); back off once you rely on webhooks.

Never mark an order delivered in your system from anything but a Fulfilled status you read from the API or a verified order.fulfilled webhook.


Did this page help you?