REST Architecture
A strictly typed, highly atomic engine. Securely drain wallet balances and synchronously map dynamic admin-altered constraints into your SaaS.
Target Instance
https://api.bunaistore.shop/v1
Authentication
Every request must include the X-API-Key
header. Keys are generated per-user inside the Telegram bot. Empty, whitespace-only, or
revoked keys return 401.
-H "X-API-Key: sk_live_a1b2c3d4..."
Type & Error Constraints
-
401
Unauthorized HeaderX-API-Key is invalid, blank, missing, or compromised.
-
402
Insufficient Funds (NSF)Volatile balance calculation failed the lock. Refuse service.
-
403
Forbidden / SuspendedAccount permanently banned. API globally suspended for token.
-
404
Not FoundProduct deleted by admin, or order doesn't belong to you.
-
409
Stock ConflictRace condition: stock was purchased by another buyer between your catalog read and checkout.
-
429
Rate LimitedToo many requests. GET endpoints: 60/min. POST endpoints: 20/min. Per API key. Retry after the
Retry-Afterheader value. -
503
Service UnavailableMarketplace Maintenance Mode active or API globally disabled by admin.
Rate Limits
All /v1/ endpoints are rate-limited per API key. GET responses include Cache-Control: public, max-age=30 headers — clients should respect these to reduce unnecessary calls.
60 req / min
20 req / min
/v1/me
curl -X GET "https://api.bunaistore.shop/v1/me" \
-H "X-API-Key: YOUR_KEY"
{
"user_id": 12345678,
"display_name": "Premium Dev",
"balance": 1500.50,
"api_orders": 45,
"api_spent": 142.00
}
Data Dictionary
user_idinteger
display_namestring |
nullnull if the user has no Telegram
display name set.balancefloat
api_ordersinteger
api_spentfloat
/v1/products
curl -X GET \
"https://api.bunaistore.shop/v1/products?view=variants&type=auto&limit=10" \
-H "X-API-Key: YOUR_KEY"
[
{
"product_id": "ab360422",
"group_id": "d8b627d5",
"group_name": "Amazon Prime",
"variant_name": "1 month",
"display_name": "Amazon Prime > 1 month",
"price": 1.00,
"stock_type": "manual",
"stock_count": 11,
"infinite_stock": false,
"warranty_hours": 1,
"has_promo": false,
"group_share_path": "/group_d8b627d5",
"plan_share_path": "/plan_ab360422_d8b627d5"
}
]
Query Parameters
viewstring |
default: variantsvariants for exact purchasable
plans or groups for grouped
collection cards.typestring |
optional"auto" or "manual". Omit to return all types.
Any other value is silently ignored and returns everything.include_noteboolean |
default: falsetrue,
appends the heavy HTML-formatted product descriptions to each object. Off by default to
optimize bandwidth.offsetinteger |
default: 0limit
for pagination.limitinteger |
default: 50100.
Any value above 100 is silently clamped down.Response Fields
group_id /
variant_namestring
group_share_path /
plan_share_pathstring |
nullapi.bunaistore.shop
for collections and exact plans.stock_typeenum
string"auto" = instant digital delivery
(accounts/keys). "manual" = admin
fulfills the order externally after purchase.stock_countinteger
9999 when infinite_stock is true. Otherwise
reflects real-time available stock. Products with 0 stock are automatically pruned from
results.infinite_stockboolean
true,
unlimited supply. Ignore stock_count.
Admin can toggle this live, switching between unlimited and finite.warranty_hoursinteger
0 = no warranty. Otherwise, the
number of hours the warranty covers from purchase time.has_promoboolean
/v1/products/{id} to inspect the
actual tier breakpoints./v1/product-groups
Use this for collection-level browsing. Grouped products such as Amazon Prime or Netflix families appear once here, with optional embedded plans via include_variants=true.
curl -X GET \
"https://api.bunaistore.shop/v1/product-groups?include_variants=true&limit=10" \
-H "X-API-Key: YOUR_KEY"
/v1/products/{id}
curl -X GET \
"https://api.bunaistore.shop/v1/products/PRD_ajS8x" \
-H "X-API-Key: YOUR_KEY"
{
"product_id": "PRD_ajS8x",
"name": "Netflix Premium",
"note": "Read TOS before buying.",
"price": 3.50,
"stock_type": "auto",
"stock_count": 12,
"infinite_stock": false,
"warranty_hours": 720,
"promo_active": true,
"promo_cb": 5.0,
"promo_tiers": {"5": 5.0, "10": 12.0}
}
Extra Properties (vs List)
notestring
"" (empty string) if not configured
— never null. Admin can update this at any time. Always display to end users before
checkout.promo_activeboolean
promo_tiers has data, discounts only
apply when this is true.promo_cbfloat
0.0 if disabled.promo_tiersobject |
{}{"min_qty": discount_%}. Example:
{"5": 5.0} = buying 5+ items gives
5% off. Returns empty {} when no
tiers configured./v1/orders
curl -X POST "https://api.bunaistore.shop/v1/orders" \ -H "X-API-Key: YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "product_id": "PRD_XYZ", "qty": 1 }'
{
"status": "success",
"order": {
"order_id": "ORDx8kLm2Qp",
"product_id": "PRD_ajS8x",
"product_name": "Netflix Premium",
"qty": 3,
"timestamp": "2024-06-19T14:32:07",
"base_price": 10.50,
"total_paid": 9.98,
"discount_applied": 0.52,
"stock_type": "auto",
"items": [
"Full account details...\nRegion: US\nPlan: Premium...",
"Full account details...\nRegion: CA\nPlan: Premium...",
"Full account details...\nRegion: UK\nPlan: Premium..."
],
"warranty_expires": "2024-07-19T14:32:07",
"refunded": false,
"after_note": "Use CA VPN for login."
}
}
Each string in
the items array can be hundreds of lines long — full account
credentials, license keys, multi-paragraph instructions, or delivery blobs with
\n newlines embedded. Buying
qty: 12 of a text-heavy
product can produce a response exceeding 50KB+. Always handle items as arbitrarily long
strings. Do not truncate or assume single-line values.
Payload Architecture
qtyinteger |
default: 11. Values ≤ 0 trigger
400.include_after_noteboolean |
default: falsetrue,
appends the post-purchase setup instructions to the response. Off by default to save
transit bandwidth.timestampISO
stringbase_pricefloat
price × qty.total_paidfloat
discount_appliedfloat
base_price - total_paid. Returns
0.0 if no promo was active.itemsarray<string>
qty. Each string is one delivered
item — can range from a short email:password to a multi-paragraph blob exceeding 1KB+ with embedded \n newlines (full account details,
license keys, instructions, etc). Always iterate the full array and render each item
independently. Do not assume single-line format.Manual products: Always returns
[] — admin fulfills externally via
bot.warranty_expiresISO string |
nullnull if warranty_hours was 0 at purchase
time.refundedboolean
false.after_notestring
"" (empty
string) if unset or if admin later deletes the product entirely. Do NOT cache — re-fetch
via /v1/orders/{id} for latest
instructions./v1/orders/{id}
curl -X GET \
"https://api.bunaistore.shop/v1/orders/ORD_1A..." \
-H "X-API-Key: YOUR_KEY"
{
"order": {
"order_id": "ORD_1A...",
"product_id": "PRD_ajS8x",
"product_name": "Netflix Premium",
"qty": 1,
"timestamp": "2024-06-19T...",
"total_paid": 3.50,
"stock_type": "auto",
"items": ["email:pass"],
"warranty_expires": "2024-07-19T...",
"refunded": false,
"after_note": "Use Canada VPN."
}
}
Key Differences vs POST
total_paidfloat
base_price and
discount_applied, the lookup only
returns the final paid amount.after_notestring
"" if the product was deleted.refundedboolean
false to true if admin issues a refund after
the original purchase.Developer
Identity & Webhook
Manage your developer profile and configure real-time event delivery. All endpoints require X-API-Key authentication.
/v1/developer/me
Returns your API key, live wallet balance, and registered webhook URL — all in one call. No separate balance endpoint needed.
curl -X GET "https://api.bunaistore.shop/v1/developer/me" \
-H "X-API-Key: YOUR_KEY"
{
"user_id": 12345678,
"api_key": "sk_live_a1b2...",
"webhook_url": "https://mystore.com/webhooks",
"balance": 142.50 // live, always current
}
/v1/developer/webhook
Register or update your webhook URL. Must be publicly reachable over HTTPS. HTTP is rejected.
{ "url": "https://mystore.com/hook" }
/v1/developer/webhook
Clears the registered webhook URL. We stop dispatching events immediately. No body required.
curl -X DELETE \
"https://api.bunaistore.shop/v1/developer/webhook" \
-H "X-API-Key: YOUR_KEY"
You can also configure your Webhook URL directly inside the Telegram bot: Profile → Developer API → Set Webhook URL. Both paths write to the same field.
/v1/topup/addresses
Returns all deposit addresses for every payment method. The enabled field reflects the admin toggle in real-time — never hardcode addresses or assume a method is always active.
curl -X GET "https://api.bunaistore.shop/v1/topup/addresses" \
-H "X-API-Key: YOUR_KEY"
{
"bep20": { "enabled": true, "address": "0xAbC1...", "network": "BSC (BEP-20)", "token": "USDT", "submit_txid": true },
"ton": { "enabled": true, "address": "UQAbc...", "network": "TON", "token": "USDT/TON", "submit_txid": true },
"trx": { "enabled": true, "address": "TXyz...", "network": "TRON (TRC-20)", "token": "USDT/TRX", "submit_txid": true },
"binance": { "enabled": true, "pay_id": "123456789", "name": "BunaiStore", "submit_order_id": true, "order_id_format": "numeric string" }
}
USDT on BSC. Send to address, then submit TXID. Minimum enforced by admin.
USDT-ERC20 or native TON. Comment field not required. Submit TXID after send.
USDT TRC-20 or native TRX on TRON. Submit TXID after confirmation.
Scan QR or use Pay ID. Submit your Order ID (not a TXID) after paying.
/v1/topup/submit_txid
Submit a payment for background verification. Accepted for all 4 methods. Returns immediately with 202 — verification runs async. On success, topup_completed + balance_updated webhooks fire automatically.
// Blockchain (BEP20 / TON / TRX) { "method": "bep20", // bep20 | ton | trx "txid": "0x1a2b3c..." } // Binance Pay (Order ID, not TXID) { "method": "binance", "txid": "40123456789" // numeric string }
{
"status": "success",
"message": "Payment verification started in background"
}
// Fires automatically when confirmed:
// webhook → topup_completed
// webhook → balance_updated
Error Responses
| Code | Error |
|---|---|
400 |
Invalid TXID format |
400 |
Invalid Order ID format |
400 |
Unsupported method |
409 |
Already processing |
409 |
Already credited |
403 |
Payment method disabled |
503 |
Maintenance mode |
503 |
API globally disabled |
500 |
Worker not initialized |
Async Verification Outcomes
After a 202, verification runs in the background. The following outcomes are not HTTP errors — they fire through your registered webhook or are silently logged:
| Outcome |
|---|
| ✓ Confirmed |
| ✗ Not Found |
| ✗ Wrong Recipient |
| ✗ Expired (Binance) |
| ⊘ Below Minimum |
Global Verification Limits
| Method | Max Concurrent | Poll Interval | Timeout |
|---|---|---|---|
bep20 |
4 simultaneous | 5 sec | 20 min |
ton |
Rate-limited (1 rps) | 5 sec | 20 min |
trx |
Rate-limited (1 rps) | 5 sec | 20 min |
binance |
5 simultaneous | 1-2 sec (Instantly shows) | 10 min |
Concurrency note: If the global semaphore for a method is full, your submission queues and waits. High-volume resellers should implement exponential backoff on 409 Already Processing responses rather than looping rapidly.
Binance Pay — Full Flow
-
01
Call
GET /v1/topup/addresses→ usebinance.pay_idandbinance.nameto build a Binance Pay QR or deep link in your storefront UI. -
02
Your customer opens the Binance app, scans the QR or enters the Pay ID, and completes the payment.
-
03
The customer copies the Order ID — a numeric string shown in the Binance payment confirmation screen (e.g.
40123456789). This is NOT a blockchain TXID. -
04
Your system calls
POST /v1/topup/submit_txidwithmethod: "binance", txid: "ORDER_ID". Returns202immediately. -
05
Our server queries the Binance Pay API to verify authenticity, amount, and recipient. If valid → credits balance → fires
topup_completed+balance_updatedwebhooks.
Order ID Format
40123456789 ✓ digits only 4.012e10 ✗ not numeric BNBP_12345 ✗ not numeric
Common Errors
Webhooks & Events
Webhooks & Events
Register a public HTTPS endpoint and we'll push a signed JSON payload every time a balance event fires — no polling required.
Quick Setup
Register your URL via POST /v1/developer/webhook or in the bot: Profile → Developer API
On every balance event we send a signed POST to your URL with JSON payload + X-Webhook-Signature header.
Verify the signature using your API Key as the HMAC-SHA256 secret (see below). Reject anything that fails.
Return any 2xx immediately. Process asynchronously. We retry 3× with 1s/2s backoff on failure.
Webhooks are server-to-server push — they are not triggered by your API calls and do not count against your rate limit. Your account is limited to 100 API requests / minute across all endpoints. Webhook deliveries are dispatched by our async worker pool independently of that quota.
Signature Verification
Every webhook includes an X-Webhook-Signature header — an HMAC-SHA256 hex digest of the raw POST body signed with your API Key. Always verify before processing.
import hmac, hashlib
def verify(api_key, body, sig):
expected = hmac.new(
api_key.encode(),
body, # raw bytes
hashlib.sha256
).hexdigest()
return hmac.compare_digest(
expected, sig
)
const crypto = require('crypto');
function verify(apiKey, body, sig) {
const expected = crypto
.createHmac('sha256', apiKey)
.update(body) // Buffer
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(sig)
);
}
<?php
function verify($apiKey, $body, $sig) {
$expected = hash_hmac(
'sha256',
$body,
$apiKey
);
return hash_equals(
$expected, $sig
);
}
?>
Event Payloads
topup_completed
Fired when payment is verified & balance credited (all 4 methods)
{
"event": "topup_completed",
"method": "bep20", // bep20 | ton | trx | binance
"txid": "0x1a2b...", // tx hash; null for binance (order_id used)
"amount": 25.50, // credited USDT
"bonus": 0.50, // promo bonus if any, else 0
"user_id": 12345678
}
balance_updated
Fired after every balance change — top-up or purchase. Contains live balance.
{
"event": "balance_updated",
"user_id": 12345678,
"new_balance": 167.50, // live wallet balance after change
"delta": 25.50, // +credited / -deducted
"reason": "topup_bep20" // topup_bep20 | topup_ton | topup_trx | topup_binance | order_purchase
}
💡 Use this to stay in sync. You never need to poll GET /v1/developer/me for balance — this event always fires after topup_completed and order_placed.
order_placed
Fired when an order is created — via API or bot
{
"event": "order_placed",
"order_id": "ORDx8kLm2Qp",
"product_id": "PRD_ajS8x",
"product_name": "Netflix Premium",
"qty": 2,
"total_paid": 7.00,
"stock_type": "auto", // auto | manual
"items": ["email:pass", "email2:pass2"], // [] for manual stock
"source": "api", // api | bot
"user_id": 12345678
}
Delivery & Retry
Return 2xx immediately and process async. After 3 failures the event is silently dropped — we do not queue indefinitely. Webhooks do not count against your 100 req/min API quota.
Swagger UI Playground
Authorize with your API Key and execute live HTTP requests directly from your browser. Responses will print natively below.