Reseller API
Sell what Zentra Digital Shop sells, from your own bot or site.
Orders are paid from your Zentra wallet at shop prices and delivered instantly — the same way a purchase in the Telegram bot is. You resell at whatever price you choose.
https://api.zentradigital.shopQuickstart
- Open @ZentraShopBot in Telegram.
- Top up your wallet — Telebirr, Bank of Abyssinia, USDT or Binance Pay.
- Tap API Link → Create API key. It is shown once.
- Send it on every request as a bearer token.
# 1. Check your key and balance
curl https://api.zentradigital.shop/v1/me \
-H "Authorization: Bearer zen_live_YOUR_KEY"
# 2. See what is for sale (no key needed)
curl https://api.zentradigital.shop/v1/products
# 3. Buy something
curl -X POST https://api.zentradigital.shop/v1/orders \
-H "Authorization: Bearer zen_live_YOUR_KEY" \
-H "X-Idempotency-Key: my-first-order" \
-H "Content-Type: application/json" \
-d '{"product_id": 1, "quantity": 1}'Authentication
Every authenticated request carries the key as a bearer token. Anyone holding it can spend your wallet, so keep it server-side — never in a browser, a mobile app, or a public repository.
-H "Authorization: Bearer zen_live_YOUR_KEY"Money
BigDecimal, decimal.Decimal, or a decimal library. Reading "0.90" as a float and summing it is how two systems come to disagree about what was charged.JSON's only numeric type is a binary float, which cannot represent most decimal fractions exactly. Amounts are therefore sent as strings so the digits you read are the digits we charged.
Retrying safely
X-Idempotency-Key on POST /v1/orders. Without it, a retry after a timeout is a second order and a second charge.Pick a value unique to the order you intend to place — a UUID, or your own order id. Retrying with the same value returns the original order with HTTP 200 and does not charge you again. If the first request is still in flight, you get 409 in_progress — wait and ask again.
A key whose order never happened is released, so you can retry with it once the problem is fixed.
Errors
Every failure has the same shape. Branch on error.code, never on the sentence — the wording may change, the code will not.
{
"error": {
"code": "insufficient_stock",
"message": "Only 3 code(s) of Gemini AI Pro 18m are left."
}
}unauthorized401No key, a malformed key, or one that is no longer live.account_suspended403The account behind this key is suspended.api_disabled503The reseller API is closed right now.invalid_request400Malformed body, or a quantity outside 1–1000.order_failed402Your wallet cannot cover it, or the supplier refused and you were refunded.out_of_stock409Nothing left of that product.insufficient_stock409Fewer units left than you asked for.not_available409The supplier has withdrawn it for now.in_progress409An identical idempotency key is still being placed.not_found404No such product or order — or the order is not yours.not_delivered409That order has nothing to export yet.rate_limited429Too many requests this minute.daily_cap_reached429This key's daily spend cap is used up.unresolved202The supplier call died in transit. You were debited and a person is reconciling it — do not retry.unresolved is the one status that is neither success nor failure: your wallet was debited but the supplier's answer never arrived. Do not retry — check GET /v1/orders, and open a ticket in the bot if it does not settle.Limits
Exceeding it returns 429 rate_limited. Back off and retry.
Returns 429 daily_cap_reached. Ask support if you need it raised.
Stock is checked before anything is charged.
Endpoints
/v1/meYour account and balance
Confirms the key works and reports the wallet it spends from. Poll this before a large order rather than discovering a shortfall mid-batch.
curl \
-H "Authorization: Bearer zen_live_YOUR_KEY" https://api.zentradigital.shop/v1/me{
"user_id": 1,
"username": "",
"balance": "42.50",
"currency": "USDT",
"api_key_prefix": "zen_live_A7f3C2d9"
}200Your account.401No key, a malformed key, or a key that is not live. code: unauthorized403The account is suspended. code: account_suspended429Too many requests this minute. code: rate_limited503The reseller API is closed. code: api_disabled/v1/productsList every product
Public — no key needed, so you can build a catalogue before signing up. Sold-out products ARE included, with available: false, so your own storefront can show them as out of stock rather than making them vanish.
curl https://api.zentradigital.shop/v1/products{
"products": [
{
"id": 1,
"name": "Gemini AI Pro 18m",
"description": "",
"unit_label": "code",
"price": "0.90",
"currency": "USDT",
"stock": 1253,
"available": true
}
]
}200The catalogue./v1/products/{product_id}One product
product_idintegerpathrequiredZentra's product id.
curl https://api.zentradigital.shop/v1/products/1{
"id": 1,
"name": "Gemini AI Pro 18m",
"description": "",
"unit_label": "code",
"price": "0.90",
"currency": "USDT",
"stock": 1253,
"available": true
}200The product.404No such product, or it is not on sale. code: not_found/v1/ordersPlace an order
Debits your wallet and fills the order immediately. On success the response already contains the delivered goods — there is nothing to poll for.
Always send X-Idempotency-Key. A network timeout without one leaves you unable to tell whether you were charged.
X-Idempotency-KeystringheaderYour own unique id for this intended order. Retrying with the same value returns the original order rather than placing a second.
product_idintegerrequiredFrom GET /v1/products.
quantityintegerHow many units.
curl -X POST https://api.zentradigital.shop/v1/orders \
-H "Authorization: Bearer zen_live_YOUR_KEY" \
-H "X-Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{"product_id":1,"quantity":1}'{
"id": 1421,
"reference": "ZEN-A7F3C2D9",
"product_id": 1,
"product_name": "",
"quantity": 2,
"total": "1.80",
"currency": "USDT",
"status": "pending",
"created_at": "2026-08-26T19:48:52Z",
"completed_at": "2026-08-26T19:48:52Z",
"delivery": [
{
"label": "Redeem link",
"value": ""
}
]
}200This idempotency key was already used — the ORIGINAL order, unchanged. You were not charged again.201Ordered and delivered.202The supplier call died in transit. Your wallet WAS debited and a human is reconciling it. Do NOT retry — check GET /v1/orders. code: unresolved400Malformed body or out-of-range quantity. code: invalid_request401No key, a malformed key, or a key that is not live. code: unauthorized402Your wallet cannot cover this, or the supplier refused it and you were refunded. code: order_failed403The account is suspended. code: account_suspended409Cannot be filled: out_of_stock, insufficient_stock, not_available, not_active — or in_progress if an identical idempotency key is still being placed.429Rate limited, or this key's daily spend cap is reached. code: rate_limited | daily_cap_reached503The reseller API is closed. code: api_disabled/v1/ordersYour recent orders
Newest first. delivery is omitted here — fetch a single order to read the goods.
limitintegerquerycurl \
-H "Authorization: Bearer zen_live_YOUR_KEY" https://api.zentradigital.shop/v1/orders{
"orders": [
{
"id": 1421,
"reference": "ZEN-A7F3C2D9",
"product_id": 1,
"product_name": "",
"quantity": 2,
"total": "1.80",
"currency": "USDT",
"status": "pending",
"created_at": "2026-08-26T19:48:52Z",
"completed_at": "2026-08-26T19:48:52Z",
"delivery": [
{
"label": null,
"value": null
}
]
}
]
}200Your orders.401No key, a malformed key, or a key that is not live. code: unauthorized403The account is suspended. code: account_suspended429Too many requests this minute. code: rate_limited503The reseller API is closed. code: api_disabled/v1/orders/{order_id}One order, with its delivery
order_idintegerpathrequiredcurl \
-H "Authorization: Bearer zen_live_YOUR_KEY" https://api.zentradigital.shop/v1/orders/13{
"id": 1421,
"reference": "ZEN-A7F3C2D9",
"product_id": 1,
"product_name": "",
"quantity": 2,
"total": "1.80",
"currency": "USDT",
"status": "pending",
"created_at": "2026-08-26T19:48:52Z",
"completed_at": "2026-08-26T19:48:52Z",
"delivery": [
{
"label": "Redeem link",
"value": ""
}
]
}200The order.401No key, a malformed key, or a key that is not live. code: unauthorized403The account is suspended. code: account_suspended404No such order, or it is not yours. code: not_found429Too many requests this minute. code: rate_limited503The reseller API is closed. code: api_disabled/v1/orders/{order_id}/exportThe delivered goods as plain text
One field per line, label: value. Convenient for piping straight into an importer.
order_idintegerpathrequiredcurl \
-H "Authorization: Bearer zen_live_YOUR_KEY" https://api.zentradigital.shop/v1/orders/13/exportRedeem link: https://example.com/redeem/abc
Code: XXXX-YYYY200The goods.401No key, a malformed key, or a key that is not live. code: unauthorized403The account is suspended. code: account_suspended404No such order, or it is not yours. code: not_found409Nothing to export yet. code: not_delivered429Too many requests this minute. code: rate_limited503The reseller API is closed. code: api_disabledSchemas
idintegerrequiredZentra's product id. Use this in POST /v1/orders — it is NOT the supplier's id.
namestringrequiredProduct name.
descriptionstringrequiredThe supplier's own description, de-duplicated across their two text fields, or the shop's override where one is set.
unit_labelstringrequiredWhat one unit is called.
pricestringrequiredPrice for ONE unit, in USDT. Decimal as a STRING, never a JSON number. JSON's only numeric type is a binary float, and a reseller reconciling totals against ours must get the same digits we charged.
currencystringrequiredUSDTAlways USDT. Present so a client never has to assume.
stockinteger | nullrequiredUnits available, or null when the supplier does not count this product. null is NOT zero.
availablebooleanrequiredWhether this can be ordered right now. False when sold out or withdrawn — the product is still listed so you know it exists.
idintegerrequiredZentra's order id.
referencestringrequiredHuman-readable reference. Quote this to support.
product_idintegerrequiredThe product ordered.
product_namestringIts name at the time of ordering.
quantityintegerrequiredUnits ordered.
totalstringrequiredWhat was charged to your wallet, in USDT. Decimal as a STRING, never a JSON number. JSON's only numeric type is a binary float, and a reseller reconciling totals against ours must get the same digits we charged.
currencystringrequiredUSDTAlways USDT.
statusstringrequiredpending · delivered · refunded · faileddelivered is terminal and successful. refunded means the supplier failed and your wallet was put back. pending means it is still being filled.
created_atstringrequiredRFC 3339 timestamp.
completed_atstring | nullWhen it finished, or null.
deliveryobject[]The goods. Present once status is delivered, null before that. Absent entirely from the list endpoint — fetch one order to read it.
Open a ticket in the bot with your order reference — ZEN-… — and a person will answer in that chat. Include the error.code if there was one.