List pre-owned accounts for sale

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

See the individual pre-owned accounts genuinely on sale under one account2 product, so you can pick one before ordering. This endpoint is for account2 only: cdkey and account1 are ordered with product_id alone and have nothing to pick from. Think of an account2 product as a box holding many individual accounts, each with its own games, level and price. The routine is: pick one entry, then send that entry's item_id and that entry's price as expected_price when you create the order — never the price of the product it sits under.

Per-account prices are already discounted — never discount them again

Each entry's 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. The price of an account2 is only ever worked out per individual account: this type has a discount cap of its own, and that account's own price floor can lift the price further, which is why two accounts under the same product routinely report different discount_percent. The value to send as expected_price is the price of the account you picked from this list, not the price of the product, which is only the cheapest listing the shop knows of. Prices move all the time — fetch the list again 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 id of the account2 product whose accounts you want to see
Details and caveats
  • the same value as the id field returned by the product list
  • We always trim it before use, so a value that is only whitespace is refused up front with 400 bad_request.

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
itemsarray
The accounts genuinely on sale under that product
Details and caveats
  • An empty array is a normal answer, not an error
  • ⚠️ This list changes constantly: an account you can see right now may be bought by someone else first, and an order naming an item_id that is gone is rejected with item_not_found
  • when that happens, fetch the list again and pick another one
items.item_idstring
The id of that account
Details and caveats
  • the value you send with an account2 order, together with the product_id of the product it sits under
  • ⚠️ It is always a string, never a number, even when it looks like digits only
  • The order body accepts nothing but a string, so send it back exactly as you received it and never convert it to a number on the way (languages like JavaScript will do that for you if you are not careful)
  • A non-string value is refused with a 400 bad_request whose message reads as though you had not sent the field at all
items.pricenumber
That account's price in baht, with the discount and that account's own price floor already applied
Details and caveats
  • this is the value you send as expected_price for an account2 order; never use the price of the product
items.discount_percentnumber
The discount you actually got on that account, in percent
Details and caveats
  • already taken off price, so never subtract it again
  • It can be lower than the cap for account2 when the price is lifted to that account's price floor

Possible statuses

StatusCodeMeaning
200
Accounts are on sale — pick one
Every entry always carries three fields
Details and caveats
  • item_id
  • price
  • discount_percent
  • and beyond those, descriptive fields that differ per platform (game count, country, two-factor state, registration date and so on)
  • The set is not the same across platforms, and a platform we do not support yet returns only item_id, so read only the fields you know, and never assume any one of them is present
  • The example is a Steam account; fields that hold a composite value (the game list, medals, transactions) are passed through as the source sends them, and the shape inside those blocks is not part of the contract and can change without notice
  • ⚠️ There are no login details in this endpoint
  • the username, password and email of an account are handed over only after the order succeeds
  • The second entry shows the price floor at work: taking this fictional key's full 12% off would put the price below what we can accept, so the price is lifted back to that floor and discount_percent reports what you actually got, 10
  • All figures are made up; none of them is real shop data.
200
Nothing is on sale right now — not an error
An empty list is the normal answer for a product whose accounts have all just been bought
Details and caveats
  • It is not an error and does not mean something is wrong with your key
  • Accounts we cannot price are also dropped from this list silently (the same reason a product answers price_unavailable rather than coming back at a price of 0)
  • So an empty list means either everything sold or nothing could be priced, and you cannot tell the two apart
  • treat it as out of stock and try again later.
400unsupported_type
That product is not an account2, so it has nothing to list
Only call this endpoint on products whose type is account2 · cdkey and account1 have no individual accounts, because product_id alone is enough to order them.
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) · Put a real value in it and send the request again.
404product_not_found
No such product, or it has been delisted
Drop this product from your own catalogue
Details and caveats
  • A delisted product answers with the same code as a product that never existed
  • indistinguishable on purpose
  • ⚠️ A delisted product returns no accounts at all, even when accounts under it are still genuinely on sale.
502supplier_unavailable
We could not load the accounts right now — retry shortly
Wait a moment and retry, and ⚠️ never take this as proof that the product has nothing for sale
Details and caveats
  • The system we source account data from did answer us, but in a shape we could not read, and we had no stored list left to serve instead
  • You only ever see this code when there was nothing left for us to serve: whenever we still hold a recent list for that product you get a 200 carrying it (possibly slightly stale)
  • the same is true of the 503 below.
503supplier_unavailable
We could not reach the source at all — retry shortly
Handle it exactly like the 502: wait and retry rather than concluding the product is sold out
Details and caveats
  • The only difference is the cause on our side: this time we could not reach the source at all
  • a timeout, a refused connection, or a failure it reported about itself
  • As with the 502, you only see this when there was no stored list left to serve instead
  • The two codes differ only in cause, never in what you should do about them.
Last updated 2026-09-07