Appearance
Sales
Every sale is the same two calls, whatever the product: airtime, data, ZESA, bills or vouchers.
Quote, then sell
POST /quoteswithproduct,recipientand, for ranged products,amount(USD) orface_value. The quote:- checks the recipient with the provider: a meter's name, a biller account;
- prices the sale for you:
face_value,feeandtotal_debit(what leaves your balance); - checks your balance and limits;
- holds the price for 5 minutes.
POST /transactionswithquote_id, yourreferenceand anIdempotency-Keyheader. Money is reserved and the sale is sent.
A quote funds exactly one sale. Using it twice answers 409 QUOTE_USED; after 5 minutes it's 409 QUOTE_EXPIRED.
Statuses
| Status | Meaning | What you do |
|---|---|---|
pending | Sent, outcome not final yet. Your money is reserved. | Wait for the webhook or poll. Never buy again. |
completed | Delivered. | Give your customer fulfilment (token, PIN, units). |
failed | Not delivered. Your money is back. | Tell your customer; they can try again with a new quote. |
reversed | Was completed, then the provider undid it. Your money is back. | Contact your customer. |
Retrying safely
Every sale needs a reference (your own id, e.g. your order number) and an Idempotency-Key (a UUID you generate). Together they make retries safe:
- Same key, same reference: you get the original sale back with
200andIdempotency-Replayed: true. Nothing is sold twice. - Same key or reference with different details:
409 IDEMPOTENCY_CONFLICT. Use a new reference for a new sale.
So if a request times out, or your server crashes mid-call, send exactly the same request again.
Receipts
Completed sales return receipt: everything you need to print for your customer (product, recipient, amount, token or PIN, units, Xash and provider references, time). You issue the receipt to your customer.
Branches and tills
Send branch_id (one of your branch codes) and terminal_id on the quote to tag the sale. An API key limited to a branch tags every sale with it automatically. The portal and GET /transactions?branch_id= report by branch. You have one balance for all branches.
Limits
Sandbox has no limits. Live sales are capped per sale and per day by your tier; see Going live. Over a limit you get 409 LIMIT_EXCEEDED with error.limit naming which one.