iFragment

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

POST
/v1/orders

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
X-API-Key<token>

ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo

In: header

Header Parameters

Idempotency-Key*string

8 to 128 characters [A-Za-z0-9._:-], unique per operation

Match^[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" }
FieldTypeDescription
productSTARS | PREMIUMwhat to deliver
quantityintegerfor STARS: from 50 to 1 000 000
months3 | 6 | 12for PREMIUM, instead of quantity
usernamestringrecipient: durov, @durov or https://t.me/durov; 4 to 32 characters [a-z0-9_], starts with a letter
externalIdstringorder 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:

  1. check the recipient (through the cache): if it does not exist, we return 422 (-9002) right away, and 422 (-9003) if Premium cannot be gifted to it; no money is held;
  2. lock the price from your price list: it is stored in the order and never changes afterwards;
  3. hold the amount on your balance (HOLD), returning 402 (-9301) if funds are insufficient;
  4. queue the order and respond 201 with status QUEUED.

Statuses

GET
/v1/orders/{id}

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
X-API-Key<token>

ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo

In: header

Path Parameters

id*string
Formatuuid

Response 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  }}

GET
/v1/orders

Project orders, newest first.

Authorization

apiKey
X-API-Key<token>

ifk_<clientId>_<secret>, for example ifk_48aa52d67c0fef6decb05b36_lVlZSWKN43sgb2vfEpFbqtzQ0ePNZya6yZr-RmJ4wxo

In: header

Query Parameters

page?integer
Range1 <= value
Default1
limit?integer
Range1 <= value <= 100
Default20
status?string

QUEUED 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"
externalId?string
Length1 <= length <= 128

Response 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)
StatusMeaningFinal
QUEUEDin the queue, waiting for a free Supplier Accountno
PROCESSINGin progress: reservation with the supplier and paymentno
SENTpayment sent to the supplier, waiting for on-chain confirmationno
SUCCEEDEDStars/Premium delivered, amount charged (CAPTURE)yes
FAILEDnot fulfilled, hold released (RELEASE), no money chargedyes
NEEDS_REVIEWoutcome unknown, our support is checking the order, the result will arrive by webhookno

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 QUEUED and 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 FAILED with code ATTEMPTS_EXHAUSTED;
  • a permanent error (recipient not found, Premium already active): FAILED right away with code RECIPIENT_NOT_FOUND or RECIPIENT_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 to NEEDS_REVIEW and is resolved manually (if rejected, it becomes FAILED with code REJECTED_BY_REVIEW).
failure.codeReason
RECIPIENT_NOT_FOUNDthe supplier could not find the recipient
RECIPIENT_NOT_ELIGIBLEthe recipient cannot receive a gift (for example, Premium is already active)
ATTEMPTS_EXHAUSTEDthe order could not be fulfilled in 3 attempts
REJECTED_BY_REVIEWthe order was cancelled after manual review

Idempotency

There are two protections against duplicates, use both:

  • Idempotency-Key protects a specific HTTP request. Repeating it with the same key and the same body returns the same response (with the Idempotent-Replayed: true header) without creating a second order. The same key with a different body returns 422 (-11002); if the first request is still running, you get 409 (-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.
  • externalId protects the order in your system. A second order with the same externalId is never created in the Project, even with a new Idempotency-Key: a repeated POST with the same parameters returns the existing order, and one with different parameters (username, product, quantity, months) returns 409 (-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 returns 404 (-9201).
  • GET /v1/orders?status=FAILED&externalId=1042&page=1&limit=20: the Project's orders, newest first (limit up to 100); pagination is in meta.pagination.

On this page