KinaStore API
Buy Discord decorations, nameplates, effects and bundles from your own app, bot or store. Every order is paid instantly from your wallet balance and delivered as standard discord.gift links in the response.
JSONEURPlans & access
API access is prepaid. Buy a plan from your wallet balance in Account → API. Your first purchase gives you an API key. Buying again while a plan is active adds the days to the time you have left, and your key stays the same.
When the plan runs out, every request returns 403 plan_expired until you renew. Orders are paid separately from your wallet balance at the item price, so keep your wallet topped up.
Authentication
Send your key in the Authorization header on every request. The X-API-Key header works too.
Authorization: Bearer ks_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keep the key secret and only use it from your server. Never put it in a website or app that other people can open. If it leaks, use Reset key in Account → API: the old key stops working immediately.
Ordering flow
- Find the item: search
GET /catalog, or send Discord shop links directly. - Create the order with
POST /orders. The total is charged from your balance right away. - Gift links in stock come back immediately in
order.items[].links. - If an item shows
missing > 0, its links are still being prepared. PollGET /orders/{id}every 30 to 60 seconds untilstatusisdelivered.
Links are also emailed to your account email. Redeem each link within 47 hours.
Idempotency
Network errors happen. To retry safely, send an Idempotency-Key header with a unique value per order (your own order ID works well). Retrying with the same key returns the original order with "replayed": true and never charges you twice.
Idempotency-Key: my-shop-order-10293
Allowed characters: A-Z a-z 0-9 _ . : -, up to 100 characters.
Errors
Errors return a non-2xx status and a JSON body with a readable message and a stable code to check in your code.
{ "error": "Not enough balance. Top up your wallet first.", "code": "insufficient_balance" }
invalid_requestMissing or invalid body, unknown item, or quantity out of rangeunknown_itemA Discord shop link didn't match any itemmissing_api_keyNo key was sentinvalid_api_keyThe key is wrong or was resetinsufficient_balanceYour wallet balance doesn't cover the order. Nothing was charged.plan_expiredYour API plan ran out. Renew it in Account → API.order_not_foundNo order with this ID on your accountrate_limitedToo many requests. Wait a minute and retry.shop_pausedBuying is paused for maintenance. Retry later.Rate limits
- 120 requests per minute per account
- 30 orders per minute per account
- Up to 10 of the same item and 50 items in total per order
Endpoints
GET/api/v1/me
Your account, balance and plan.
{
"email": "[email protected]",
"balance": 42.50,
"currency": "EUR",
"plan": { "plan": "monthly", "expiresAt": 1795000000000, "active": true }
}
GET/api/v1/balance
Your current wallet balance.
{ "balance": 42.50, "currency": "EUR" }
GET/api/v1/catalog
Search the catalog. All query parameters are optional.
qSearch text, e.g. raccoonkindDecoration, Nameplate, Effect, Frame or BundlecategoryCollection namesortprice_asc, price_desc or namepage, sizePagination{
"currency": "EUR",
"items": [
{ "sku": "1545192554469920779", "name": "Alt Raccoon", "kind": "Decoration",
"category": "All Ears", "price": 1.43, "orig": 4.99, "image": "https://cdn.discordapp.com/…" }
],
"total": 1659, "page": 1, "pages": 48
}
GET/api/v1/items/{sku}
One item with its current price. Returns 404 unknown_item if the SKU isn't sold.
POST/api/v1/orders
Create an order. The total is charged from your balance immediately. Send either items or links.
itemsArray of { "sku": string, "qty": 1-10 }linksDiscord shop link(s), as one string or an array. Repeated links increase the quantity.{ "items": [ { "sku": "1545192554469920779", "qty": 2 } ] }
{ "links": "https://discord.com/shop#itemSkuId=1545192554469920779" }
Response 201 Created:
{
"order": {
"id": "KS-B125312CBAC8",
"status": "delivered",
"total": 2.86,
"createdAt": 1793071787921,
"paidAt": 1793071787922,
"deliveredAt": 1793071787925,
"items": [
{ "sku": "1545192554469920779", "name": "Alt Raccoon", "qty": 2, "price": 1.43,
"links": ["https://discord.gift/AbCdEf123456", "https://discord.gift/GhIjKl789012"],
"missing": 0 }
]
},
"balance": 39.64
}
GET/api/v1/orders
Your orders, newest first. Use limit (1-100, default 20). For the next page, pass the createdAt of the last order you received as before.
{ "orders": [ { "id": "KS-B125312CBAC8", "status": "delivered", "total": 2.86, "items": 2,
"createdAt": 1793071787921, "deliveredAt": 1793071787925 } ] }
GET/api/v1/orders/{id}
One order with its gift links. Same shape as the order object above.
Order statuses
paidCharged. Some links are still being prepared (missing > 0).deliveredAll links are in items[].links and were emailed to you.Code examples
cURL
curl -X POST https://YOUR-DOMAIN/api/v1/orders \
-H "Authorization: Bearer $KINASTORE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-order-001" \
-d '{"links":"https://discord.com/shop#itemSkuId=1545192554469920779"}'
Node.js (18+)
const API = "https://YOUR-DOMAIN/api/v1";
const KEY = process.env.KINASTORE_API_KEY;
async function call(path, options = {}) {
const res = await fetch(API + path, {
...options,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", ...options.headers },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
return data;
}
// Buy one item, retry-safe thanks to the Idempotency-Key
let { order } = await call("/orders", {
method: "POST",
headers: { "Idempotency-Key": "my-order-001" },
body: JSON.stringify({ items: [{ sku: "1545192554469920779", qty: 1 }] }),
});
// Wait until every link is ready
while (order.status !== "delivered") {
await new Promise(r => setTimeout(r, 30000));
({ order } = await call(`/orders/${order.id}`));
}
console.log(order.items.flatMap(i => i.links));
Python
import os, time, requests
API = "https://YOUR-DOMAIN/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['KINASTORE_API_KEY']}"}
def call(method, path, **kwargs):
res = requests.request(method, API + path, headers={**HEADERS, **kwargs.pop("headers", {})}, timeout=30, **kwargs)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['code']}: {data['error']}")
return data
order = call("POST", "/orders",
headers={"Idempotency-Key": "my-order-001"},
json={"items": [{"sku": "1545192554469920779", "qty": 1}]})["order"]
while order["status"] != "delivered":
time.sleep(30)
order = call("GET", f"/orders/{order['id']}")["order"]
print([link for item in order["items"] for link in item["links"]])