Secret Game Shop — Reseller API

Sell GNK top-ups, gift cards and subscriptions from your own shop or bot. Orders are paid from your GNK wallet at your reseller price.

Authentication

Base URL: https://api.secretgameshop.com/api/reseller/v1

Send your key in a header on every request:

X-Api-Key: gnk_live_xxxxxxxxxxxxxxxx
# or
Authorization: Bearer gnk_live_xxxxxxxxxxxxxxxx

Create your key on secretgameshop.com → Account → Reseller API (available after GNK enables reseller access for your account). The key is shown once — rotating it disables the old one immediately. Optional IP whitelist and a per-key limit of 120 requests / minute apply.

Keep the key on your server only — never in an app or a web page. If you use the IP whitelist, add both the IPv4 and the IPv6 address of your server: a request from an address that is not listed gets IP_NOT_ALLOWED, and the message shows the address we saw.

Every response is JSON:

{"ok": true,  "data": { ... }}
{"ok": false, "error": {"code": "INSUFFICIENT_BALANCE", "message": "..."}}

All amounts are whole MMK. All timestamps are Myanmar time (UTC+06:30) in ISO 8601, e.g. 2026-09-28T14:30:00+06:30. Responses may carry extra fields (such as category, label, game_slug) — ignore the ones you do not use.

Catalog

GET/games?search=&kind=top_up|gift_card

Games you can sell. fields tells you what to send when ordering; gift cards need no fields. All games come back in one response (no pagination); the list is cached for up to 2 minutes.

{"ok":true,"data":{"total":2,"games":[
  {"id":1,"slug":"mobilelegends","name":"Mobile Legends","kind":"top_up",
   "fields":[{"key":"account_id","required":true},{"key":"server_id","required":true}],"package_count":24},
  {"id":1470,"slug":"gemini-ai-pro-18-months","name":"Gemini AI Pro 18 Months","kind":"gift_card","fields":[],"package_count":1}
]}}
GET/games/{slug or id}

One game with its packages. price is your price: a fixed reseller price when GNK has set one for the package, otherwise retail_price minus your discount_percent. retail_price is the public website price.

{"ok":true,"data":{"game":{"slug":"mobilelegends","kind":"top_up","fields":[...],"account_check":true,
  "packages":[{"id":13,"name":"86 Diamonds","price":5568,"retail_price":5800,"gift_card":false,"stock":null}]}}}

stock is the number you can still buy for packages sold from GNK's own stock, and null when there is no fixed limit. When it is 0, ordering returns OUT_OF_STOCK.

{"ok":true,"data":{"game":{"slug":"gemini-ai-pro-18-months","kind":"gift_card","fields":[],"account_check":false,
  "packages":[{"id":25442,"name":"Gemini AI Pro 18 Months","price":3800,"retail_price":5000,"gift_card":true,"stock":68}]}}}
POST/check-account

Check a player before ordering — free, nothing is charged. Send game (slug or id), account_id, and server_id when the game needs one. Available when the game shows "account_check": true in /games/{slug}; other games return CHECK_NOT_SUPPORTED. Limit: 30 checks / minute.

-d '{"game":"mobile-legends","account_id":"123456789","server_id":"1234"}'

{"ok":true,"data":{"valid":true,"name":"PlayerName","message":null}}
{"ok":true,"data":{"valid":false,"name":null,"message":"Invalid game account"}}

Treat the result as a hint. An order for a wrong ID still fails safely and is refunded.

Wallet

GET/wallet
{"ok":true,"data":{"balance":250000,"currency":"MMK","discount_percent":4}}

Top up your wallet on secretgameshop.com as usual.

Orders

POST/orders
FieldDescription
package_idrequiredFrom /games/{slug}
client_refrequiredYour own unique id for this order (max 100). Sending the same client_ref again returns the existing order — never a second charge. Safe to retry on timeouts.
account_idtop-upsPlayer / user id
server_idif requiredServer / zone id
max_priceoptionalThe most you accept to pay per unit (MMK). If the current price is higher the order is refused with PRICE_CHANGED and nothing is charged.
quantityoptionalUnits to buy in this order, 1–50 (default 1). Above 1 is only available for packages that show a stock number. All-or-nothing: you get that many entries in codes, price is the total and quantity is echoed on the order. If stock is short you get OUT_OF_STOCK and nothing is charged.

Top-ups and packages without a stock number are one unit per order — send one request per unit, each with its own client_ref.

curl -X POST https://api.secretgameshop.com/api/reseller/v1/orders \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"package_id":13,"account_id":"123456789","server_id":"1234","client_ref":"myshop-10001"}'
HTTP 201
{"ok":true,"data":{"duplicate":false,"wallet_balance":244432,"order":{
  "order_id":"SGS-ORD-6F1C…","client_ref":"myshop-10001","status":"processing",
  "game":"Mobile Legends","package_id":13,"package":"86 Diamonds","account_id":"123456789","server_id":"1234",
  "price":5568,"quantity":1,"codes":[],"failure_reason":null,"refunded":false,
  "created_at":"2026-09-28T14:30:00+06:30","updated_at":"2026-09-28T14:30:00+06:30"}}}

Status: processing → completed (gift cards: codes filled) or failed (refunded: true — the price is already back in your wallet). If the order is rejected at placement you get an error and nothing is charged.

Gift card / subscription order

Send only package_id and client_ref. Packages sold from GNK stock usually complete in the same response. Each entry in codes is a string — a code, an activation link or account details — pass it to your customer exactly as received.

-d '{"package_id":25442,"client_ref":"myshop-10002"}'

HTTP 201
{"ok":true,"data":{"duplicate":false,"wallet_balance":246200,"order":{
  "order_id":"SGS-ORD-9A2B…","client_ref":"myshop-10002","status":"completed",
  "game":"Gemini AI Pro 18 Months","package_id":25442,"package":"Gemini AI Pro 18 Months","account_id":null,"server_id":null,
  "price":3800,"quantity":1,"codes":["https://…activation-link…"],"failure_reason":null,"refunded":false,
  "created_at":"2026-09-28T14:35:00+06:30","updated_at":"2026-09-28T14:35:00+06:30"}}}
GET/orders/{order_id or client_ref}

Current status. Poll every 10–30 s if you don't use webhooks.

GET/orders?status=processing|completed|failed&page=1&per_page=20

Your API orders, newest first (max 100 per page).

{"ok":true,"data":{"orders":[ ...same as GET /orders/{id}... ],"page":1,"per_page":20,"total":57}}

Webhooks

Set a webhook URL (https) and generate a webhook secret on the Reseller API page. We POST when an API order's status changes or codes arrive:

POST https://your-shop.example/gnk-webhook
X-Webhook-Signature: sha256=<hex HMAC-SHA256(raw body, webhook secret)>

{"event":"order.status_changed","timestamp":"2026-09-28T14:30:12+06:30","order":{ ...same as GET /orders/{id}... }}

Verify the signature on the raw body, answer any 2xx quickly. Non-2xx is retried (10 s, 30 s, 1 m, 5 m, 15 m). Treat webhooks as a hint — you can always confirm with GET /orders/{id}.

An order that is already completed in the POST /orders response (packages sold from GNK stock, including quantity orders) sends no webhook — everything is in that response. Your endpoint has 10 seconds to answer. After the last retry we stop; read the order with GET /orders/{id}.

// Node.js
const sig = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.get('X-Webhook-Signature') || ''));
// PHP
$raw = file_get_contents('php://input');
$sig = 'sha256=' . hash_hmac('sha256', $raw, $secret);
$valid = hash_equals($sig, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '');
# Python
sig = 'sha256=' + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(sig, request.headers.get('X-Webhook-Signature', ''))

Error codes

HTTPcodeMeaning
401UNAUTHORIZEDMissing / wrong key
403RESELLER_DISABLED · API_DISABLED · IP_NOT_ALLOWEDAccess off, or IP not whitelisted
404GAME_NOT_FOUND · PACKAGE_NOT_AVAILABLE · ORDER_NOT_FOUND
402INSUFFICIENT_BALANCETop up your wallet — nothing charged
409IN_PROGRESSSame client_ref still processing — retry shortly
422VALIDATION_FAILED · ORDER_FAILEDBad input, or supplier rejected — nothing charged
409PRICE_CHANGEDPrice is now above your max_price — nothing charged. Read the new price from /games/{slug}
422OUT_OF_STOCKPackage is sold out — nothing charged. Check stock in /games/{slug} and try again later
422CHECK_NOT_SUPPORTEDAccount check is not available for this game
503CHECK_UNAVAILABLEAccount check is down for the moment — you can still place the order
429RATE_LIMITEDOver 120 requests / minute, or 30 account checks / minute

Retrying. Timeout or 5xx on POST /orders: send the same request again with the same client_ref — you get the existing order back and are never charged twice. 409 IN_PROGRESS or 429: wait a few seconds and retry. Other 4xx: the request itself must change, do not retry as-is.

Prices can change when supplier costs move — always read the current price from /games/{slug} before showing it to your customers.