Documentation

Delivery types: what the buyer actually receives

Every product carries a deliveryType. It answers one question — what comes back when the order completes — and it decides what you must collect from the buyer before you can order. Get it wrong and you either ask for data nobody needs or promise a key that is never coming.

The five values

deliveryTypeWhat the buyer receivesWhat you send
id_onlyNothing to hand over — the top-up lands on the account.The player's in-game id (and sometimes a server).
gift_cardA key or code, returned to you on completion.Usually nothing.
login_requiredWe top up through the account you name.The account credentials the schema asks for.
serviceA manually fulfilled service.Whatever the schema asks for.
otherNot classified yet — read the product's own schema.Whatever the schema asks for.

The column is nullable. An unclassified SKU comes back as deliveryType: null, not as other, and because the catalog filter compares for exact equality, ?deliveryType=other will not find it. Your own type for this field has to admit null, and a full sync has to run at least one unfiltered pass.

Whatever the type, deliveryData is a required key in the order body. When a product needs no fields, send an empty object — omitting the key is a schema validation failure, not an empty-but-valid request.

What each type needs

id_only

The buyer's in-game identifier, and nothing else. No key comes back: a completed id_only item has cdKeys: [] and that is the final, correct answer. This is the largest slice of the catalog — PUBG Mobile UC, Mobile Legends diamonds, Free Fire diamonds, Genshin Impact crystals — and the one where a wrong id costs real money, because we validate the format of an id but never check that the account exists.

Everything about this flow — the per-game field ids, the format rules, what a completed order without keys looks like — lives on Direct top-up API. It is the page to read before you build a top-up checkout.

gift_card

A code the buyer redeems themselves: a platform gift card, a game currency card, a store balance top-up. Usually the product declares no delivery fields at all, so you send "deliveryData": {}.

The result arrives as cdKeys on the order item. Two things about that array:

  • It is populated only when the item is completed. On any other status you get [] — an empty array is "not yet", not "none".
  • In the webhook payload the same item carries cdKeys only for key-bearing products, so a receiver must treat an absent field and an empty array identically.

Store the code encrypted, show it once, and log who revealed it. It is a bearer instrument: whoever reads it can redeem it, which makes a support ticket containing one a permanent liability.

login_required

The top-up is performed by signing into the buyer's account, so the schema asks for credentials — a login, a password, sometimes a region or a security answer.

Do not expect the field type to tell you which entry is the secret. In the live catalog a password arrives as an ordinary text field whose id and label name it — { "id": "password", "label": "Пароль", "required": true, "type": "text" } — because one upstream supplier declares only text, number and select, and the other collapses every non-select, non-image field to text. Decide sensitivity from the field's id and label, not from its type; that is exactly what our own classifier does when it labels a product login_required in the first place.

Treat this data as toxic. Collect it over TLS, keep it out of your application logs and your error tracker, do not persist it after the order reaches a terminal state, and tell the buyer to change the password afterwards. Nothing on our side needs it a second time.

service

A manually fulfilled item — an operator does the work rather than a machine. Two consequences for your code: it is slower, and its item status can pass through states an automated SKU never shows, such as awaiting_code or awaiting_screenshot. Code that waits only for completed or failed will sit on those forever, so treat any status you do not recognise as "still working". Orders has the full status list.

other, and null

other is the honest bucket: classified as "none of the above". null means "not classified yet" — the same practical situation from your side, but a different value, and the two are not interchangeable in a filter.

For both, the product's deliveryDataSchema is the authority. If it asks for fields, collect them; if it is empty, send {}. Do not build behaviour on the type name here.

Reading the schema, not this page

deliveryDataSchema is the per-product contract. The keys you send are each field's id, never its label, and every value must be a JSON string — an unquoted number is rejected by body validation before our own error handling runs.

Field typeMeaning
textFree text. Format rules may still apply per game family.
numberNumeric; may declare a range.
selectThe value must be one of the declared options, exactly.
imageA file upload — not supported on this API.
passwordUnderstood if a supplier ever sends it, but nothing in the live catalog emits it today. Never rely on its presence to find a secret.

Products that declare an image field are excluded from the B2B catalog entirely, and an order naming one is rejected with a 400. If a SKU you can see in a retail shop is missing from your feed, this is usually why.

Because type does not mark secrets, apply the rule from login_required everywhere: treat any field whose id or label says credential — password, pass, pwd, пароль — as sensitive whatever its type, and keep it out of logs, error reports and analytics. The wire format of a secret is an ordinary JSON string; only your handling makes the difference.

Choosing the right form in your UI

Build the form from the schema at request time rather than from a table you maintain:

  1. Read deliveryDataSchema for the exact product the buyer chose — not for the game, and not from yesterday's cache. The same title can carry different field ids across regions and denominations.
  2. Render every entry, using its label verbatim in the buyer's language, and mark the required ones.
  3. Make the buyer confirm anything that identifies an account before payment, and say plainly that a mistake cannot be refunded.
  4. Send the collected values as strings, keyed by id.

That loop is the same for all five types, which is the point: one renderer, no per-game branching, and a new game your supplier adds tomorrow works without a release on your side.

Need an API key?

Tell us which games and regions you sell and we will issue a key, then walk the integration with you. Integration questions are answered by the same people who run the API.