REST API · Version 1
Buy from the shop, straight from your code.
Browse products, place orders, and get your delivered content back in a single call — paid from the same wallet balance you top up in the bot. Plain HTTPS, JSON in and out, one API key. If you can send an HTTP request, you can use this.
Quickstart
Your first order in three calls
Grab your key from the bot (🔌 Developer API menu → Reveal key), then: check what's in stock, confirm your balance, and buy. Every request carries your key in the Authorization header.
# 1 · list what's in stock
curl https://api-esb.eklas.dev/v1/products?in_stock=true \
-H "Authorization: Bearer tgb_live_9f2c4a…"
# 2 · check your wallet
curl https://api-esb.eklas.dev/v1/balance \
-H "Authorization: Bearer tgb_live_9f2c4a…"
# 3 · buy product #55, two units — the codes come back in the response
curl -X POST https://api-esb.eklas.dev/v1/orders \
-H "Authorization: Bearer tgb_live_9f2c4a…" \
-H "Content-Type: application/json" \
-d '{"product_id": 55, "quantity": 2, "client_order_id": "my-0001"}'
import requests
API = "https://api-esb.eklas.dev/v1"
AUTH = {"Authorization": "Bearer tgb_live_9f2c4a…"}
# browse
products = requests.get(f"{API}/products", params={"in_stock": True}, headers=AUTH).json()
# buy — response already contains the delivered items
order = requests.post(f"{API}/orders", headers=AUTH, json={
"product_id": 55,
"quantity": 2,
"client_order_id": "my-0001", # retry-safe, never double-charges
}).json()
print(order["data"]["status"], order["data"]["items"])
const API = "https://api-esb.eklas.dev/v1";
const auth = { Authorization: "Bearer tgb_live_9f2c4a…" };
const order = await fetch(`${API}/orders`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({ product_id: 55, quantity: 2, client_order_id: "my-0001" }),
}).then(r => r.json());
console.log(order.data.status, order.data.items);
Authentication
Get your key
Open the bot, go to the 🔌 Developer API menu, and tap Reveal key. Your key is tied to your own account and wallet — send it as a Bearer token on every request:
Authorization: Bearer tgb_live_9f2c4a1e7b6d…
Your key starts with tgb_live_ and is shown to you once, so copy it somewhere safe. From that same menu you can:
- Copy your key — tap to copy it.
- Rotate it — get a fresh key; the old one stops working the instant the new one appears.
Want your key locked to specific server IPs, a higher rate limit, or a daily spending cap? Message support and we'll set it up.
Keep it server-side. Your key can spend your wallet balance. Never ship it in a browser, mobile app, or public repo. If it leaks, rotate immediately — the old key dies the moment the new one is issued.
Conventions
Responses & errors
Every response is JSON with a top-level success boolean. Successful calls carry a data object (plus meta for paginated lists); failures carry a structured error.
{
"success": true,
"data": { /* the resource */ },
"meta": { "page": 1, "limit": 50, "total": 128 }
}
{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Your wallet balance is too low for this order.",
"http_status": 402
}
}
Always branch on error.code, not on the human message — codes are stable, messages may be reworded. The full list lives in Error codes.
Catalogue
List products
Returns the products available to you, with your account's pricing already applied. Supports search, category filter, stock filter, and pagination.
Query parameters
| Name | Type | Description |
|---|---|---|
| searchoptional | string | Match against product name and description. |
| category_idoptional | integer | Only products in this category. |
| typeoptional | string | code, telegram_premium, telegram_stars, seat… |
| in_stockoptional | boolean | Hide sold-out items when true. |
| pagedefault 1 | integer | Page number. |
| limitdefault 50 | integer | Items per page, max 100. |
{
"success": true,
"data": [
{
"id": 55,
"name": "Netflix 1 Month — Private",
"description": "Full HD, private profile, 30 days.",
"type": "code",
"price": 4.50,
"currency": "USDT",
"in_stock": true,
"stock": 20,
"unlimited_stock": false,
"delivery": "instant",
"max_quantity": 10,
"requires": [],
"category": { "id": 3, "name": "Streaming" }
}
],
"meta": { "page": 1, "limit": 50, "total": 128 }
}
delivery tells you how the product arrives: instant — the order call returns the content directly (digital codes, supplier-fulfilled, and Telegram Premium/Stars all resolve server-side before responding). manual — a human delivers it, so the order comes back pending_manual and you fetch it once ready.
Catalogue
Get a product
Full detail for a single product, including any extra fields the buyer must supply at checkout (via requires).
{
"success": true,
"data": {
"id": 72,
"name": "Telegram Premium — 3 Months",
"type": "telegram_premium",
"price": 11.00,
"currency": "USDT",
"in_stock": true,
"unlimited_stock": true,
"delivery": "instant",
"max_quantity": 1,
"requires": ["telegram_username"] // pass in customer_payload when ordering
}
}
Catalogue
List categories
The category tree, handy for building filters in your own UI.
{
"success": true,
"data": [
{ "id": 3, "name": "Streaming", "product_count": 18 },
{ "id": 5, "name": "Telegram", "product_count": 7 }
]
}
Orders
How delivery works
Placing an order charges your wallet and returns the finished product in the same response. You never deal with a "processing" state — for digital codes, supplier-backed items, and Telegram Premium/Stars, the API completes fulfillment on the server before it answers, so the delivered content is right there in items. There are just two outcomes to handle:
completed
The default. The wallet was charged and the goods are delivered — read them from items (and content) on the order. Nothing more to do.
delivery: instantpending_manual
Only for products marked manual — a person delivers them by hand. Fetch the order later with GET /v1/orders/{id}; items fills in once staff complete it.
delivery: manualIf delivery can't be completed, you aren't charged. When an instant order can't be fulfilled (e.g. the upstream ran out), it comes back as an error with code delivery_failed and your wallet is automatically refunded — the response includes your restored balance.
Idempotency. Send a unique client_order_id with every order. If a network blip makes you retry, the API returns the same order instead of charging you twice. Reuse the id to safely recover a lost response.
Orders
Place an order
Charges your wallet, fulfils the order, and returns it with the delivered items. For instant products the call blocks briefly while delivery completes, then returns completed.
Body parameters (JSON)
| Name | Type | Description |
|---|---|---|
| product_idrequired | integer | The product to buy. |
| quantitydefault 1 | integer | Units to buy, up to the product's max_quantity. |
| client_order_idrecommended | string | Your own unique id for this purchase. Makes retries safe. |
| customer_payloadconditional | object | Required when the product lists requires (e.g. {"telegram_username": "@ali"} for Premium/Stars, or {"emails": ["[email protected]"]} for seats). |
curl -X POST https://api-esb.eklas.dev/v1/orders \
-H "Authorization: Bearer tgb_live_9f2c4a…" \
-H "Content-Type: application/json" \
-d '{
"product_id": 55,
"quantity": 2,
"client_order_id": "my-0001"
}'
{
"success": true,
"data": {
"id": 9812,
"status": "completed",
"product_id": 55,
"product_name": "Netflix 1 Month — Private",
"quantity": 2,
"total_price": 9.00,
"currency": "USDT",
"client_order_id": "my-0001",
"items": ["NFLX-AAAA-1111", "NFLX-BBBB-2222"],
"content": "NFLX-AAAA-1111\nNFLX-BBBB-2222",
"balance": 33.50,
"created_at": "2026-07-18T14:03:22Z"
}
}
{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Your wallet balance is too low for this order.",
"http_status": 402,
"details": { "balance": 3.00 }
}
}
Orders
Get an order
Fetch a single order by its id or by your client_order_id — a receipt for instant orders, and the way to collect a pending_manual delivery once staff complete it.
{
"success": true,
"data": {
"id": 9813,
"status": "completed",
"product_name": "Telegram Premium — 3 Months",
"quantity": 1,
"total_price": 11.00,
"currency": "USDT",
"items": ["Activated for @ali_dev"],
"content": "Activated for @ali_dev",
"created_at": "2026-07-18T14:05:10Z"
}
}
Orders
List your orders
Your order history, newest first, with optional status filtering and pagination.
Query parameters
| Name | Type | Description |
|---|---|---|
| statusoptional | string | Filter: completed, pending_manual, failed, cancelled. |
| pagedefault 1 | integer | Page number. |
| limitdefault 20 | integer | Items per page, max 100. |
{
"success": true,
"data": [
{ "id": 9813, "status": "completed", "product_name": "Telegram Premium — 3 Months", "total_price": 11.00, "created_at": "2026-07-18T14:05:10Z" },
{ "id": 9812, "status": "completed", "product_name": "Netflix 1 Month — Private", "total_price": 9.00, "created_at": "2026-07-18T14:03:22Z" }
],
"meta": { "page": 1, "limit": 20, "total": 42 }
}
Account
Your account
Profile and wallet snapshot for the key holder.
{
"success": true,
"data": {
"user_id": 6210034,
"username": "eklas",
"balance": 33.50,
"currency": "USDT",
"total_spent": 210.00
}
}
Account
Check balance
A lightweight call for when you only need the wallet figure — e.g. a pre-flight check before a big order.
{
"success": true,
"data": { "balance": 33.50, "currency": "USDT" }
}
Account
Ping
Unauthenticated health check — use it to confirm the API is reachable before wiring up auth.
{ "success": true, "data": { "service": "eklas-buyer-api", "version": "1.0" } }
Reference
Error codes
HTTP status plus a stable error.code. Branch on the code.
Reference
Rate limits
Your key has a request budget, split into two buckets:
| Bucket | Limit | Applies to |
|---|---|---|
| read | 120 / min | All GET endpoints. |
| write | 40 / min | Placing orders (POST). |
If you go over, you get a 429 with a Retry-After header telling you how many seconds to wait — pause for that long, then retry. Need higher limits? Just ask support.
Good to know
Keep your key safe
🔑 Treat it like a password
Your key can spend your wallet balance. Keep it on your server — never in a browser, mobile app, or public repo.
🔁 Rotate if it leaks
Grab a fresh key from the bot's Developer API menu. The old one stops working immediately.
🔒 Always use HTTPS
Call the API over https://. Plain-HTTP requests are redirected.
🧾 Retries can't double-charge
Send a unique client_order_id on each order and a retry safely returns the same order.
🌐 Lock it to your IPs
Ask support to restrict your key to your server's IP addresses for extra safety.
💵 Set a spending cap
Want a daily limit on how much the key can spend? Support can add one for you.
FAQ
Common questions
How do I get a key?
Open the bot → 🔌 Developer API → Reveal key. It's yours alone.
How do I add funds?
Top up your wallet inside the bot (Deposit). The API spends that same balance.
What currency are prices in?
USDT — shown on every product and order as currency.
What if delivery fails?
You're refunded automatically and the call returns delivery_failed — you're never charged for something you didn't get.
Is it safe to retry an order?
Yes. Reuse the same client_order_id and you'll get the same order back, never a second charge.
Do orders arrive instantly?
Digital products come back in the response. Hand-delivered items show pending_manual — fetch the order again once it's ready.