Documentation

Direct top-up API: deliver by UID / Player ID

For a large part of the catalog there is nothing to hand the buyer — no key, no code, no account login. You send us the player's in-game ID, we send the order to the supplier, and the currency arrives on that account, usually in under a minute. In the GameCore API this is the id_only delivery type, and it is the flow behind PUBG Mobile UC, Mobile Legends diamonds, Free Fire diamonds and Genshin Impact crystals.

This page is the whole path: how to find those products, what to put in deliveryData, what we validate and — more importantly — what we do not.

What id_only actually means

Every product in the catalog carries a deliveryType. There are five values:

deliveryTypeWhat the buyer receives
id_onlyNothing. The top-up lands on the account you named.
gift_cardA key or code, returned to you on completion.
login_requiredWe top up through the account credentials you supply.
serviceA manual service item, fulfilled by an operator.
otherNot classified yet — read the product's own schema.

The field is nullable, and a product that has not been classified yet carries deliveryType: null — not other. That matters for two reasons. Your own type for the field has to admit null, and ?deliveryType= matches on exact equality, so an unclassified product is returned by no filter value at all: it appears only in an unfiltered request. Read the product's deliveryDataSchema to find out what such a SKU actually needs.

id_only is the only type where a successful order returns no key and no code. That is not a missing field — it is the product. Getting this right in your own order model up front is most of the integration.

Finding the id_only products

The delivery type is a catalog property and a catalog filter. Ask for one game's products and narrow to the type:

GET /b2b/catalog/games/pubg-mobile/products?deliveryType=id_only
X-Api-Key: gc_live_…

Two things about the catalog worth knowing before you build against it:

  • Products whose form asks for a screenshot or image upload are excluded from the B2B catalog entirely. There is no file channel over this API, so those SKUs are not just hidden — an order for one is rejected with a 400. If a product you see in a shop is missing from your feed, this is usually why.
  • Store product IDs, not names or prices. Names get retranslated, prices move with the rate, IDs are stable.

The delivery fields come from the product, not from this page

Each product carries a deliveryDataSchema — the list of fields that product needs. The keys you put in deliveryData must be the id of each schema field:

{
"id": "gameUserId",
"type": "text",
"label": "Player ID",
"required": true
}

That field becomes:

"deliveryData": { "gameUserId": "5123456789" }

Three rules that cost integrators the most time:

  1. Key on id, not on label. The label is display copy and is translated; the id is the wire name.
  2. Every value must be a JSON string. The request schema is string-to-string. "playerId": 1234567890 — an unquoted number — is rejected by body validation before any of our own error handling runs, so it comes back in a different shape than every other error on this page.
  3. Read the schema per product, not per game. Suppliers author these forms, and the same game can carry different field names across regions or denominations.

Field types you will meet: text, number, select (value must be one of the declared options), password and image (both outside the id_only flow).

What each game asks for

Below is the shape of the request for the four most-integrated titles, together with the format rules our validator enforces before a charge. The field ids are the ones seen across suppliers today — read deliveryDataSchema rather than hardcoding them.

PUBG Mobile

One numeric field, 6 to 15 digits. Appears as gameUserId, playerId, player_id, userId or target depending on the supplier form.

{
"items": [
{
"productId": 34521,
"quantity": 1,
"deliveryData": { "gameUserId": "5123456789" }
}
]
}

The same rule covers PUBG Mobile Lite and PUBG: NEW STATE.

Free Fire

One numeric field, 6 to 15 digits, usually playerID or playerId. Free Fire MAX behaves identically.

{
"items": [
{
"productId": 41180,
"quantity": 1,
"deliveryData": { "playerID": "812345678" }
}
]
}

Genshin Impact

One numeric UID, 8 to 10 digits, most often under the id account. Honkai: Star Rail uses the same range; Zenless Zone Zero is 9 to 11 digits.

{
"items": [
{
"productId": 52903,
"quantity": 1,
"deliveryData": { "account": "700123456" }
}
]
}

Genshin UIDs are server-scoped by their leading digit, so a UID copied from the wrong region is a well-formed number pointing at a different account. See the warning below.

Mobile Legends

MLBB needs two values — the player ID and the server (zone) ID — and their field ids are the least standardised in the catalog. We do not publish a fixed pair here because there isn't one: suppliers name them differently, and a page that guessed would be wrong for some of your SKUs. Read deliveryDataSchema for the product you are selling and send every field it marks required:

{
"items": [
{
"productId": 38004,
"quantity": 1,
"deliveryData": { "playerId": "123456789", "zone": "2143" }
}
]
}

If a required field is missing, the order is rejected with a 400 naming the field — nothing is charged.

Creating the order

POST /b2b/orders
X-Api-Key: gc_live_…
X-Idempotency-Key: 7f3c1a9e-2b44-4d1a-9d4e-1c0f9a2b3c4d
Content-Type: application/json
{
"items": [
{
"productId": 34521,
"quantity": 1,
"deliveryData": { "gameUserId": "5123456789" }
}
],
"externalOrderId": "MP-98765",
"callbackUrl": "https://api.yourshop.com/webhooks/gamecore"
}

X-Idempotency-Key is required — a request without it is a 400. externalOrderId is your own reference, echoed back to you everywhere. callbackUrl is where the result is delivered; see the webhooks page.

Up to 200 items per request. Items are grouped by game and supplier, so one request can produce more than one order:

{
"success": true,
"data": {
"paymentCode": "P-AB12CD",
"totalAmount": 1064.5,
"orders": [
{
"code": "ash-XY7K3M",
"gameId": "pubg-mobile",
"gameName": "PUBG Mobile",
"total": 1064.5,
"itemCount": 1
}
]
}
}

The order code is a per-store prefix plus six characters from an alphabet with no O, 0, 1, I or L — safe to read aloud to a customer over the phone. Persist code; it is the handle for every later lookup.

There is no status in this response because there is nothing to report yet: every order leaves this call in processing.

We do not check the Player ID

This is the single most expensive thing to learn late. A well-formed ID that belongs to somebody else is indistinguishable, to us, from the right one. The top-up is delivered to whoever owns that account, and it cannot be recalled — not by us, not by the supplier.

What we do check, before anything is charged:

  • Required fields are present and non-empty.
  • Format, where a rule exists for that game family — digit count, numeric-only, select options, number ranges. A PUBG ID of 12 is rejected; 512345678999 is accepted.
  • Region and availability for your account.

What we do not check: whether that account exists, whether it is in the region the SKU serves, or whether it is the account your buyer meant. There is no pre-order existence probe on this API path.

The practical consequence is that ID entry belongs on your side of the funnel, not ours:

  • Show the field label from deliveryDataSchema verbatim, in the buyer's language.
  • Make the buyer re-enter or confirm the ID before payment, not after.
  • Tell them plainly, at the point of entry, that a wrong ID cannot be refunded. It converts better than a support ticket does.

completed with no keys is a success

Poll the order, or wait for the webhook:

GET /b2b/orders/ash-XY7K3M
X-Api-Key: gc_live_…
{
"success": true,
"data": {
"code": "ash-XY7K3M",
"status": "completed",
"items": [
{
"productName": "PUBG Mobile 660 UC",
"amount": 1,
"price": 1064.5,
"status": "completed",
"cdKeys": [],
"completedAt": "2026-08-18T18:31:15.432Z"
}
]
}
}

cdKeys: [] on a completed item is the correct and final answer for id_only — there is no key coming later. In the webhook payload the same item carries no cdKeys field at all, so a receiver must treat "absent" and "empty" the same way.

Order statuses: pending, processing, completed, failed, cancelled, refunded. The first two are non-terminal; the rest are final.

Item statuses are separate and include two that surprise people: alongside pending, completed and failed you can see awaiting_code and awaiting_screenshot. Code that waits for completed or failed and nothing else will sit on those forever — treat any unknown item status as "still working".

An order where some items succeeded and others failed resolves to failed at the order level. The value of the failed lines is credited back to your balance automatically.

When an order is rejected

Order errors come back as { "success": false, "error": "…" }. The ones specific to this flow:

HTTPMeaning
400Missing X-Idempotency-Key, empty items, more than 200 items, unknown or unavailable product, a required delivery field missing or malformed, a product needing an image upload, region not allowed.
402Not enough balance, or the order exceeds your credit limit.
409The same X-Idempotency-Key was already used with different items, or an identical request is still in flight.
429Rate limited. The body carries code: "RATE_LIMITED" and retryAfter, and a Retry-After header comes with it.
503A field rule could not be evaluated. Retry; do not strip the field.

Be precise about what "different" means, because it is narrower than the whole request. The fingerprint behind that 409 is built from items alone — productId, quantity and deliveryData, normalised: schema keys sorted, values trimmed, and a username lowercased with a leading @ removed, so @Player1 and player1 are one order rather than two. callbackUrl and externalOrderId are deliberately outside the fingerprint. Reusing a key with the same items but a different callback URL that passes validation is therefore not a 409 — it is a replay: you get the original order back, and the new callback URL has no effect on it. A replacement that fails validation never gets that far: callbackUrl is checked before your key is looked up, so a bad URL is a 400 Invalid callbackUrl: … rather than either a replay or a conflict. Fix a callback URL before the order exists, not after.

A 409 for a reused key must not be retried with the same key — it will 409 forever. Mint a new key for a genuinely new order, and reuse an old key only to replay the same items.

Note that 401 and 403 answer in a different envelope — { "error": …, "message": … } — so a client branching on success sees undefined on exactly the two failures it is most likely to hit first.

Before you go live

  • Delivery fields are read from deliveryDataSchema at request time and keyed on id.
  • Every deliveryData value is a string.
  • A completed order with cdKeys: [] is handled as fulfilled, not as an error.
  • Unknown item statuses are treated as in-progress.
  • X-Idempotency-Key is stored with your order so a retry is a replay, not a second charge.
  • The buyer confirms the ID before payment, and knows a mistake is final.

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.