| Property | Value |
|---|---|
| Format | JSON (application/json) |
| Auth | X-API-Key header |
| Base URL | https://coindonate.uz/api/v1 |
| Version | v1 (stable) |
| Currency | USD (primary), UZS shown for reference |
X-API-Key header.CoinDrop dashboard → Profile → Security & API Key → Generate key
Format:
cd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
curl https://coindonate.uz/api/v1/balance \ -H "X-API-Key: cd_your_api_key_here"
import requests headers = {"X-API-Key": "cd_your_api_key_here"} r = requests.get("https://coindonate.uz/api/v1/balance", headers=headers) print(r.json())
POST /orders depends on the game. The
games endpoint tells you
exactly which fields a game needs via id_type, amount_based,
requires_server and has_validator. Use those flags to build the right form — no hardcoding.
| Game type | Flags | You send | player_id is… |
|---|---|---|---|
| Standard PUBG, Standoff 2, Blood Strike |
id_type: numeric |
product_id + player_id |
Numeric player ID |
| With server / zone Mobile Legends, Genshin |
requires_server: true |
product_id + player_id + server_id |
Numeric ID (+ Zone ID) |
| Telegram Stars | amount_based: trueid_type: text |
amount (or product_id: amt_N) + player_id |
Telegram @username |
| Telegram Premium | id_type: text |
product_id + player_id |
Telegram @username |
| Steam | amount_based: trueid_type: text |
amount (USD) + player_id |
Steam login |
| Vouchers / gift cards Roblox, Discord, Xbox… |
separate endpoint | product_id + quantity (no player) |
— (a code is returned) |
POST /api/v1/orders
{
"game_key": "pubg-mobile-buykos",
"product_id": "12345", # from /games/{key}/products
"player_id": "52425715863"
}When requires_server: true, add server_id (the server_label tells you what it's called, e.g. "Zone ID"). You can also verify the ID first with validate (this game has has_validator: true).
POST /api/v1/orders
{
"game_key": "mobile-legends-global",
"product_id": "88",
"player_id": "123456789",
"server_id": "2345"
}Two ways: a preset product_id: "amt_50", or a custom amount (any number, min 50). Send the Telegram username as player_id.
# Custom amount — 250 Stars to @durov
POST /api/v1/orders
{ "game_key": "telegram-stars", "amount": 250, "player_id": "durov" }{
"success": true,
"order_id": 512,
"status": "delivered",
"game_key": "telegram-stars",
"product_name": "250",
"player_id": "durov",
"amount_usd": 3.94,
"amount_uzs": 48500,
"message": "Delivered successfully"
}Premium uses ready packages (3 / 6 / 12 months) — pick a product_id from products, and send the Telegram username as player_id.
POST /api/v1/orders
{ "game_key": "telegram-premium", "product_id": "prem_3m", "player_id": "durov" }Steam's amount is a direct USD wallet value (min $0.30). Send the Steam login as player_id. Steam has no validator.
POST /api/v1/orders
{ "game_key": "steam", "amount": 10, "player_id": "mysteamlogin" }Vouchers use their own endpoint and return the code(s) immediately — no player ID needed. See Buy a code.
POST /api/v1/promocodes/games/roblox-us/order
{ "product_id": "80", "quantity": 1 }
→ { "codes": ["ABCD-1234-EFGH"], "pin_code": "ABCD-1234-EFGH" }price_usd + your own markup (set in your profile). What you'd charge your customers.price_usd always reflects exactly
what will be deducted from your balance — you never need to compute it yourself. Subscribe to a
reseller tariff to lower your price_usd.
| Scope | Limit | If exceeded |
|---|---|---|
| All requests | 120 / minute | Blocked for 5 minutes |
| Orders (POST) | 30 / minute | Blocked for 10 minutes |
When blocked, the API returns 429 with the remaining wait time. Space out your
requests and cache the games/products lists on your side (they change rarely).
{
"detail": "Rate limit exceeded (120/60s). Blocked for 300s."
}403 — API access is disabled and no orders are
accepted until it is re-enabled. Check account.api_enabled on the
Account endpoint.
notification_url when creating an order.
When the order reaches a final state, we POST a JSON payload to your URL.
| Field | Description |
|---|---|
| event | order.updated or promocode.order |
| order_id | Your CoinDrop order id |
| status | delivered or failed |
| external_ref | The value you sent when creating the order |
{
"event": "order.updated",
"order_id": 142,
"status": "delivered",
"game_key": "pubg-mobile",
"product_name": "60 UC",
"player_id": "52425715863",
"amount_usd": 0.9208,
"amount_uzs": 11300.0,
"external_ref": "bot_user_42"
}
Most orders finish within ~30s and the webhook fires right away. If an order is still
processing, the webhook is sent later, once it settles. Always use an
https:// URL and respond with 200. Treat the webhook as advisory —
confirm with Order status if needed.
| Code | Meaning | Cause |
|---|---|---|
| 200 | OK | Success |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | API disabled, account blocked, IP not allowed, or game unavailable |
| 404 | Not Found | Resource not found |
| 422 | Unprocessable | Insufficient balance or invalid input |
| 429 | Too Many Requests | Rate limit exceeded |
| 503 | Service Unavailable | Upstream provider temporarily down |
{
"detail": "Error description here"
}Your account overview: balance (USD + UZS), API status, discounts, and which tariffs (subscriptions) are currently active and until when.
curl https://coindonate.uz/api/v1/account \ -H "X-API-Key: cd_your_key"
{
"success": true,
"account": {
"id": 6,
"name": "My Game Shop",
"email": "[email protected]",
"api_enabled": true,
"balance_usd": 39.12,
"balance_uzs": 481200,
"retail_markup_pct": 15.0,
"personal_discount_pct": 0.0,
"total_discount_pct": 3.0,
"has_validator": true
},
"tariffs": [
{
"plan": "Reseller",
"kind": "reseller",
"period": "1m",
"status": "active",
"validator_access": false,
"discount_pct": 3.0,
"expires_at": "2026-08-04T10:00:00"
}
],
"usd_to_uzs": 12300.0
}| Field | Meaning |
|---|---|
| api_enabled | If false, no requests are accepted (403). |
| total_discount_pct | Tariff + personal discount off the CoinDrop margin. |
| has_validator | Whether player-validation is unlocked (Validator tariff). |
| tariffs[] | Active subscriptions with their expiry. |
curl https://coindonate.uz/api/v1/balance \ -H "X-API-Key: cd_your_key"
{
"success": true,
"balance_usd": 39.12,
"balance_uzs": 481200,
"name": "My Game Shop"
}curl https://coindonate.uz/api/v1/games \ -H "X-API-Key: cd_your_key"
{
"success": true,
"games": [
{
"key": "pubg-mobile",
"name": "PUBG Mobile",
"image_url": "https://coindonate.uz/...",
"category": "mobile",
"popular": true,
"id_type": "numeric",
"id_label": "Player ID",
"amount_based": false,
"requires_server": false,
"server_label": "",
"has_validator": false
},
{
"key": "mobile-legends",
"name": "Mobile Legends",
"id_type": "numeric",
"id_label": "Player ID",
"amount_based": false,
"requires_server": true,
"server_label": "Server ID",
"has_validator": true
},
{
"key": "telegram-stars",
"name": "Telegram Stars",
"id_type": "text",
"id_label": "Telegram username",
"amount_based": true,
"requires_server": false,
"has_validator": true
}
]
}| Field | Tells you… |
|---|---|
| id_type | numeric (player ID) or text (Telegram username / Steam login). |
| id_label | What to show the user as the ID field label. |
| amount_based | If true, products use amt_<n> ids (Stars, Steam) — the user picks a quantity. |
| requires_server | Whether a server_id is needed (MLBB, Genshin…). |
| has_validator | Whether you can call the validate endpoint for this game. |
Use these fields to render the right input form per game — no hardcoding needed.
How billing works: your account keeps a stars pool. When a gift is ordered, if the pool has enough stars the gift is sent free (already paid). If not, CoinDrop auto-buys a stars package (minimum 50) at your price, charges your USD balance, and the leftover stays in your pool for the next gifts. You only ever pay for stars — never separately for the gift.
curl https://coindonate.uz/api/v1/gifts \ -H "X-API-Key: cd_your_key"
{
"success": true,
"gifts": [
{
"gift_id": 5170145012310081615,
"emoji": "💝",
"custom_emoji_id": "5465263910414195580",
"stars": 15,
"limited": false,
"price_usd": 0.2364,
"price_uzs": 2900.0,
"min_purchase_stars": 50
}
]
}| Field | Meaning |
|---|---|
| gift_id | Gift ID — pass this to /gifts/send. |
| custom_emoji_id | Premium (custom) emoji ID — render the animated gift emoji in your bot via <tg-emoji emoji-id="…">. |
| stars | Star cost of this gift. |
| price_usd | Value of the gift's stars at your price. Actual charge may be 0 if your pool already has enough stars. |
| min_purchase_stars | If a top-up is needed, this is the minimum stars bought (≥ 50). |
Sends the gift from your connected account. If your stars pool is short, CoinDrop auto-buys the
needed package first and charges your balance. Use dry_run:true to preview the exact
charge without sending.
curl -X POST https://coindonate.uz/api/v1/gifts/send \
-H "X-API-Key: cd_your_key" \
-H "Content-Type: application/json" \
-d '{
"gift_id": 5170145012310081615,
"player_id": "devsardor",
"external_ref": "your-order-id-123",
"notification_url": "https://your-site.com/webhook"
}'| Param | Required | Description |
|---|---|---|
| gift_id | yes | ID from /api/v1/gifts. |
| player_id | yes | Recipient's Telegram @username (without @) or numeric ID. |
| anonymous | no | true = hide the sender's name (gift shows as anonymous). |
| comment | no | Text message attached to the gift (max 255 chars). |
| external_ref | no | Your own order id — echoed back in responses & webhook. |
| notification_url | no | Webhook URL — POSTed when the order finishes. |
| dry_run | no | true = validate & return the exact charge, but do not send. |
{
"success": true,
"order_id": 82,
"status": "delivered",
"game_key": "gift",
"product_name": "🎁 Gift (25⭐)",
"recipient": "devsardor",
"gift_stars": 25,
"stars_bought": 50, # 0 = sent free from pool
"charged_usd": 0.788, # 0 when sent from pool
"charged_uzs": 9600.0,
"message": "Gift delivered"
}{
"success": true, "dry_run": true, "status": "validated",
"gift_stars": 25, "pool_stars": 20,
"will_buy_stars": 50, "would_charge_usd": 0.788, "would_charge_uzs": 9600.0,
"balance_usd": 3746.5
}400 no connected account (connect one in dashboard → Gift akkaunt) 400 connected account has no @username (required to buy stars) 404 gift not found / sold out 422 insufficient balance
Gift orders also appear in List orders
with game_key: "gift".
| Parameter | Type | Description |
|---|---|---|
| game_key | string | Game ID: pubg-mobile, free-fire, mobile-legends… |
curl https://coindonate.uz/api/v1/games/pubg-mobile/products \ -H "X-API-Key: cd_your_key"
{
"success": true,
"products": [
{
"id": "12345",
"name": "60 UC",
"amount": null,
"price_usd": 0.9208,
"price_uzs": 11300,
"retail_usd": 1.0589,
"retail_uzs": 13000,
"image": "/api/product-img/xxx.webp"
}
],
"meta": {
"id_type": "numeric",
"id_label": "Player ID",
"amount_based": false,
"requires_server": false,
"server_label": "",
"has_validator": false
}
}
Each product carries two price tiers, both in USD and UZS:
price_usd/price_uzs (you pay) and
retail_usd/retail_uzs (suggested resale). See
Pricing model.
The meta block repeats the game-type info.
amount_based: true, product ids look like amt_50,
amt_100 … and each includes an amount field. Order them the same
way — just send the amt_<n> id and the username/login as
player_id.
{
"success": true,
"products": [
{ "id": "amt_50", "name": "50 Stars", "amount": 50,
"price_usd": 0.788, "price_uzs": 9700, "retail_usd": 0.9062, "retail_uzs": 11100 },
{ "id": "amt_100", "name": "100 Stars", "amount": 100,
"price_usd": 1.576, "price_uzs": 19400, "retail_usd": 1.812, "retail_uzs": 22300 }
],
"meta": { "id_type": "text", "id_label": "Telegram username", "amount_based": true,
"requires_server": false, "has_validator": true }
}Verify a player ID before ordering (returns the player nickname when supported). Prevents wrong-ID top-ups.
| Field | Type | Required | Description |
|---|---|---|---|
| player_id | string | ✓ | Player / user ID |
| server_id | string | optional | Server / zone (MLBB, Genshin…) |
curl -X POST https://coindonate.uz/api/v1/games/mobile-legends/validate \
-H "X-API-Key: cd_your_key" \
-H "Content-Type: application/json" \
-d '{ "player_id": "123456789", "server_id": "2345" }'
Requires an active Validator tariff (has_validator: true).
Not available for Steam.
{
"success": true,
"valid": true,
"player_id": "123456789",
"username": "PlayerNick",
"premium": false
}{
"success": false,
"valid": false,
"message": "Player not found"
}When an order is placed, price_usd is deducted from your CoinDrop balance. Orders are fulfilled automatically (up to ~30s). If fulfilment fails, your balance is refunded.
| Field | Type | Required | Description |
|---|---|---|---|
| game_key | string | ✓ | pubg-mobile, free-fire, mobile-legends… |
| product_id | string | ✓ | id from the products endpoint. Optional for amount-based games if you send amount. |
| player_id | string | ✓ | Player ID / Telegram username / Steam login |
| amount | number | optional | Amount-based only (Stars/Steam): custom quantity instead of a preset. Stars min 50, Steam min $0.30. |
| server_id | string | optional | Server / region (MLBB, Genshin…) |
| player_name | string | optional | Player nickname |
| external_ref | string | optional | Your internal ID (bot user ID, etc.) |
| notification_url | string | optional | HTTPS webhook — POSTed when the order settles. See Webhooks. |
curl -X POST https://coindonate.uz/api/v1/orders \
-H "X-API-Key: cd_your_key" \
-H "Content-Type: application/json" \
-d '{
"game_key": "pubg-mobile",
"product_id": "12345",
"player_id": "52425715863",
"external_ref": "telegram_user_999"
}'import requests r = requests.post( "https://coindonate.uz/api/v1/orders", headers={"X-API-Key": "cd_your_key"}, json={ "game_key": "pubg-mobile", "product_id": "12345", "player_id": "52425715863", "external_ref": "user_999", }, ) data = r.json() if data["success"]: print("Delivered!", data["order_id"]) else: print("Status:", data["message"])
const res = await fetch('https://coindonate.uz/api/v1/orders', { method: 'POST', headers: { 'X-API-Key': 'cd_your_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ game_key: 'pubg-mobile', product_id: '12345', player_id: '52425715863', external_ref: 'user_999', }), }); const data = await res.json(); console.log(data);
amt_<n> product id, or send a custom amount
(any quantity — Stars min 50, Steam min $0.30). Send the Telegram username / Steam login
as player_id. Telegram Premium uses normal product ids like any other game.
# Preset:
curl -X POST https://coindonate.uz/api/v1/orders \
-H "X-API-Key: cd_your_key" -H "Content-Type: application/json" \
-d '{ "game_key": "telegram-stars", "product_id": "amt_50", "player_id": "durov" }'
# Custom amount (e.g. 177 Stars) + webhook:
curl -X POST https://coindonate.uz/api/v1/orders \
-H "X-API-Key: cd_your_key" -H "Content-Type: application/json" \
-d '{
"game_key": "telegram-stars",
"amount": 177,
"player_id": "durov",
"external_ref": "bot_user_42",
"notification_url": "https://your-server.com/webhooks/coindrop"
}'{
"success": true,
"order_id": 142,
"status": "delivered",
"game_key": "pubg-mobile",
"product_name": "60 UC",
"player_id": "52425715863",
"amount_usd": 0.9208,
"amount_uzs": 11300.0,
"message": "Delivered successfully"
}{
"detail": "Insufficient balance"
}curl https://coindonate.uz/api/v1/orders/142 \ -H "X-API-Key: cd_your_key"
{
"success": true,
"order": {
"id": 142,
"game_key": "pubg-mobile",
"product_name": "60 UC",
"player_id": "52425715863",
"server_id": "",
"amount_usd": 0.9208,
"amount_uzs": 11300.0,
"status": "delivered",
"external_ref": "bot_user_42",
"created_at": "2026-07-02T10:30:00",
"updated_at": "2026-07-02T10:30:22"
}
}
Status values: processing · delivered · failed
| Query param | Default | Description |
|---|---|---|
| page | 1 | Page number |
| per_page | 50 | Items per page (max 100) |
| status | — | delivered | failed | processing |
| date_from | — | From date (YYYY-MM-DD) |
| date_to | — | To date (YYYY-MM-DD) |
curl "https://coindonate.uz/api/v1/orders?page=1&per_page=20&status=delivered&date_from=2026-07-01" \ -H "X-API-Key: cd_your_key"
{
"success": true,
"orders": [
{
"id": 142,
"game_key": "pubg-mobile",
"product_name": "60 UC",
"player_id": "52425715863",
"quantity": 1,
"amount_usd": 0.9208,
"amount_uzs": 11300.0,
"status": "delivered",
"external_ref": "bot_user_42",
"created_at": "2026-07-02T10:30:00"
}
],
"meta": { "page": 1, "per_page": 20, "total": 137, "total_pages": 7 }
}curl https://coindonate.uz/api/v1/promocodes/games \ -H "X-API-Key: cd_your_key"
{
"success": true,
"games": [
{ "key": "roblox-us", "name": "ROBLOX US CARD" },
{ "key": "discord-nitro", "name": "Discord Nitro" },
{ "key": "xbox-usa", "name": "XBOX Giftcards (USA)" }
]
}
Each product shows is_active (live stock). When false, it is out
of stock — don't order it. Prices carry the same USD + UZS tiers as game products.
curl https://coindonate.uz/api/v1/promocodes/games/roblox-us/products \ -H "X-API-Key: cd_your_key"
{
"success": true,
"products": [
{
"id": "80",
"name": "Roblox 10$ US",
"price_usd": 9.717,
"price_uzs": 119500,
"retail_usd": 11.1745,
"retail_uzs": 137400,
"currency": "USD",
"is_active": true,
"stock": 178
}
]
}
Deducts price_usd × quantity from your balance and returns the delivered
codes immediately. On failure your balance is refunded. Out-of-stock items
return 422 before charging.
| Field | Type | Required | Description |
|---|---|---|---|
| product_id | string | ✓ | id from the voucher products endpoint |
| quantity | integer | optional | Number of codes (default 1, max 99) |
| external_ref | string | optional | Your internal ID |
| notification_url | string | optional | HTTPS webhook |
curl -X POST https://coindonate.uz/api/v1/promocodes/games/roblox-us/order \
-H "X-API-Key: cd_your_key" \
-H "Content-Type: application/json" \
-d '{ "product_id": "80", "quantity": 2 }'{
"success": true,
"order_id": 143,
"status": "delivered",
"product_name": "Roblox 10$ US",
"quantity": 2,
"unit_price_usd": 9.717,
"amount_usd": 19.434,
"amount_uzs": 239000,
"codes": ["ABCD-1234-EFGH", "WXYZ-5678-IJKL"],
"pin_code": "ABCD-1234-EFGH",
"message": "Delivered"
}{
"detail": "This item is currently out of stock"
}