Market API

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.

Example Header
-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-After header 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.

GET Endpoints

60 req / min

POST Endpoints

20 req / min


GET

/v1/me

cURL Request
curl -X GET "https://api.bunaistore.shop/v1/me" \
  -H "X-API-Key: YOUR_KEY"
Response
{
  "user_id": 12345678,
  "display_name": "Premium Dev",
  "balance": 1500.50,
  "api_orders": 45,
  "api_spent": 142.00
}

Data Dictionary

user_idinteger
Your Telegram user ID. Always present, never changes.
display_namestring | null
Nullable: Returns null if the user has no Telegram display name set.
balancefloat
Current wallet balance in USD. Volatile — changes on every top-up or purchase.
api_ordersinteger
Counts only orders placed via the API. Bot-placed orders are excluded.
api_spentfloat
Total USD spent via API orders only. Does not include bot purchases or top-ups.
GET

/v1/products

cURL Execution
curl -X GET \
  "https://api.bunaistore.shop/v1/products?view=variants&type=auto&limit=10" \
  -H "X-API-Key: YOUR_KEY"
Payload List
[
  {
    "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: variants
Use variants for exact purchasable plans or groups for grouped collection cards.
typestring | optional
Filter by "auto" or "manual". Omit to return all types. Any other value is silently ignored and returns everything.
include_noteboolean | default: false
If true, appends the heavy HTML-formatted product descriptions to each object. Off by default to optimize bandwidth.
offsetinteger | default: 0
Skip the first N products. Use with limit for pagination.
limitinteger | default: 50
Hardcapped at 100. Any value above 100 is silently clamped down.

Response Fields

group_id / variant_namestring
Grouped catalog variants return both the collection identity and exact plan identity for cleaner client UX.
group_share_path / plan_share_pathstring | null
Public bridge routes on api.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
Returns 9999 when infinite_stock is true. Otherwise reflects real-time available stock. Products with 0 stock are automatically pruned from results.
infinite_stockboolean
Critical: If 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
Indicates bulk discount tiers exist. Use /v1/products/{id} to inspect the actual tier breakpoints.
GET

/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"
GET

/v1/products/{id}

cURL Execution
curl -X GET \
  "https://api.bunaistore.shop/v1/products/PRD_ajS8x" \
  -H "X-API-Key: YOUR_KEY"
Deep Response
{
  "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
Pre-purchase description set by admin. Returns "" (empty string) if not configured — never null. Admin can update this at any time. Always display to end users before checkout.
promo_activeboolean
Whether bulk discount is currently enabled. Even if promo_tiers has data, discounts only apply when this is true.
promo_cbfloat
Global cashback percentage applied on top of tier discounts. 0.0 if disabled.
promo_tiersobject | {}
Map of {"min_qty": discount_%}. Example: {"5": 5.0} = buying 5+ items gives 5% off. Returns empty {} when no tiers configured.
POST

/v1/orders

Submit Request
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
  }'
Response
{
  "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."
  }
}
Large Payload Warning

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: 1
Omitting defaults to 1. Values ≤ 0 trigger 400.
include_after_noteboolean | default: false
If true, appends the post-purchase setup instructions to the response. Off by default to save transit bandwidth.
timestampISO string
UTC timestamp of when the order was atomically committed. Always present.
base_pricefloat
Original total before any promo discount. Calculated as price × qty.
total_paidfloat
Actual amount deducted from wallet after promo tiers applied.
discount_appliedfloat
base_price - total_paid. Returns 0.0 if no promo was active.
itemsarray<string>
Auto products: Array length equals 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 | null
null if warranty_hours was 0 at purchase time.
refundedboolean
Admin can issue refunds retrospectively. Always starts false.
after_notestring
Not stored in order. Fetched live from the product at response time. Returns "" (empty string) if unset or if admin later deletes the product entirely. Do NOT cache — re-fetch via /v1/orders/{id} for latest instructions.
GET

/v1/orders/{id}

cURL Execution
curl -X GET \
  "https://api.bunaistore.shop/v1/orders/ORD_1A..." \
  -H "X-API-Key: YOUR_KEY"
Deep Response
{
  "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
Unlike POST which returns both base_price and discount_applied, the lookup only returns the final paid amount.
after_notestring
Live pointer: NOT stored in order history. Fetched from the product at query time. If admin updates instructions or deletes the product after your purchase, this value changes. Returns "" if the product was deleted.
refundedboolean
Can change from 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.

GET /v1/developer/me

Returns your API key, live wallet balance, and registered webhook URL — all in one call. No separate balance endpoint needed.

cURL
curl -X GET "https://api.bunaistore.shop/v1/developer/me" \
  -H "X-API-Key: YOUR_KEY"
200 OK
{
  "user_id":     12345678,
  "api_key":     "sk_live_a1b2...",
  "webhook_url": "https://mystore.com/webhooks",
  "balance":     142.50   // live, always current
}
POST /v1/developer/webhook

Register or update your webhook URL. Must be publicly reachable over HTTPS. HTTP is rejected.

{ "url": "https://mystore.com/hook" }
200 { "status": "success", "webhook_url": "..." }
400 URL must start with http or https
DELETE /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"
200 { "status": "success", "message": "Webhook URL cleared..." }

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.


GET

/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
curl -X GET "https://api.bunaistore.shop/v1/topup/addresses" \
  -H "X-API-Key: YOUR_KEY"
200 OK
{
  "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" }
}
BEP-20

USDT on BSC. Send to address, then submit TXID. Minimum enforced by admin.

TON

USDT-ERC20 or native TON. Comment field not required. Submit TXID after send.

TRX

USDT TRC-20 or native TRX on TRON. Submit TXID after confirmation.

Binance Pay

Scan QR or use Pay ID. Submit your Order ID (not a TXID) after paying.


POST

/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.

Request Body
// Blockchain (BEP20 / TON / TRX)
{
  "method": "bep20",   // bep20 | ton | trx
  "txid":   "0x1a2b3c..."
}

// Binance Pay (Order ID, not TXID)
{
  "method": "binance",
  "txid":   "40123456789"  // numeric string
}
202 Accepted
{
  "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

  1. 01
    Call GET /v1/topup/addresses → use binance.pay_id and binance.name to build a Binance Pay QR or deep link in your storefront UI.
  2. 02
    Your customer opens the Binance app, scans the QR or enters the Pay ID, and completes the payment.
  3. 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.
  4. 04
    Your system calls POST /v1/topup/submit_txid with method: "binance", txid: "ORDER_ID". Returns 202 immediately.
  5. 05
    Our server queries the Binance Pay API to verify authenticity, amount, and recipient. If valid → credits balance → fires topup_completed + balance_updated webhooks.

Order ID Format

40123456789   ✓ digits only
4.012e10      ✗ not numeric
BNBP_12345   ✗ not numeric

Common Errors

400 — non-numeric characters in txid
409 — Order ID already submitted
(async) expired — payment not completed in time
(async) wrong recipient — ID valid but not ours

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

01

Register your URL via POST /v1/developer/webhook or in the bot: Profile → Developer API

02

On every balance event we send a signed POST to your URL with JSON payload + X-Webhook-Signature header.

03

Verify the signature using your API Key as the HMAC-SHA256 secret (see below). Reject anything that fails.

04

Return any 2xx immediately. Process asynchronously. We retry with 1s/2s backoff on failure.

Webhooks & API Rate Limits

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.

Python
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
    )
Node.js
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
<?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

Max Attempts
1s / 2s
Exponential Backoff
5s
Response Timeout

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.

api.bunaistore.shop / REST V1.0.0
LIVE

Swagger UI Playground

Authorize with your API Key and execute live HTTP requests directly from your browser. Responses will print natively below.