Direct top-ups and code products: the two kinds of order

6 min read

Every denomination here is one of two shapes, and your integration has to tell them apart: one hands you a code to pass on, the other credits an account and hands you nothing.

Every denomination in this catalogue is one of two shapes, and an integration has to tell them apart before it places its first order. A code product returns a string you pass to your customer. A direct top-up credits an account inside a game or an app, so the order has to carry that account's id and there is nothing to hand over afterwards. Most of what is on sale here is the second kind. This guide covers how to tell which is which, what each one needs in the request, and the one difference between them that your support process has to account for.

How to tell them apart

You do not guess, and you do not hard-code a list. A denomination that is credited to an account requires player_id in the order body, and one that is not will not ask for it. If you send an order without it for a denomination that needs it, the request is refused with 422 player_id_required — before anything is charged and without creating an order — so the failure is a validation error in your own code path rather than a mystery in your ledger.

The safe pattern is to treat player_id as a field your own product records carry: when you list a denomination in your shop, record whether buying it will ask the customer for an account, and collect it in your own checkout. Reading the refusal at order time and then going back to ask the customer is a worse experience and a slower one.

What each kind returns

Both are placed the same way, through POST /v1/orders with a SKU and a quantity, and both come back pending with an empty codes array. What differs is what arrives when fulfilment completes:

  • A code product has its code available from GET /v1/orders/{number}. That endpoint is the only place a code is ever served. It appears in no list — GET /v1/orders never carries codes — and it is never sent in a webhook, because a notification travels to an address you chose through whatever sits in between, and a code is the goods themselves.
  • A direct top-up has nothing to fetch. The credit is in the account you named. The order's own record keeps the account it was sent to, which is what you compare against your customer's record when they say nothing arrived.

In both cases the completion reaches you as order.delivered on your webhook if you are subscribed to it — and order.refunded instead if it could not be filled, in which case the whole amount is already back in your wallet.

Quantity means two different things

This is the second place an integration written for one shape breaks.

  • `per_item` is the ordinary case: a fixed denomination sold one at a time. price is the price of one, and quantity is how many of them you want.
  • `per_unit` is sold by the counter — coins, credit, minutes. One unit can be worth a fraction of the smallest amount that can be written down, so price is quoted per pricing_unit units, and quantity in an order means the number of units, not the number of blocks.

For a per_unit denomination, price any quantity from price_per_unit, which the catalogue returns as an exact decimal string. And respect min_quantity, max_quantity and quantity_step: a quantity outside them is refused with 422 invalid_quantity, nothing is charged, and no order is created. Nothing is silently rounded into range — an order for 101 is not quietly turned into an order for 100, because charging for something nobody asked for and reporting success is the wrong answer twice.

The asymmetry your support process has to know

The two shapes fail in different ways, and only one of them is recoverable.

A code that has been issued is final. It stays usable whatever any order record says, which is why there is no way to ask for it back and no endpoint that would. If your customer says it does not work, the answer is almost always a redemption-side mismatch rather than a bad code.

A direct top-up that reached the wrong account cannot be pulled back out of it — not by you, not by us. So validate the account id in your own checkout before you send it: the one moment at which the mistake is fixable is before the request, and there is no cancellation window after it.

This is also why a failure and a wrong account are not the same event. An order that could not be filled returns its whole value to your wallet automatically, becomes refunded, and fires order.refunded. An order that succeeded against an id you sent is a successful order.

Everything a denomination is, in one place

The catalogue endpoint — GET /v1/catalog, documented in full in the API reference — returns products, their denominations, the SKU to order by, the pricing kind, your own price and the list price. The public price list shows the same denominations and their list prices in a table, which is the page to cite when you need a figure rather than repeating one in your own documentation.

One thing the catalogue deliberately does not return is any count of what is available. A denomination that is listed can be ordered, and one that cannot be ordered is not listed — there is no shelf to count and none to run empty.

Questions

Can I detect the two kinds from the SKU?

No, and you should not try. Treat the requirement for an account as a property you store per denomination when you add it to your shop, and let 422 player_id_required be the backstop rather than the mechanism.

Why is an order always pending, even for a code?

Because the order is accepted before it is filled, so your connection is never held open waiting on fulfilment. Fulfilment is automatic and takes seconds. Follow it with the webhook, or read the order back.

What if an order stays pending for a long time?

Then it is under review, and nothing about it is decided by guessing: no code is delivered, no money is returned, and it is never sent again. Its result reaches you when it is settled. Do not re-send it to hurry it along — see retrying an order without buying it twice.

Can one order contain several denominations?

No. One order is one denomination and one quantity. That is in your favour on failure: if one of a customer's three items cannot be filled, only that one is refunded and the other two stand.

Last updated

Want to put this into practice?

Agreed wholesale prices, instant automatic delivery, and an API for shops.

More guides