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" }
}| Field | Rules |
|---|---|
quoteId | Required. An unexpired, unused quote created by your workspace. |
clientReference | Required, ≤ 200 printable characters. Your own identifier — echoed on the order, order lists, and webhooks so you can correlate without storing our ids. |
purchaseInput.phoneNumber | Required 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.
| Status | Meaning | Terminal? |
|---|---|---|
Pending | Admitted, not yet funded/validated. | no |
Reserved | Wallet funds reserved; queued for fulfillment. | no |
Processing | Being fulfilled. | no |
Fulfilled | Delivered — artifacts are ready to reveal. Funds are captured. | yes |
Failed | Could not be fulfilled; the reservation is released back to your wallet. | yes |
Cancelled | Cancelled before fulfillment; reservation released. | yes |
Refunded | A previously captured order was refunded to your wallet. | yes |
RefundPending | A refund is in flight. | no |
ReconciliationRequired | Needs 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, andorder.refundedtransitions 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.
Updated about 2 hours ago