Telegram Stars API — MyStars Fulfilment API documentation
Changelog
Every change to the public API, newest first.
- v1.12.0feature
Payment window extended to 2 hours
An order now stays open for payment for 2 hours instead of 1. Nothing changed in the shape of any request or response: expires_at on the order is still the single source of truth for the deadline, it simply lands further out. If you read expires_at rather than assuming a duration, your integration needs no change — and the spec no longer quotes a duration anywhere, precisely so it cannot go stale again. One thing worth re-checking: the SDK helpers that wait for an order to settle default to a 30-minute budget, which is now a quarter of the window rather than half, so raise it if you rely on the default to cover a full payment window.
- v1.11.0feature
Recipient checks now tell you when they could not decide
POST /v1/recipients/check always returns an `indeterminate` boolean. It is `true` when the probe could not reach a verdict and the endpoint failed open — meaning `eligible: true` is a permissive default, not a measurement — and `false` on every real verdict. Previously the two were indistinguishable on the wire, so a degraded probe looked exactly like a confirmed-deliverable recipient. Treat indeterminate as "unknown", never as "yes": proceeding to POST /v1/orders is safe (it runs its own authoritative check), but do not show the buyer a confirmed recipient. Strictly additive — the field was previously absent, and absent means the same as `false`.
- v1.10.0feature
Batch pricing + a dedicated order-lifecycle rate-limit bucket
New GET /v1/pricing/batch quotes up to 200 Stars quantities in ONE request (deduped, sorted; per-entry amount + fee match GET /v1/pricing cent-for-cent) — built for storefronts refreshing a whole pack catalog. Also: POST /v1/orders, GET /v1/orders/{id} and POST /v1/orders/{id}/cancel are now metered by a dedicated per-minute bucket of the same size as your general budget, isolated from read traffic — a burst of pricing or catalog reads can no longer 429 an order create, status poll, or cancel (and vice versa). Both changes are strictly additive; keep reading the RateLimit-* headers.
- v1.9.0feature
Payment window extended to 1 hour
An order now stays open for payment for 1 hour instead of 15 minutes. The order's expires_at field is unchanged in shape and remains the single source of truth for the deadline — it simply lands further out, giving payers more time. No request or response field changed; if you read expires_at (rather than assuming 15 minutes) nothing in your integration needs to change.
- v1.8.2feature
Order create response echoes what was ordered
POST /v1/orders now returns type plus quantity (for Stars) or months (for Premium) — the field that doesn't apply is null — so the created order is self-describing, just like GET /v1/pricing and the order view. You no longer have to correlate the response back to your request. Purely additive; existing fields are unchanged.
- v1.8.1fix
Clearer docs: request-flow diagram + recipient-check guidance
The reference now leads with the recommended call order (pricing → recipient check → order create) and a request-flow diagram in Quick start. POST /v1/recipients/check explains why type is required and warns that a recipient who already has an active Premium subscription can't be gifted Premium — Telegram blocks it (eligible:false, reason already_subscribed; the order would 422). Docs only — no contract change.
- v1.8.0feature
USDT payments show a processing-fee breakdown
For usdt_ton, GET /v1/pricing and the order-create payment block now include a fee object — subtotal, processing_fee (the 1% swap + 0.5 GRAM gas), total and a description — so you can see exactly what the swap costs. The fee was ALWAYS included in amount; this only itemises it (fee.total equals amount, never adds to it). GRAM quotes return fee: null. Purely additive.
- v1.7.1feature
Pricing response echoes what it priced
GET /v1/pricing now returns type plus quantity (for Stars) or months (for Premium) — the field that doesn't apply is null — so the amount is self-describing and you no longer have to correlate the quote back to your request. Purely additive; existing fields are unchanged.
- v1.7.0feature
New: list available products
A new GET /v1/products endpoint returns the product catalog — the two product types (stars, premium) and the buyable shape of each (stars: a continuous quantity range 50–1000000; premium: the fixed tiers 3/6/12 months). It's price-free metadata so you can build your own catalog/UI without guessing the bounds; keep using GET /v1/pricing for the price.
- v1.6.1fix
Reversal terminology standardised
The docs now use 'reversal' consistently to match the reversed order status and the reversal_tx field — the section is now 'Reversals & delivery' and adds a 'When a reversal happens' breakdown of the exact cases. No behaviour change: when and how your funds are returned is unchanged.
- v1.6.0feature
Unmatched payments are auto-refunded
A payment that arrives with no order reference (a missing or unrecognised memo) is now returned to the sender minus the network fee. If it can be attributed unambiguously to one of your pending orders, that order ends failed with failure_reason no_memo / wrong_memo and the refund reference in reversal_tx.
- v1.5.0feature
Rate limits documented; probe cap raised to 30/min
The overview now documents the per-minute request budget, the RateLimit-* / Retry-After headers, the daily order cap and the per-recipient guard. The tighter pricing & recipient-check probe cap was raised from 10 to 30 requests/min.
- v1.4.0feature
Pricing response adds quote freshness + GRAM↔USDT rate
GET /v1/pricing now returns quoted_at and valid_until (a re-quote hint) plus usdt_per_ton (the current public GRAM↔USDT rate; the field name is frozen for wire compatibility).
- v1.3.0feature
Terminal failed and expired order-status webhooks now fire
If you set a callback_url, you now also receive the terminal failed and expired webhook events — previously only delivered and reversed were delivered.
- v1.2.0breaking
Pricing response returns amount + currency only
GET /v1/pricing now returns just the final amount and currency. The previous breakdown object has been removed — amount is already the full, all-in total to pay, so no change is needed unless you read breakdown fields.
- v1.1.0feature
Pay in USDT (TON), plus a price breakdown
Orders and quotes now accept payment_currency: usdt_ton alongside ton (GRAM). The pricing response gained a breakdown object (Fragment cost, markup, gas, and DEX fee + swap gas for USDT).
- v1.0.0breaking
recipient is now an object, not a bare string
Create-order and recipient-check requests take recipient: { username } instead of a top-level username string. Update your request bodies before this version — the old shape is rejected with 400 bad_request.