Orders and statuses
Creating an order, its lifecycle, retries and idempotency.
An Order delivers Stars or Premium to one recipient. The order belongs to the Project that created it.
Creating
Checks the recipient, holds the price on your balance and queues the Order (QUEUED).
Repeating with the same externalId returns the existing Order (with different parameters: 409, code -9207).
Stars: quantity 50 to 1,000,000; Premium: months 3, 6 or 12.
Authorization
apiKey ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo
In: header
Header Parameters
8 to 128 characters [A-Za-z0-9._:-], unique per operation
^[A-Za-z0-9._:-]{8,128}$Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/orders" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "externalId": "bot-order-1024", "username": "durov", "product": "STARS", "quantity": 100 }'{ "success": true, "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "externalId": "bot-order-1024", "product": "STARS", "quantity": 100, "months": null, "username": "durov", "recipientName": "Pavel Durov", "status": "QUEUED", "price": "1.65", "currency": "USD", "failure": null, "createdAt": "2026-10-06T09:15:22.443Z", "sentAt": null, "completedAt": null }, "meta": { "requestId": "9f1c2b7e-3a4d-4e5f-8a6b-7c8d9e0f1a2b", "timestamp": "2026-10-06T09:15:22.481Z", "processingTimeMs": 38 }}an order
POST /v1/orders
X-API-Key: ifk_…
Idempotency-Key: 5c0e1d7a-3b1f-4c1e-9d55-0f6b6a1b2c3d
Content-Type: application/json{ "product": "STARS", "quantity": 500, "username": "durov", "externalId": "1042" }| Field | Type | Description |
|---|---|---|
product | STARS | PREMIUM | what to deliver |
quantity | integer | for STARS: from 50 to 1 000 000 |
months | 3 | 6 | 12 | for PREMIUM, instead of quantity |
username | string | recipient: durov, @durov or https://t.me/durov; 4 to 32 characters [a-z0-9_], starts with a letter |
externalId | string | order id in your system (1 to 128 characters), unique within the Project |
A 201 response contains the order object:
{
"id": "0199a1b2-6f00-7abc-9def-112233445566",
"externalId": "1042",
"product": "STARS",
"quantity": 500,
"months": null,
"username": "durov",
"recipientName": "Pavel Durov",
"status": "QUEUED",
"price": "8.25",
"currency": "USD",
"failure": null,
"createdAt": "2026-09-26T10:15:30.000Z",
"sentAt": null,
"completedAt": null
}price is a string in US dollars. failure is set only for FAILED: { "code": "RECIPIENT_NOT_FOUND", "message": "Telegram user not found" }.
When creating an order we:
- check the recipient (through the cache): if it does not exist, we return
422(-9002) right away, and422(-9003) if Premium cannot be gifted to it; no money is held; - lock the price from your price list: it is stored in the order and never changes afterwards;
- hold the amount on your balance (
HOLD), returning402(-9301) if funds are insufficient; - queue the order and respond
201with statusQUEUED.
Statuses
Current state of one order: its status, what was bought and for how much. Poll it after creating an order, or subscribe to the webhook instead of polling.
Authorization
apiKey ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo
In: header
Path Parameters
uuidResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/orders/497f6eca-6276-4993-bfeb-53cbbbba6f08"{ "success": true, "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "externalId": "bot-order-1024", "product": "STARS", "quantity": 100, "months": null, "username": "durov", "recipientName": "Pavel Durov", "status": "SUCCEEDED", "price": "1.65", "currency": "USD", "failure": null, "createdAt": "2026-10-06T09:15:22.443Z", "sentAt": "2026-10-06T09:15:31.902Z", "completedAt": "2026-10-06T09:15:48.215Z" }, "meta": { "requestId": "9f1c2b7e-3a4d-4e5f-8a6b-7c8d9e0f1a2b", "timestamp": "2026-10-06T09:15:22.481Z", "processingTimeMs": 38 }}Project orders, newest first.
Authorization
apiKey ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo
In: header
Query Parameters
1 <= value11 <= value <= 10020QUEUED waiting, PROCESSING in progress, SENT transfer to the supplier sent and awaiting confirmation,
SUCCEEDED delivered, FAILED not delivered (hold released), NEEDS_REVIEW an operator checks the outcome.
Value in
- "QUEUED"
- "PROCESSING"
- "SENT"
- "SUCCEEDED"
- "FAILED"
- "NEEDS_REVIEW"
1 <= length <= 128Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/orders"{ "success": true, "data": [ { "id": "2f1a8b3c-5d6e-4f70-8a9b-0c1d2e3f4a5b", "externalId": "bot-order-1025", "product": "PREMIUM", "quantity": null, "months": 6, "username": "durov", "recipientName": "Pavel Durov", "status": "FAILED", "price": "16.99", "currency": "USD", "failure": { "code": "RECIPIENT_NOT_ELIGIBLE", "message": "The recipient cannot receive this gift (Premium may already be active)" }, "createdAt": "2026-10-06T09:16:02.118Z", "sentAt": null, "completedAt": "2026-10-06T09:16:05.774Z" }, { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "externalId": "bot-order-1024", "product": "STARS", "quantity": 100, "months": null, "username": "durov", "recipientName": "Pavel Durov", "status": "SUCCEEDED", "price": "1.65", "currency": "USD", "failure": null, "createdAt": "2026-10-06T09:15:22.443Z", "sentAt": "2026-10-06T09:15:31.902Z", "completedAt": "2026-10-06T09:15:48.215Z" } ], "meta": { "requestId": "9f1c2b7e-3a4d-4e5f-8a6b-7c8d9e0f1a2b", "timestamp": "2026-10-06T09:20:00.012Z", "processingTimeMs": 21, "pagination": { "page": 1, "limit": 20, "totalItems": 2, "totalPages": 1 } }}QUEUED ──▶ PROCESSING ──▶ SENT ──▶ SUCCEEDED
▲ │ │
└── retry ───┘ ├──▶ QUEUED (proven that the transfer did not go through)
│ └──▶ NEEDS_REVIEW ──▶ SUCCEEDED | FAILED
└──▶ FAILED (permanent error or 3 attempts)| Status | Meaning | Final |
|---|---|---|
QUEUED | in the queue, waiting for a free Supplier Account | no |
PROCESSING | in progress: reservation with the supplier and payment | no |
SENT | payment sent to the supplier, waiting for on-chain confirmation | no |
SUCCEEDED | Stars/Premium delivered, amount charged (CAPTURE) | yes |
FAILED | not fulfilled, hold released (RELEASE), no money charged | yes |
NEEDS_REVIEW | outcome unknown, our support is checking the order, the result will arrive by webhook | no |
Going from QUEUED to SUCCEEDED usually takes 10 to 60 seconds. If all accounts are busy (see throttling), the order waits in QUEUED. This is normal.
Retries
The platform retries for you, you do not need to recreate the order:
- an account problem before the payment is sent (expired session, low wallet funds): the order goes back to
QUEUEDand is picked up by another account, the attempt is not counted; - a temporary supplier or network error before sending: retry after 30 seconds × attempt number; 3 attempts in total, after which the order becomes
FAILEDwith codeATTEMPTS_EXHAUSTED; - a permanent error (recipient not found, Premium already active):
FAILEDright away with codeRECIPIENT_NOT_FOUNDorRECIPIENT_NOT_ELIGIBLE; - after
SENT, the order is retried only if it is proven that the transfer did not go through (the attempt is counted). This rules out double delivery and double payment; if the outcome is unclear within 10 minutes, the order moves toNEEDS_REVIEWand is resolved manually (if rejected, it becomesFAILEDwith codeREJECTED_BY_REVIEW).
failure.code | Reason |
|---|---|
RECIPIENT_NOT_FOUND | the supplier could not find the recipient |
RECIPIENT_NOT_ELIGIBLE | the recipient cannot receive a gift (for example, Premium is already active) |
ATTEMPTS_EXHAUSTED | the order could not be fulfilled in 3 attempts |
REJECTED_BY_REVIEW | the order was cancelled after manual review |
Idempotency
There are two protections against duplicates, use both:
Idempotency-Keyprotects a specific HTTP request. Repeating it with the same key and the same body returns the same response (with theIdempotent-Replayed: trueheader) without creating a second order. The same key with a different body returns422(-11002); if the first request is still running, you get409(-11003), retry a bit later. Error responses are not stored: you can retry a corrected request with the same key. Generate the key once per operation (for example, a UUID) and reuse it on timeouts.externalIdprotects the order in your system. A second order with the sameexternalIdis never created in the Project, even with a newIdempotency-Key: a repeatedPOSTwith the same parameters returns the existing order, and one with different parameters (username,product,quantity,months) returns409(-9207). If you are not sure whether an order was created, look it up:GET /v1/orders?externalId=1042.
Fetching orders
GET /v1/orders/{id}: a single order; an order of another Project returns404(-9201).GET /v1/orders?status=FAILED&externalId=1042&page=1&limit=20: the Project's orders, newest first (limitup to 100); pagination is inmeta.pagination.