/dev/v1/resellers instead.
Wholesale orders are paid from your reseller balance, not with a card. Top up your balance in
the portal before ordering — there is no payment step in this API.
Authentication
Send your key as a bearer token on every request:401 — invalid, unknown, or revoked key
401 — invalid, unknown, or revoked key
Returned for a missing or malformed
Authorization header, an unrecognised key, and a revoked
key. The message is always Invalid API key. — Shoppex does not distinguish the cases, so a
revoked key cannot be used to probe which keys ever existed.403 — program disabled or account not active
403 — program disabled or account not active
The reseller program of this shop is not active. when the merchant turned the program off.
This reseller account is suspended. when the merchant suspended you. Any other non-active
status returns This reseller account cannot use the API.429 — rate limited
429 — rate limited
See Rate limits. Retry after the window in
retry-after.Errors
Every failure uses one envelope:details appears only on VALIDATION_ERROR. error.balance appears only on
INSUFFICIENT_BALANCE.
Every response — success or failure — carries an
x-request-id header. Quote it when reporting
a problem to the merchant.
An order belonging to a different reseller returns
ORDER_NOT_FOUND, not 403. Ownership is
checked before existence, so the API never reveals that an order exists but is not yours.Idempotency
POST /reseller/v1/orders requires an Idempotency-Key header. It is the only endpoint
that requires one, and the only one that reads it.
- Same key, same payload → the stored response is replayed. No second order, no second
charge. The response carries
idempotency-status: cached, anddata.order.reusedistrue. - Same key, different payload →
409 CONFLICT. - Same key while the first call is still running →
409 CONFLICTwithidempotency-status: processing. Retry shortly. - Keys live 24 hours. After that the key is free again and a reuse creates a real order.
- Maximum key length is 128 characters.
X-Idempotency-Keyis accepted as a synonym.
5xx and 429 responses are never cached, so a transient outage cannot permanently
poison a key. Beyond the cache, the order itself carries an idempotency hash in the database,
so a duplicate cannot be charged twice even if the cache is unavailable — and that backstop
answers the same way the cache does: the original order for the same basket, 409 CONFLICT
for a different one.
The idempotency-status response header tells you which path ran: created, cached,
processing, or bypassed.
Rate limits
A token bucket per key: 30 requests burst, refilling 10 tokens every 2 seconds. It applies uniformly to every endpoint, reads included.
Rejected requests return
429 with code RATE_LIMITED. An invalid key never consumes a token.
GET /reseller/v1/me
Your identity, tier, and progress toward the next tier.next_tier is null when no higher tier has an auto-advance threshold configured. Inside
requirements, revenue and orders are independently nullable — the merchant may have
configured only one of them. requires_both: false means either threshold alone advances you.
GET /reseller/v1/balance
available is balance - hold_balance and is the figure an order is checked against. A
reseller who has never topped up reads all zeros.
GET /reseller/v1/products
The wholesale catalog: only products the merchant enrolled in the program, priced for your tier.applied_override tells you which rung of the price cascade set your price: variant (a
per-variant override), product (a per-product override), or tier (your tier’s flat
discount). stock: -1 means unlimited.
Check orderable, not stock, before you order a row. stock is the merchant’s own local
stock, and when supplier_backed is true the merchant fulfils that product through their
own supplier — the row carries no local units by design and its real quantity limit sits
with that supplier, so this API does not report a number for it. A row with
"orderable": false is the only one POST /reseller/v1/orders refuses as out of stock.
On a product that has variants, the top-level orderable is a gate for the whole
product: it is true when the product itself is orderable or at least one of its
variants is. So filtering a listing on the top-level flag never hides something you could
buy — but once a product is in, still check each variant’s own orderable to pick the option
to order. supplier_backed is never aggregated: it always describes the exact row it sits on.
POST /reseller/v1/orders
Buys stock at wholesale price and pays from your balance in one step. RequiresIdempotency-Key.
- 1–100 items per order, each
quantityan integer between 1 and 100000. variant_idis optional; omit or sendnullfor products without variants.
201:
delivered: false and an empty serials array, partially_delivered becomes true, and
refunded_amount carries the amount credited back to your balance.
partially_delivered also becomes true when a line delivered fewer codes than it ordered,
so compare serials against quantity per line rather than assuming an all-or-nothing line.
refunded_amount is the total credited back to your balance for this order, whatever caused
the refund.
When your balance is too low, the call returns 402 and tells you the gap:
GET /reseller/v1/orders
GET /reseller/v1/orders/
One order with its delivered codes. This is the endpoint to poll or re-read when you need the serials again — they are the same codes a normal buyer would see.serials is always an array, empty when nothing has been delivered for that line — never
null. delivered is simply delivered_at !== null.
Conventions
- Money is always a decimal string (
"425.00"), never a float. Percentages are numbers. - Timestamps in
dataare ISO 8601 strings. - Lists return
datapluspagination(page,per_page,total) — page-based, not cursor-based. - Field names are
snake_casethroughout.
Next Steps
Merchant reseller API
Automate the program from the merchant side.
Reseller webhooks
reseller:* events and their payloads.