Get one product
/api/v1/products/{product_id}catalog:readGet the latest details and the real, orderable price for one product. What you do next depends on the product's type. cdkey and account1 — the price in this response is the value to send as expected_price, and product_id alone is enough to order with. account2 — you cannot order from this page: one pre-owned product has many individual accounts for sale, each in different condition and at a different price, so go on to GET /api/v1/products/{product_id}/items, pick one account, and use that account's price and item_id instead.
A missing available means not checked, not out of stock
This field only ever comes back as false (confirmed out of stock) or null (not checked against the source). There is no true — catalogue endpoints answer from data the shop already holds and do not query the source on every call, so most products come back as null, which is the truth: nobody checked that product's stock on this pass. Never test available === true, and never write a condition like not true means sold out — reading it that way hides nearly the whole catalogue, products that really are for sale, from your own storefront. Hide only the ones that are false. Real stock is checked against the source again when the order is placed, which is the only point at which an answer about availability is dependable.
The price you get back is already discounted — never discount it again
price is the number of baht this key pays, not the shop's shelf price, and discount_percent says how many percent already came off the shelf price. Taking your discount off it a second time produces a price the system will never accept, and then every order is refused on price with nothing to tell you why. Your discount is set as a single number, but each product type has a cap of its own, and on top of that a per-product price floor raises the price back up whenever the discounted price would fall below what we can accept (but never above the shelf price), which is why discount_percent can be lower than the cap — both cases are shown in this page's responses. You can read the already-resolved discount per type from GET /api/v1/me in the discount.effective_percent field, but do not compute the price on your side: use the price this endpoint returns. Prices move all the time, so store them for as long as you like but always re-read them immediately before ordering.
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
Path
| Name | Type | How to use it |
|---|---|---|
| product_idRequired | string | The product id Details and caveats
|
Header
| Name | Type | How to use it |
|---|---|---|
| AuthorizationRequired | string | Your key as Bearer <key>. Required on every request; there is no other way to authenticate. |
Response fields
| Name | Type | How to use it |
|---|---|---|
| product | object | The product you asked for, in exactly the same shape as one entry in the list of GET /api/v1/productsDetails and caveats
|
| product.id | string or null | The product id. This is the product_id you order with and the one you ask for individual listings with |
| product.game | string or null | The product name shown to customers |
| product.platform | string or null | The product's platform — the same value used as the platform parameter of the product list |
| product.type | string or null | The product type, and the field that decides how you order this product: cdkey (a game key)Details and caveats
|
| product.slug | string or null | The normalised name, used to build shop links and the same value the search parameter of the product list matches on |
| product.image | string or null | Portrait cover image URL · null when there is no usable image |
| product.rating | number or null | The game's rating from our data source — not a shop review score |
| product.genres | array of strings | The game's genres · any value in here works as the genre parameter of the product list · an empty array means no genres are recorded |
| product.available | false or null | Stock state, and it has only two values: false means confirmed out of stock, null means nobody checked this product on this passDetails and caveats
|
| product.price | number | What this key pays, in baht, after the discount and after the per-product price floor Details and caveats
|
| product.discount_percent | number | The discount actually applied to this product, in percent · already taken off price · it can be lower than that type's cap when the price is lifted to the product's price floor |
Possible statuses
| Status | Code | Meaning |
|---|---|---|
| 200 | — | cdkey / account1 — you can order at this priceproduct.price is the value to send as expected_price, and product_id alone is enough to order withDetails and caveats
|
| 200 | — | account2 — not orderable yet, pick an individual account first⚠️ On an account2 product, price is only a from-price (the cheapest account the shop knows of), not the price you will payDetails and caveats
|
| 400 | bad_request | No product_id in the URLThe product_id segment of the URL is empty, or holds only whitespace (we always trim it before use)Details and caveats
|
| 404 | product_not_found | No such product, or it has been delisted Drop this product from your own catalogue; retrying changes nothing Details and caveats
|
| 409 | price_unavailable | The product exists, but cannot be priced right now **Skip it for now and tell us Details and caveats
|