Sell our products on your own website, bot or panel. The AI Store Plus Reseller API gives you the same catalog, prices and automatic delivery as our Telegram store, over a simple JSON API. Orders are paid from your store balance.

Base URLhttps://api.aistoreplus.com/v1
AuthenticationAuthorization: Bearer rk_…
FormatJSON · UTF-8 · HTTPS only
CurrencyUSD (amounts are strings)

1. Get started

  1. Open our Telegram store @aistoreplus_bot and press Start.
  2. Top up your balance in Wallet (USDT TRC20 / BEP20 or Binance Pay). API orders are paid from this balance.
  3. Go to Profile → Reseller API (or send /api) and tap Create new key.
  4. Copy the key right away. It is shown only once: we store only an encrypted fingerprint and cannot show it again. Then delete the message with the button under it.
  5. Call GET https://api.aistoreplus.com/v1/me with your key to confirm everything works.

You can have up to 3 active keys (for example one for production and one for testing) and create up to 5 keys in 24 hours. Revoke a key at any time from the same screen; it stops working immediately.

2. Authentication

Send your key in the Authorization header. X-API-Key is accepted as an alternative.

Authorization: Bearer rk_YOUR_KEY
# or
X-API-Key: rk_YOUR_KEY

A missing, wrong or revoked key returns 401 unauthorized. Every response carries an X-Request-Id header; include it when you contact support.

IP restriction (recommended)

In Reseller API → IP limit you can allow a key only from your server's IP addresses or ranges (IPv4/IPv6, up to 10, e.g. 203.0.113.7, 198.51.100.0/24). Requests from any other address get 403 ip_not_allowed, so a leaked key cannot be used elsewhere.

3. Limits

Requests60 per minute per key. Each response returns X-RateLimit-Limit and X-RateLimit-Remaining. Above the limit: 429 rate_limited with Retry-After: 60.
Invalid keysAfter 20 requests with an invalid key from the same IP within a minute, that IP gets 429 too_many_failed_attempts for a minute.
Quantity1 to max_quantity of the product per order (25 for most products; 1 for products that need customer inputs).
Request bodyUp to 64 KB.

4. Account and balance

GET/meAccount, balance and key details

Returns your balance, total spent, membership tier and the key in use. /balance returns the same data.

Response 200
{
  "customer": "CUS-7K2M9QX4",
  "balance": "125.5000",
  "currency": "USD",
  "total_spent": "310.0000",
  "tier": "Silver",
  "tier_discount": "0.0300",
  "api_key": {
    "prefix": "rk_AbCdEfGh",
    "label": "API key 1",
    "rate_limit_per_minute": 60,
    "ip_restricted": true
  }
}

tier_discount is applied automatically to every order; the catalog shows list prices and the order response shows what you actually paid.

GET/v1API status (no key needed)

Response 200
{
  "name": "AI Store Plus Reseller API",
  "version": "v1",
  "status": "ok",
  "base_url": "https://api.aistoreplus.com/v1",
  "documentation": "https://aistoreplus.com/en/reseller-api/"
}

5. Products

GET/productsEverything you can order

The list is exactly what our Telegram store sells right now. Products that are hidden, not yet added to the store, or delivered manually by our team are not included and cannot be ordered through the API.

Response 200
{
  "total": 2,
  "currency": "USD",
  "products": [
    {
      "id": 127,
      "name": "Example Pro · 18 Months",
      "category": "AI",
      "price": "0.7000",
      "currency": "USD",
      "stock": 1581,
      "in_stock": true,
      "product_type": "code",
      "warranty_days": 0,
      "min_quantity": 1,
      "max_quantity": 25,
      "required_inputs": []
    },
    {
      "id": 2210,
      "name": "Example Team Seat · 1 Month",
      "category": "AI",
      "price": "14.0000",
      "currency": "USD",
      "stock": null,
      "in_stock": true,
      "product_type": "service",
      "warranty_days": 0,
      "min_quantity": 1,
      "max_quantity": 1,
      "required_inputs": [
        { "key": "customer_email", "label": "Email address (the invitation is sent here)" }
      ]
    }
  ]
}
priceList price per unit in USD, as a string with 4 decimals. Never parse it as a float in money calculations.
stockUnits available. null means the stock is not reported (the product is still orderable); 0 means sold out.
in_stocktrue if you can order now.
product_typeready_account, code, coupon or service.
required_inputsInformation the product needs from your customer (see Required inputs). Empty for most products.
max_quantityHighest quantity allowed in one order.

Prices and stock change during the day. Refresh the catalog every few minutes and check in_stock and price right before you sell.

GET/products/{id}One product with its description

Same fields plus description. A product that is not in the store returns 404 product_not_found.

6. Place an order

POST/ordersBuy from your balance

product_idInteger, required. From /products.
quantityInteger, required. 1 to the product's max_quantity.
idempotency_keyString, required, 1–64 characters. Your own unique ID for this order (for example your order number).
inputsObject, only for products with required_inputs: {"customer_email": "buyer@example.com"}.
Request
{"product_id": 127, "quantity": 2, "idempotency_key": "my-order-10045"}
Response 201
{
  "order": {
    "order_code": "ORD-C19QV1RX",
    "status": "completed",
    "status_detail": "delivered",
    "product_id": 127,
    "product_name": "Example Pro · 18 Months",
    "quantity": 2,
    "delivered_quantity": 2,
    "unit_price": "0.7000",
    "total": "1.4000",
    "refunded": "0.0000",
    "currency": "USD",
    "created_at": "2026-10-28T10:15:02.123456+00:00",
    "delivered_at": "2026-10-28T10:15:03.004512+00:00",
    "items": [
      "https://example.com/activate/AAAA-BBBB",
      "https://example.com/activate/CCCC-DDDD"
    ],
    "instructions": "Open the link while signed in to your own account.",
    "outcome": "delivered"
  },
  "replay": false
}

Idempotency: safe retries

If your connection drops, send the same request with the same idempotency_key. You will get the original order back with status 200 and "replay": true; you are never charged twice. Use a new key for every new order. Never reuse a key for a different product or quantity.

What happens to your money

  • The order total is taken from your balance when the order is created. A 402 insufficient_balance means nothing was charged.
  • Most products are delivered instantly: the delivered accounts, codes or links are in items, and the activation note (if any) in instructions.
  • Some products (invitations, upgrades) are completed by the provider a little later: the order stays processing; poll order status.
  • If an order cannot be fulfilled, the amount is refunded to your balance automatically and shown in refunded. If only part is delivered, the rest is refunded (partial).
outcomedelivered all delivered · partial partly delivered, rest refunded · failed not delivered, fully refunded · supplier accepted, completed later · needs_review result unclear, our team checks it (no refund yet) · replay same idempotency_key, original order returned

7. Order status and history

GET/orders/{order_code}One order, with delivered items

Returns the order in the same shape as the order response (with items and instructions). Orders of other accounts return 404 not_found.

statusprocessing in progress · completed delivered · partial partly delivered, rest refunded · refunded fully refunded · failed not delivered · cancelled cancelled
status_detailThe internal state, for logs and support. Build your logic on status.

While an order is processing, poll every 1–2 minutes. Most deferred orders finish within minutes; invitations can take a few hours.

GET/orders?limit=20&offset=0Your orders, newest first

limit 1–100 (default 20). The list does not include delivered items; fetch a single order for those.

Response 200
{
  "total": 37,
  "limit": 20,
  "offset": 0,
  "orders": [
    {
      "order_code": "ORD-C19QV1RX",
      "status": "completed",
      "status_detail": "delivered",
      "product_id": 127,
      "product_name": "Example Pro · 18 Months",
      "quantity": 2,
      "delivered_quantity": 2,
      "unit_price": "0.7000",
      "total": "1.4000",
      "refunded": "0.0000",
      "currency": "USD",
      "created_at": "2026-10-28T10:15:02.123456+00:00",
      "delivered_at": "2026-10-28T10:15:03.004512+00:00"
    }
  ]
}

8. Webhooks (order status notifications)

Instead of polling, let us call your server when an API order is finished. Register one HTTPS URL per account; for every order you place through the API we send a signed POST when it reaches a final status.

PUT/webhookSet (or change) your webhook URL

Request
{"url": "https://example.com/aistore/webhook"}
Response 201
{
  "webhook": {
    "url": "https://example.com/aistore/webhook",
    "created_at": "2026-10-28T13:05:12+00:00",
    "last_success_at": null,
    "last_failure_at": null,
    "last_error": null,
    "consecutive_failures": 0
  },
  "secret": "whsec_k3Jx9Qm2LpV7tR8wYz4AbCdEfGhIjKlMnOpQrStUvWx"
}

The secret is returned only once, when the webhook is created. Store it securely; you need it to verify signatures. Send "rotate_secret": true to get a new secret (the old one stops working immediately). Changing only the URL keeps the secret.

GET/webhookCurrent URL and delivery health

POST/webhook/testSend a signed ping now

Response 200
{"delivered": true, "status": 200, "error": null}

DELETE/webhookRemove the webhook

Events

order.completedAll units delivered; order.items contains them.
order.partialSome units delivered, the rest refunded to your balance.
order.refundedNot delivered, fully refunded.
order.failedNot delivered.
order.cancelledCancelled.
pingSent only by POST /webhook/test; order is null.

The body carries the order in exactly the same shape as GET /orders/{order_code}:

POST to your URL
{
  "id": "evt_1842",
  "event": "order.completed",
  "created_at": "2026-10-28T13:10:41+00:00",
  "order": {
    "order_code": "ORD-C19QV1RX",
    "status": "completed",
    "status_detail": "delivered",
    "product_id": 127,
    "product_name": "Example Pro · 18 Months",
    "quantity": 1,
    "delivered_quantity": 1,
    "unit_price": "0.7000",
    "total": "0.7000",
    "refunded": "0.0000",
    "currency": "USD",
    "created_at": "2026-10-28T13:10:38+00:00",
    "delivered_at": "2026-10-28T13:10:39+00:00",
    "items": ["https://example.com/activate/AbC123"],
    "instructions": "Open the link and sign in with your own account."
  }
}

Headers and signature

X-AIStore-EventEvent name, e.g. order.completed.
X-AIStore-DeliveryUnique event ID (evt_…). The same event can arrive more than once; skip IDs you have already processed.
X-AIStore-TimestampUnix time (seconds) when we signed the request.
X-AIStore-Signaturev1= + hex HMAC-SHA256 of timestamp + "." + raw body, keyed with your secret.
Verify the signature (Python)
import hashlib, hmac, time

def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
    timestamp = headers["X-AIStore-Timestamp"]
    if abs(time.time() - int(timestamp)) > 300:  # older than 5 minutes
        return False
    expected = "v1=" + hmac.new(
        secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, headers["X-AIStore-Signature"])
Verify the signature (PHP)
<?php
$secret    = getenv('AISTORE_WEBHOOK_SECRET');
$raw       = file_get_contents('php://input');           // raw body, before json_decode
$timestamp = $_SERVER['HTTP_X_AISTORE_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_AISTORE_SIGNATURE'] ?? '';
$expected  = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
if (abs(time() - (int) $timestamp) > 300 || !hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);   // $event['event'], $event['order']
http_response_code(200);

Delivery and retries

  • Answer with any 2xx status within 10 seconds. The response body is ignored and redirects are not followed.
  • Otherwise we retry after 30 s, 2 min, 10 min, 30 min, 2 h, 6 h and 12 h (8 attempts in total), then give up. GET /webhook shows the last error.
  • Only orders placed through the API after you set the webhook are sent. You can always check an order with GET /orders/{order_code}.
  • The URL must be https:// and resolve to a public address; local, private and internal addresses are refused.
  • Reject requests whose signature does not match or whose timestamp is older than 5 minutes.

9. Test order

To test your integration end to end, order the product API Test Order from /products. It costs $0.01, is delivered instantly and returns a dummy single-use code such as API-TEST-3F9A1C2B7D10 in items. It is a real order: the amount is taken from your balance, the order appears in /orders and your webhook receives order.completed. The test product is visible only in the API, not in our Telegram store.

10. Required inputs

Some products are delivered to your customer's own account (for example a team invitation). Their required_inputs list tells you what to send in inputs. Such orders are limited to quantity 1. We never ask for passwords.

customer_emailEmail address (the invitation is sent here)
customer_gmailGmail address (the invitation is sent here)
customer_accountEmail or ID of the account to upgrade
customer_account_gmailGmail address of the account to upgrade
customer_usernameUsername of the account to upgrade
customer_phonePhone number in international format (+905551112233)
full_nameFull name
countryCountry

A missing input returns 422 input_required with the missing keys in error.fields; nothing is charged.

11. Errors

Errors always have the same shape. Use code in your logic; message is for people and may change.

Response 402
{
  "error": {
    "code": "insufficient_balance",
    "message": "Insufficient balance."
  }
}
HTTPcodeMeaning
400invalid_json · missing_field · invalid_field · invalid_quantityThe request is malformed. Nothing was charged.
401unauthorizedMissing, wrong or revoked key.
402insufficient_balanceTop up your balance in the bot.
403ip_not_allowedThe key has an IP limit and your IP is not on it.
404product_not_found · not_foundProduct not in the store / order not found.
409out_of_stock · price_changed · product_unavailable · order_refusedCannot be sold right now. Refresh the catalog and retry. Nothing was charged.
422input_required · invalid_inputA required customer input is missing or invalid.
429rate_limited · too_many_failed_attemptsSlow down; see Retry-After.
503api_disabledShort maintenance. Retry later.

12. Code examples

cURL
# Products
curl https://api.aistoreplus.com/v1/products \
  -H "Authorization: Bearer rk_YOUR_KEY"

# Place an order (use a unique idempotency_key per order)
curl -X POST https://api.aistoreplus.com/v1/orders \
  -H "Authorization: Bearer rk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": 127, "quantity": 1, "idempotency_key": "my-order-10045"}'
Python
import requests

API = "https://api.aistoreplus.com/v1"
HEADERS = {"Authorization": "Bearer rk_YOUR_KEY"}

products = requests.get(f"{API}/products", headers=HEADERS, timeout=30).json()["products"]
item = next(p for p in products if p["id"] == 127 and p["in_stock"])

resp = requests.post(
    f"{API}/orders",
    headers=HEADERS,
    json={"product_id": item["id"], "quantity": 1, "idempotency_key": "my-order-10045"},
    timeout=90,
)
data = resp.json()
if resp.status_code in (200, 201):
    order = data["order"]
    print(order["order_code"], order["status"], order.get("items"))
else:
    print("Error:", data["error"]["code"], data["error"]["message"])
PHP
<?php
$api = 'https://api.aistoreplus.com/v1';
$key = 'rk_YOUR_KEY';

$ch = curl_init("$api/orders");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 90,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $key", 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode([
        'product_id'      => 127,
        'quantity'        => 1,
        'idempotency_key' => 'my-order-10045',
    ]),
]);
$body   = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status === 200 || $status === 201) {
    echo $body['order']['order_code'], ' ', $body['order']['status'], PHP_EOL;
} else {
    echo 'Error: ', $body['error']['code'], PHP_EOL;
}
Node.js
const API = "https://api.aistoreplus.com/v1";
const headers = { Authorization: "Bearer rk_YOUR_KEY", "Content-Type": "application/json" };

const res = await fetch(`${API}/orders`, {
  method: "POST",
  headers,
  body: JSON.stringify({ product_id: 127, quantity: 1, idempotency_key: "my-order-10045" }),
});
const data = await res.json();
if (res.ok) {
  console.log(data.order.order_code, data.order.status, data.order.items);
} else {
  console.error(data.error.code, data.error.message);
}

13. Security

  • Keep the key on your server. Never put it in a website, browser extension, mobile app or public repository.
  • Set an IP limit for production keys.
  • If a key may have leaked, revoke it in the bot and create a new one. Revoking takes effect immediately.
  • We store only an encrypted fingerprint of your key. Our team will never ask you for your key or your password.
  • Delivered accounts and codes are in API responses only. Store them securely and deliver them to your customer over a secure channel.

14. Support

Questions or a problem with an order? Write to @aistoreplus on Telegram with the order_code and the X-Request-Id of the response.

API version v1 · last updated 28 October 2026.