Get one product

GET/api/v1/products/{product_id}
Required scope:catalog:read

Get 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

NameTypeHow to use it
product_idRequiredstring
The product id
Details and caveats
  • the same value as the id field returned by the product list
  • It is trimmed before use, so a value that is only whitespace is refused up front with 400 bad_request without looking anything up.

Header

NameTypeHow to use it
AuthorizationRequiredstring
Your key as Bearer <key>. Required on every request; there is no other way to authenticate.

Response fields

NameTypeHow to use it
productobject
The product you asked for, in exactly the same shape as one entry in the list of GET /api/v1/products
Details and caveats
  • the only difference is that the price here has been through the per-product price floor, so it is the price you can actually order at
product.idstring or null
The product id. This is the product_id you order with and the one you ask for individual listings with
product.gamestring or null
The product name shown to customers
product.platformstring or null
The product's platform — the same value used as the platform parameter of the product list
product.typestring or null
The product type, and the field that decides how you order this product: cdkey (a game key)
Details and caveats
  • account1 (a game account that has never been used)
  • account2 (a pre-owned game account)
  • The first two are bought with product_id alone, while account2 requires you to pick an individual account first and send its item_id with the order
product.slugstring or null
The normalised name, used to build shop links and the same value the search parameter of the product list matches on
product.imagestring or null
Portrait cover image URL · null when there is no usable image
product.ratingnumber or null
The game's rating from our data source — not a shop review score
product.genresarray 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.availablefalse or null
Stock state, and it has only two values: false means confirmed out of stock, null means nobody checked this product on this pass
Details and caveats
  • there is no true, so hide only the products that are false and never test for true
product.pricenumber
What this key pays, in baht, after the discount and after the per-product price floor
Details and caveats
  • for cdkey and account1 this is the value you send as expected_price
  • For account2, use the price of the individual account you picked instead
product.discount_percentnumber
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

StatusCodeMeaning
200
cdkey / account1 — you can order at this price
product.price is the value to send as expected_price, and product_id alone is enough to order with
Details and caveats
  • product has exactly the same fields as one entry in the list: id
  • game
  • platform
  • type
  • slug
  • image
  • rating
  • genres
  • available
  • price
  • discount_percent
  • a field that is null means that value could not be read or does not exist, not that it is a meaningful empty value
  • The same product appears in the list at 440 with a discount of 12, but here it is 450 and 10: the list endpoint skips the per-product price floor while this one applies it, lifting the price back up to that floor, so the discount you actually get is 10
  • the value to send as expected_price is 450, not 440. Every example on this page is the answer given to a fictional key whose discount is set to 12%, and all figures are made up; none of them is real shop data.
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 pay
Details and caveats
  • every pre-owned account is different, so the real price belongs to the individual account
  • Your next step is to call GET /api/v1/products/{product_id}/items, pick one, and use that account's price and item_id when you order
  • The price floor for this type is applied per individual account too, not at product level.
400bad_request
No product_id in the URL
The product_id segment of the URL is empty, or holds only whitespace (we always trim it before use)
Details and caveats
  • Put a real value in it and send the request again; nothing was left in progress.
404product_not_found
No such product, or it has been delisted
Drop this product from your own catalogue; retrying changes nothing
Details and caveats
  • A product that has been delisted answers with the same code as a product that never existed
  • the two are indistinguishable on purpose.
409price_unavailable
The product exists, but cannot be priced right now
**Skip it for now and tell us
Details and caveats
  • do not drop it from your catalogue** the way you would for product_not_found, because the product is still there
  • The cause is that this product's shelf price cannot be read
  • missing, or at or below zero
  • so we refuse to quote a price we cannot stand behind.
Last updated 2026-09-07