Create an order
/api/v1/ordersorders:writeBuy one product and pay for it out of your wallet. cdkey (a game key) and account1 (a never-used game account) are delivered straight away, inside this request. account2 (a pre-owned account) is delivered later, over your callback or by reading the order again, because the account has to be checked before we buy it and that takes far longer than one request can wait, so this call only takes the order and queues it. Every reply carries an order_id — always keep it, because it is the only way back to the order. If a request times out or you are unsure it completed, send the identical request again with the same request_id, never a fresh one.
The same request_id returns the same answer, never a second order
Replaying the same request_id with the same request body returns 200 with the existing order; nothing is ordered a second time · Replaying the same request_id with a different type, product_id, item_id or expected_price returns 409 idempotency_conflict, and the existing order is not touched at all · Those four fields are the whole fingerprint; no other field in the body affects it · When a response is lost in transit, replay the identical request. That is the entire reason this field exists — you will either get the existing order back, or the order will be created for the first time, never both · Changing request_id in order to try again places a second order, which means paying twice whenever the first one had actually succeeded.
How much an order was charged is answered in one place: charged
The same error code can reach you through paths with different payment histories, and the HTTP status does not tell you which one you got — a price_changed answered live at ordering time happens before any money moves, while the same code on an account2 order the queue closed as failed comes from a path that had already passed the reservation step; supplier_failed on a pre-owned account usually happens at the account-check step, which is before any charge at all · Do not infer it from the code or from the status: reconcile from that order's charged only — it is null while the order carries no record of a charge, and an object once it does.
queued carries no timing guarantee, and the figures below describe the whole system
We buy pre-owned accounts one order at a time, in the order they arrive, from a single queue shared by every reseller — not several at once · A typical order has an answer in about two minutes, because the first account check of an order takes longer than one pass of that queue can finish · The realistic ceiling is therefore around 24 orders per hour for the entire system, not per reseller, and not a best-case figure · The queue gives nobody a reserved share and has no length limit: submitting many account2 orders at once slows down everybody's orders, including your own · An order whose check or purchase does not succeed is retried for you with a growing gap between attempts, at most 5 times, and is then closed as failed · An attempt only counts when we actually work on that order; time spent waiting for its turn does not count, so five attempts is not an upper bound on how long an order can wait.
The authentication refusals reach this endpoint too
The five refusals at the authentication gate — invalid_key (401) · key_revoked (401) · insufficient_scope (403) · quota_exceeded (429) · too_many_inflight (429) — can happen on every endpoint, this one included, because they are thrown before the request reaches a single line of this endpoint's own logic (the scope it needs is the one declared at the top of this page). The response tabs above therefore list only what this endpoint itself answers; that is not a claim that those five cannot happen here. All of the detail lives in one place, on the identity endpoint page at /developers/access/me: the HTTP status of each one, which of them spend your daily quota, which headers come back, and why the two 429s need opposite handling. It is deliberately not copied onto every page, because the second copy is the one nobody remembers to update the day the rules change.
Request parameters
Request body
| Name | Type | How to use it |
|---|---|---|
| typeRequired | cdkey · account1 · account2 | Which of the three product types you are buying, and the single field that decides when you get the goods: cdkey and account1 are bought inside this request, so they answer 201 (finished) or 202 (charged, goods seconds behind), while account2 has to be checked before we buy it, so it always answers 202 with status set to queued and is delivered laterDetails and caveats
|
| product_idRequired | string, at most 200 characters, only A-Z a-z 0-9 _ - | The product id taken from the catalogue, the id field of a product · A value that breaks the format is refused with bad_request before anything touches the database. |
| item_idOptional | same format as product_id | Required for account2 and for nothing else: the one pre-owned account you picked from GET /api/v1/products/{product_id}/itemsDetails and caveats
|
| expected_priceRequired | number, or a numeric string of at most 40 characters | The exact price in THB you are willing to pay, copied from the catalogue response for this same product or listing; the server re-prices on every request and compares the two exactly, and if they differ it refuses with price_changed instead of charging you a different amountDetails and caveats
|
| request_idRequired | string, at most 200 characters | Your own idempotency key, one unique value per real order (a UUID, say), stored on your side: sending it again returns the order you already have rather than buying a second one Details and caveats
|
Header
| Name | Type | How to use it |
|---|---|---|
| AuthorizationRequired | string | Your key as Bearer <key>, required on every request · A key holding only orders:write can place orders but cannot follow them up afterwards, so ask for orders:read as well. |
| Content-TypeRequired | string | Must be application/json, because a body declared as anything else is read as plain text or as a form and the request fails with 400 bad_request even when the JSON itself is valid to the letterDetails and caveats
|
Response fields
| Name | Type | How to use it |
|---|---|---|
| order_id | string or null | The order id: ord_ followed by 24 hexadecimal charactersDetails and caveats
|
| status | string or null | Where the order stands: queued = accepted, waiting for us to buy it (account2 only)Details and caveats
|
| type | string or null | The type you ordered — this is what tells you which shape delivery takes. |
| product_id | string or null | The product you ordered. |
| price | number or null | The agreed price in THB, equal to the expected_price you sent. |
| charged | object or null | { amount, currency }, what was actually taken from your wallet, and the only answer to the question of whether this order was paid for: it is null while the order carries no record of a charge and an object once it doesDetails and caveats
|
| delivery | object or null | The goods, present only while status is completed, and it comes in two shapes that share no field at all: cdkey and account1 give you { keys: [{ serial }] }, while account2 gives you login, password, email, email_password and extraDetails and caveats
|
| error | object or null | Why it did not succeed. Details and caveats
|
| created_at | ISO 8601 string (UTC) or null | When the order was created. |
| updated_at | ISO 8601 string (UTC) or null | When the order was last modified. |
Possible statuses
| Status | Code | Meaning |
|---|---|---|
| 200 | — | Replay — the order you already have, not a new purchase You sent the identical request again (after a lost response, say), so you get the existing order back exactly as it stands right now Details and caveats
|
| 201 | — | Finished inside this request — nothing to follow up The purchase already ran to completion, so you can read the result straight off this response: no callback to wait for and no need to read the order again Details and caveats
|
| 202 | — | Charged, goods not here yet — read the order again The order has a charged but delivery is still null: we took the money and placed the order, but the key did not arrive in time for this response, which is normal, not a failureDetails and caveats
|
| 202 | — | Non-baht wallet — price is THB, charged is your wallet's currencySame situation as the row above, shown for a wallet that is not in baht Details and caveats
|
| 202 | — | Queued — we will buy the pre-owned account and tell you afterwards account2 always answers this way with status set to queued, because a pre-owned account has to be checked before we buy it (we sign in and confirm the account really works), and that takes far longer than one request can waitDetails and caveats
|
| 400 | bad_request | The body did not pass validation Fix the request and send it again under the same request_idDetails and caveats
|
| 409 | idempotency_conflict | The same request_id with a different bodyThe existing order is not touched at all, so read it before deciding whether to place a new order under a fresh request_id · This code never shows up on an order. |
| 404 | product_not_found | No such product, or it is not on sale Refresh from the catalogue and pick another one Details and caveats
|
| 404 | item_not_found | The account2 listing you picked has goneThat one was bought by someone else or withdrawn, so fetch the listings again and pick another Details and caveats
|
| 409 | price_changed | The price no longer matches what you sent The fresh price is in data: price is the value to send as expected_price on a new order, which needs a new request_id, because a new price is a different orderDetails and caveats
|
| 409 | out_of_stock | Sold out, or someone else took it first Pick another one · This code can also show up as error.code on an order that closed as failed. |
| 409 | price_unavailable | We cannot price it The product exists but cannot be sold through the API, so skip it and tell us Details and caveats
|
| 409 | agreed_price_invalid | The agreed price failed validation at charging time Get a fresh quote from the catalogue and order again under a new request_idDetails and caveats
|
| 404 | wallet_not_found | No wallet was found for this account Contact us · This code can also show up as error.code on an order that closed as failed. |
| 400 | insufficient_points | The wallet balance does not cover this order Top up and order again Details and caveats
|
| 400 | unsupported_currency | Your wallet currency cannot be used for this order Contact us · This code can also show up as error.code on an order that closed as failed. |
| 502 | supplier_failed | The order could not be placed with our distributor Always read charged on the order before deciding to retryDetails and caveats
|
| 500 | order_save_failed | We bought it but failed to record it — the one code you must never retry We charged the wallet and placed the order, but the step that records the result on the order failed Details and caveats
|
| 500 | unknown | Closed for a reason we do not classify Stop and let a human look Details and caveats
|