Check your key, balance and quota

GET/api/v1/me
Required scope:wallet:read

Make this your first request after receiving a key, to confirm the key really works. It also answers the four things the rest of these docs assume you already know: what this key is allowed to do (scopes) · how much money your wallet holds and in which currency (wallet) · what discount you actually get, per product type (discount) · how many requests you have left today (quota). ⚠️ There is no sign-up, no test key and no sandbox: keys are issued by the shop's admin only, every request from here on runs against the live system, and every order spends real money.

The refusals at the authentication gate — they can happen on any endpoint

These five are thrown before the request ever reaches the logic of any endpoint: invalid_key (401) no authentication header, malformed key, unknown key, or the wrong secret · key_revoked (401) this key has been revoked · insufficient_scope (403) the key works but lacks the scope that endpoint requires · quota_exceeded (429) the daily quota is spent · too_many_inflight (429) this key has more requests open at once than its ceiling allows. None of these five spends any of your daily quota, both 429s included, because the refusal happens before the counter is ever touched. The 401 and 403 group comes back with **no X-RateLimit-* header at all — the counter was never read, so there is nothing to report — while both 429s carry the full set plus Retry-After, and all five carry Cache-Control: private, no-store. The two 429s share a status but need opposite handling, so branch on data.code, never on the HTTP status alone:** quota_exceeded really is spent until the reset, while too_many_inflight clears itself within seconds — code that treats every 429 the same will sleep for hours over a collision that resolves in two.

Decide from statusCode and data.code only

Every endpoint reports failure in the same envelope: error is always true and only present on failure · url is the full URL of the request that failed · statusCode repeats the HTTP status inside the body · statusMessage is the literal string Server Error every time, even on a 400 or a 401, because it is a framework default that no endpoint here ever sets; it carries no meaning at all, so do not read it. The HTTP status line itself is that same default — you will see 401 Server Error, not 401 Unauthorized — so do not read that either; read the number. message is a sentence for humans and its wording can change at any time, so never match on it. data.code is our own code, from a limited set, and it is the value these docs pin down. New values of data.code can be added at any time, so always keep a fallback branch for one you do not recognise rather than branching without a default. Orders are the one exception: an order that was created successfully and failed afterwards reports that failure in a field on the order itself, while the HTTP status stays 200.

The wallet balance and the catalogue price are not always the same unit

Every price the API quotes — for a product, for a market listing, for an order — is always in Thai baht, with no currency field beside it, because it can never be anything else. The two other amounts always carry their own currency, and you must read it from the neighbouring field rather than assume baht: wallet.balance here is held in the currency named by wallet.currency, and the amount actually taken for an order (the charged field) is in the wallet's currency at the moment it was taken. Subtracting a catalogue price from a wallet balance to decide whether there is enough money is therefore a unit error the moment the wallet is not in baht.

Request parameters

Header

NameTypeHow to use it
AuthorizationRequiredstring
Your key as Bearer <key>, required on every request to every endpoint, and **issued by the shop's admin only
Details and caveats
  • you cannot sign up for one yourself, there is no test key and no sandbox**, so every request runs against the live system and every order spends real money from a real wallet
  • The key itself is shaped nx_<key_id>_<secret>: the prefix nx, then a 12-character key_id, then a 32-character secret, all of them A-Z a-z 0-9 only, joined by underscores
  • The secret is shown once, when the key is issued. We store only its hash and keep the key itself nowhere, so it cannot be looked up later; lose it and the shop has to issue a new key, which revokes the old one
  • Keep the key on your own server
  • never ship it in front-end code or in an app your end users can open.

Response fields

NameTypeHow to use it
uidstring
The id of the shop account this key is attached to · Purchases are charged to this account's wallet, and every order placed with this key belongs to this account
scopesarray of strings
The permissions (scopes) this key holds, of which there are four in the whole system: catalog:read reads the catalogue and the pre-owned account listings
Details and caveats
  • orders:write places orders and sets the webhook destination
  • orders:read reads your own orders
  • wallet:read this endpoint
  • One key can hold several, and the shop's admin sets them when the key is issued
  • **Scopes are matched exactly
  • there is no wildcard**, and a missing one is an immediate 403 insufficient_scope
  • The scope each endpoint needs is printed at the top of its page
wallet.balancenumber or null
The wallet balance, where null means we could not read it and does not mean zero
Details and caveats
  • treating it as 0 when deciding whether there is enough money turns down your own orders while the money may well be there
  • The wallet is topped up from the shop's website only: there is no top-up through the API, and no endpoint for refunds or warranty claims either; contact the shop for those
wallet.currencystring
The currency of that balance; THB when the account has no currency stored
discount.tier_percentnumber
Your account's discount tier, in percent
Details and caveats
  • Never price with this number: each product type has its own discount cap, so if your tier is above the cap for a type you get the cap, not this figure
  • It is 0 when the account has no discount
  • To see what you actually get, read discount.effective_percent below
discount.effective_percentobject
The discount you actually get per product type, in percent, with that type's cap already applied, keyed by product type
Details and caveats
  • e.g.
  • { "cdkey": 10, "account2": 12 }
  • This is as close to the truth as this endpoint can get, but you still cannot price from it yourself, because an individual product can have a price floor that eats into the discount
  • The correct pattern is to read the price from the catalogue and send that value back as expected_price when you order. Treat these figures as background for your own reporting, not as pricing inputs
quota.limitnumber
Today's quota for this key: 2,000 requests per day by default, counted afresh at midnight Thai time (UTC+7), and adjustable per key
Details and caveats
  • talk to the shop if your real workload exceeds the default
  • The same number as the X-RateLimit-Limit header
quota.remainingnumber
How many requests you have left today, with this one already counted
Details and caveats
  • the same number as the X-RateLimit-Remaining header
  • A request that gets past the key and scope gates always spends quota, whatever the outcome, even when it ends in a 404 or a 409
quota.resets_atISO 8601 string (UTC)
When the counter resets — the same moment as X-RateLimit-Reset, written as a date instead of a Unix timestamp

Possible statuses

StatusCodeMeaning
200
The key works — here are its details
Every field this endpoint returns is in the example
Details and caveats
  • none of them is ever left out, though several can be null, and a null in wallet.balance means we could not read the balance rather than that it is zero
  • New fields can be added to any response without notice, so your parser must skip fields it does not know rather than fail the whole request
  • The response always carries the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers (the last one a Unix timestamp in seconds)
  • Every endpoint in these docs answers with Cache-Control: private, no-store because the prices and amounts returned depend on the calling key
  • never cache a response across resellers or across your own end users.
401invalid_key
The key cannot be used — retrying will not help
Check that the header is there and that the key is the right one, because retrying can never succeed
Details and caveats
  • Five different problems answer identically here, deliberately: no header sent, a header that is not shaped as Bearer <key>, a malformed key, a key we never issued, and the wrong secret
  • telling them apart helps whoever is guessing at keys.
401key_revoked
The key has been revoked — ask for a new one
Ask the shop for a new key; no number of retries will get through
Details and caveats
  • This key does exist and its secret is correct, but the key has been revoked
  • We report it separately from invalid_key precisely so you know the problem is not in your code or your configuration.
403insufficient_scope
The key works, but lacks the scope this endpoint needs
The key itself is fine; it simply does not hold the permission this endpoint requires
Details and caveats
  • The scope each endpoint needs is printed at the top of its own page (for this one, wallet:read), so ask the shop to add that scope to your key
  • The body never says which scope is missing, so read it off the page of the endpoint you just called.
429quota_exceeded
Today's quota is spent — wait for the reset
Read Retry-After and wait exactly that long
Details and caveats
  • never invent your own backoff, because retrying before the reset cannot succeed
  • Today's counter is used up, so X-RateLimit-Remaining is 0 and Retry-After is the whole stretch until the next reset, which can be tens of thousands of seconds.
429too_many_inflight
Too many requests at once — retry in a few seconds
This key currently has more requests open at the same time than its ceiling allows (5 concurrent requests by default, adjustable per key)
Details and caveats
  • Lower your concurrency and try again in a few seconds: this clears itself the moment the requests you already have open finish
  • Note that X-RateLimit-Remaining is not zero
  • your daily quota is fine, concurrency alone is the problem, which is why this needs the opposite handling to quota_exceeded, the other 429.
Last updated 2026-09-07