Ключ передаётся в заголовке. Он привязан к вашему аккаунту: цены, баланс и заказы — ваши.
Заголовок
Authorization: Bearer gs_live_ваш_ключ
Ключ лежит в кабинете, в «Настройках», там же его можно перевыпустить. Не кладите его в код фронтенда — запросы к нам делает только ваш сервер.
Подробности
Ограничения: 120 запросов в минуту на чтение и 20 в минуту на создание заказов, отдельно для боевого и тестового ключа. При превышении придёт 429 с заголовкомRetry-After— он говорит, через сколько секунд повторить. Заказы ограничены строже намеренно: цикл там тратит деньги, и в отличие от чтения это не отменить ожиданием. У методов Telegram-подарков сверх этого свои, более низкие лимиты на аккаунт — см. «Telegram Gifts».
Работает сейчас
Эти методы доступны и отвечают.
GET/ping
Проверка ключа
Первое, что стоит вызвать при подключении: подтверждает, что ключ рабочий, и возвращает аккаунт, к которому он привязан. Поле project — то же самое под старым именем, оставлено для уже написанных интеграций.
mode — live или test. Проверяйте его при старте: ключ, попавший не в тот конфиг, молчит ровно до момента, когда заказ либо не придёт, либо потратит настоящие деньги.
GET/catalog
Каталог
Каталог один на всех, и в нём только то, что есть в наличии. Цены — ваши: сколько за товар платите нам вы.
cost_* — сколько платите нам вы; розничную цену для своего покупателя ставите у себя. Пагинация идёт по has_more, а не по общему числу: считать точное количество на каждый запрос — лишняя нагрузка, а листать вперёд это не мешает. include=offers вкладывает номиналы прямо сюда — это ровно те же строки и те же цены, что отдаёт /catalog/{id}, читаются одним запросом на всю страницу вместо запроса на каждый товар. Витрине на 50 товаров это экономит 50 обращений. Учтите, что ответ становится заметно больше: если нужен только список названий и вилка цен, не просите.
GET/catalog/{id}
Товар и номиналы
Конкретные суммы пополнения или варианты ключа с ценой каждого. Товар, которого в каталоге нет — снят с продажи или id неверный, — вернёт 404.
cost — сколько вы платите нам. Розничную цену вы ставите у себя: поля price_* и price из каталога убраны 15.09.2026 вместе с настройкой наценки в кабинете — она всё равно ни на что у нас не влияла, а два места для одного числа расходились. stock: null означает, что поставщик не считает остаток по этой позиции — это не ноль. min_qty и max_qty — пределы поставщика на quantity; null значит «без ограничения», выход за них отвергается до списания денег. У пополнений (topups) quantity всегда равен 1 независимо от этих полей: количество задаётся выбором номинала. fields — что спросить у покупателя перед заказом. Все перечисленные обязательны. Ключи задаёт поставщик и они различаются от игры к игре — у 8 Ball Pool это user_id, у Age of Empire Mobile player_id плюс server_id, у AFK Journey account_id, — поэтому читайте их отсюда, а не зашивайте. Именно эти ключи уходят в объект fields при покупке; пустой список означает, что спрашивать нечего. У каждого поля есть type: "text" — свободный ввод, "select" — закрытый список, и тогда рядом лежит непустой options, а значение обязано быть ровно одним из options[].value (сервер поставщика отвергнет любое другое уже после списания). Поля type и options добавлены 15.09.2026; старые интеграции их просто не читают, ключи и label не менялись.
GET/balance
Баланс
Остаток на счёте аккаунта и последние движения по нему. Тестовым ключом — игровой счёт песочницы.
available — то, что реально можно потратить прямо сейчас: сравнивайте цену с ним, а не с balance. Обычно это одно и то же число, но если мы открыли вам овердрафт, overdraft показывает разрешённый минус, а balance после его использования становится отрицательным — это не ошибка ответа. Овердрафт по умолчанию нулевой, включается только по договорённости. Исключение — Telegram-подарки: овердрафта у них нет, и цену подарка сравнивайте с balance. order_id — заказ, за который списали: по нему можно найти заказ, ответ на создание которого до вас не дошёл. У пополнений и корректировок он null.
GET/products
Что принимают свои товары
Границы сумм, доступные валюты и обязательные поля — те же самые, по которым заказ проверяется при создании. Нужен, чтобы форма не давала ввести то, что мы потом откажемся принять.
telegram_gift — Telegram-подарки: цены здесь нет, у каждого лота она своя (GET /gifts/{slug}). available у подарков значит только, что они подключены: этот метод к сервису подарков не ходит и его не ждёт. Открыты ли продажи прямо сейчас, говорит available в карточке лота и в котировке; если закрыты — заказ ответит 503 gifts_unavailable, ничего не списав. Подробно — в разделе «Telegram Gifts». usd_rub — курс для вашей рублёвой витрины, тот же, что отдаёт GET /rates. Он НЕ равен currencies[].rate у steam_topup и не должен им подменяться: тот курс — делитель, которым сумма в рублях превращается в номинал, и счёт считается по нему. Границы Steam заданы в долларах номинала и пересчитаны по курсу поставщика на момент ответа. min округлён вверх, max вниз, так что значение, взятое отсюда, гарантированно пройдёт проверку. Кэшируйте на минуту — в ответе это поле cache_seconds, и то же самое приходит заголовком Cache-Control: private, max-age=60. Чаще незачем: курсы у поставщика мы перечитываем раз в две минуты (rates_refresh_seconds), так что минутный кэш не сделает вас устаревшее нас. Лимит метода — 120 запросов в минуту на ключ, отдельный от лимита /quote. limit_scope: order — тысяча долларов это предел на один заказ; ограничений на сутки нет, потолком служит баланс. available: false у steam_topup означает, что поставщик или его курсы сейчас недоступны — заказы по нему не примутся.
GET/rates
Курс для рублёвой витрины
Сколько рублей стоит доллар, если считать по нему розницу. Наши цены, балансы и пополнения — в долларах; этот курс нужен, чтобы перевести их в рубли у себя.
Это НЕ тот курс, что лежит в /products → steam_topup → currencies[].rate. Тот — делитель, которым сумма в рублях превращается в долларовый номинал, и счёт за пополнение Steam считается именно по нему: возьмёте для номинала этот курс — и посчитанная вами сумма разойдётся с нашей, а значения у краёв диапазона будут отклонены. Здесь — курс для витрины: по нему считайте свою розницу, он учитывает стоимость конвертации рублей в доллары. Кэшируйте на минуту (cache_seconds, тот же Cache-Control: private, max-age=60), лимит общий с чтением — 120 запросов в минуту. То же число дублируется в /products полем usd_rub, чтобы не делать второй запрос, если вы и так туда ходите.
POST/recipient
Кто получит подарок
Имя и аватар владельца username в Telegram — то же, что показывает форма Fragment. Плюс ответ на вопрос, можно ли этому человеку подарить выбранное.
Параметры
product
telegram_stars или telegram_premium. Для Telegram-подарка — telegram_gift, см. «Telegram Gifts»
username
Получатель, без @
months
Для Premium: 3, 6 или 12
quantity
Для звёзд: сколько. На то, кто получатель, не влияет
Три поля, три разных вопроса. found — нашли ли человека; can_receive — можно ли выдать ему именно это; reason — что это значит для получателя; resolution — что ответил источник в этой попытке. Решение о продаже принимайте по can_receive, карточку рисуйте по found, текст покупателю пишите по reason, а resolution берите только в логи. reason: found; has_premium — Premium у него уже есть; not_resolvable — за этим ником никого не удалось получить; unavailable — отказ по другой причине; unknown — источник не ответил, found и can_receive придут null, заказ создавать можно. Мы НЕ отдаём «такого пользователя нет» и не будем: снаружи отсутствующий аккаунт и аккаунт, который Fragment не отдаёт, — это один и тот же ответ, и @durov тому пример. resolution: fragment_ok — ник разрешён; fragment_said_no — Fragment заявил, что такого ника нет (иногда ошибочно); fragment_declined — отказал без объяснения; fragment_silent — не ответил. Для steam_topup — supplier_ok / supplier_said_no / supplier_silent. Для Steam передавайте product: steam_topup и steam_login вместо username; reason там cannot_refill, если Steam не примет пополнение. Показывайте имя и аватар покупателю до оплаты: это снимает большую часть ошибок в username. photo — прямая ссылка на CDN Telegram, она может смениться, не кэшируйте её надолго. Ответ кэшируется у нас на минуту. Лимит общий с /quote — 60 запросов в минуту.
POST/quote
Цена без покупки
То же тело, что у покупки, но ничего не создаётся и не списывается. Нужен, чтобы посчитать свою цену покупателю до заказа: у звёзд, Premium и пополнения Steam цена берётся из живых курсов, и в каталоге её нет.
Параметры
offer
Товар каталога: id номинала из GET /catalog/{id}
product
Свои товары: telegram_stars, telegram_premium или steam_topup. Telegram-подарок — telegram_gift, см. «Telegram Gifts»
quantity
Для звёзд и каталога — количество
months
Для Premium: 3, 6 или 12
username
Для Premium: получатель. Без него цена ориентировочная — см. estimate
steam_login
Для Steam не нужен: цена от суммы, а не от аккаунта
estimate: true бывает только у Premium: цену за конкретного получателя называет Fragment, и когда её получить не удалось — имя не передали или Fragment не ответил — в ответе стоит наша сохранённая база, то есть ориентир, а не обещание. Передавайте username: тогда при estimate: false цена ровно та, что спишется при заказе, в ответе появляется recipient с именем и аватаром получателя, а если подарить нельзя — придёт 422 recipient_refused с причиной и с тем же recipient внутри объекта error, ещё до того как вы возьмёте деньги с покупателя. Отдельный запрос к POST /recipient при этом не нужен: у Premium имя и аватар приходят вместе с ценой, у звёзд — одним кэшируемым запросом. Лимит у метода свой: 60 запросов в минуту, отдельно от лимита заказов.
POST/orders
Покупка
Создаёт заказ и сразу списывает деньги с баланса. Выдача идёт в фоне — статус смотрите следующим методом.
Параметры
offer
Товар каталога: id номинала из GET /catalog/{id}
fields
Для пополнений: данные покупателя из поля fields того же ответа
product
Свои товары: telegram_stars, telegram_premium или steam_topup. Telegram-подарок — telegram_gift, см. «Telegram Gifts»
username
Для Telegram: получатель, без @
quantity
Для звёзд — сколько штук, минимум 50; для каталога — сколько единиц
months
Для Premium: 3, 6 или 12
steam_login
Для Steam: логин аккаунта, латиницей
amount
Для Steam: сумма зачисления на аккаунт
currency
Для Steam: валюта суммы, например RUB или KZT
client_ref
Ваш номер заказа, обязателен. Повтор с тем же значением вернёт прежний заказ, а не купит второй
Обязательно передавайте client_ref. Если запрос отвалится по таймауту, повтор с тем же значением вернёт тот же заказ и не купит второй раз — ответ будет 200 вместо 201.
POST/orders
Пополнение Steam
Тот же метод, но сумму называете вы: amount — сколько зачислить на аккаунт, currency — в какой валюте. Цена считается по курсу на момент заказа.
Логин, а не отображаемое имя: деньги уходят на тот аккаунт, который назвали, и вернуть их нельзя. client_ref обязателен.
GET/orders
Список заказов
Ваши заказы, новые сверху. Нужен, чтобы сверить день, чтобы найти заказ, id которого вы не сохранили, — и чтобы следить за выдачей: вебхуков мы не шлём.
Параметры
status
processing, delivered или failed
client_ref
Ваш номер заказа. Точное совпадение — вернётся не больше одного
created_from
С какой даты: YYYY-MM-DD или YYYY-MM-DD HH:MM:SS, UTC
created_to
По какую. Дата без времени включает весь день
updated_since
Что изменилось с этого момента, старые изменения первыми. Так следят за выдачей без вебхука
page
Страница, с 1
per_page
Сколько на странице: по умолчанию 50, максимум 100
Коды выданных товаров в списке НЕ приходят — как и данные покупателя из fields. Нашли нужный заказ, забрали коды через GET /orders/{id}: так один утёкший ключ не выгружает всю историю кодов одним запросом. has_more — это не общее число: при активной торговле страницы съезжают, поэтому для сверки надёжнее сужать created_from и created_to, чем листать вглубь. Заказы Telegram-подарков приходят здесь же, с product: telegram_gift и блоком gift, и фильтр status работает для них так же.
GET/orders/{id}
Статус заказа
Три состояния: processing — в работе, delivered — выдано, failed — не получилось, деньги вернулись на баланс. Здесь же приезжает сам товар: у ключей и подарочных карт коды лежат в delivery.items.
delivery появляется только у товаров каталога и только когда status = delivered: до выдачи там null, а не пустой список. В items столько кодов, сколько прислал поставщик — обычно по одному на единицу quantity, но сверяйте с длиной массива, а не считайте по quantity. У пополнений (topups) кодов нет вовсе, там items пустой: товар ушёл прямо на игровой аккаунт из fields. Для звёзд, Premium и Steam полей item, fields и delivery нет — это не каталог. У Telegram-подарка вместо них блок gift с этапом покупки — см. «Ход покупки» в разделе «Telegram Gifts».
Telegram Gifts
Уникальные подарки Telegram, выставленные на продажу на Fragment: витрина, точная цена, покупка и передача получателю. Работает с 26.09.2026.
Продаётся конкретный лот — один подарок со своим номером, моделью, фоном и узором, выставленный по фиксированной цене. Мы покупаем его и передаём получателю в Telegram: тот становится полноправным владельцем, подарок виден по ссылке t.me/nft/…. В выдаче только лоты «купить сразу» — без аукционов и проданных — и только коллекции из нашего сверенного списка.
Как проходит покупка
1
GET/gifts/collections
Коллекции, которые мы продаём, и значения фильтров к ним — GET /gifts/collections/{collection}. Оба ответа держите у себя: меняются редко.
2
GET/gifts
Лента лотов с ориентировочной ценой; листается по next_offset.
3
GET/gifts/{slug}
Карточка: свойства, картинки, анимация и точная цена для вас.
4
POST/recipient
С product: telegram_gift — примет ли человек подарок. Имя и аватар — покупателю на подтверждение.
5
POST/quote
Точная цена и хватает ли баланса — прямо перед тем, как брать деньги.
6
POST/orders
С max_price — потолком цены из котировки. Деньги списываются сразу.
7
GET/orders/{id}
Или GET /orders?updated_since=… — опрос до delivered или failed. Обычно это минута-две.
Цена в ленте — оценка.
price_estimate считается по цене, которую показывает Fragment, и обычно выше точной, но бывает и чуть ниже. Точную называют карточка и POST /quote — брать деньги с покупателя стоит по ней.
Ваше число — только потолок.
max_price в заказе обязателен. Лот дороже — 409 price_changed с новой ценой, ничего не списано; дешевле — спишется меньшее. Уменьшить счёт ваше число не может.
В минус подарки не продаются.
Овердрафта у подарков нет, даже если он вам открыт: сравнивайте цену с balance, а не с available. Так считает и enough в котировке. Не хватает — 402 insufficient_funds.
Один лот — один заказ.
Пока по лоту идёт заказ — до delivered или failed, в том числе когда подарок уже куплен и ждёт передачи, — второй получит 409 gift_reserved. mine: true и номер в order — это ваш же заказ: следите за ним, а не создавайте новый. mine: false — лот покупает кто-то другой.
Лот уникален и может уйти.
Между карточкой и заказом его могут купить на Fragment. До оплаты это 409 gift_not_buyable, деньги не списаны; после — заказ закончится failed, и деньги вернутся.
Возврат — когда подарок точно не куплен.
Если подарок уже куплен, а передать его не выходит, деньги сами не возвращаются: заказ ждёт на этапе bought, а когда передача остановлена — needs_action говорит почему. Дело в получателе — назовите другого.
Лимит на получателя.
Одному человеку за сутки уходит ограниченное число заказов звёзд, Premium и подарков — считаются все заказы ему, не только ваши. Подарки идут в число заказов, но не в сумму: дорогому подарку лимит по сумме не мешает. Упёрлись — 429 recipient_daily_limit с числами в полях; это не про частоту, и Retry-After у него нет.
Доставка — минута-две.
Обычно столько проходит от заказа до подарка в Telegram. Узнаёте опросом: вебхуков мы не шлём, а в бота о доставке подарка не пишем — туда приходит, только если деньги вернулись или получатель не может принять подарок.
Картинки и анимация — файлы Fragment.
images — JPEG 320 и 640 px, animation — Lottie JSON: это не картинка, его проигрывают lottie-web или другим плеером Lottie. Браузер покупателя грузит их прямо с CDN Fragment, ключ не нужен. Навсегда у себя не копируйте: файлы не наши.
Каталог не для обхода.
Витрина и цены читаются через общую для всех очередь, и у аккаунта в ней своя доля. Честная пропускная способность — около трёх заказов-подарков в минуту на аккаунт: каждый заказ заново читает лот. Не обходите и не зеркальте каталог — читайте ленту, когда её листает покупатель.
Лимиты на аккаунт, в минуту
Сверх общих лимитов ключа — свои у каждого метода подарков, отдельно для боевого и тестового ключа. Чтение сверх лимита — не отказ: ответ приходит из того, что уже прочитано, и 429 rate_limited — только если отдать нечего. У всех таких 429 заголовок Retry-After: 60.
GET/gifts30, дальше — из уже прочитанного
GET/gifts/collections20, дальше — из уже прочитанного
GET/gifts/collections/{collection}20, дальше — из уже прочитанного
GET/gifts/{slug}12, дальше — из уже прочитанного; цены там может не оказаться
POST/quote10, дальше — цена из уже прочитанного
POST/recipient8, дальше — из уже прочитанного; username в /quote считается сюда же
POST/orders6, дальше — 429 сразу
POST/orders/{id}/recipient5, дальше — 429 сразу
Подробности
Кроме счётчиков метода, у аккаунта одна доля в общей очереди — на кабинет, боевой и тестовый ключ вместе: чтение тестовым ключом настоящее и стоит столько же. Кончилась доля — то же «из уже прочитанного» и тот же 429. Поэтому тесты, которые гоняют витрину тестовым ключом в цикле, могут на минуту оставить боевую витрину того же аккаунта без свежих данных.
Покупка с потолком
// Точная цена — прямо перед оплатой. Её же отдаёте потолком в заказ.
const quote = await post("/quote", {
product: "telegram_gift",
gift: "winterwreath-4614",
username: "ivan",
});
// …покупатель согласился на quote.price и заплатил вам…
const res = await post("/orders", {
product: "telegram_gift",
gift: "winterwreath-4614",
username: "ivan",
max_price: quote.price, // дороже не спишется, дешевле — спишется меньшее
client_ref: "shop-order-1026", // повтор после обрыва вернёт тот же заказ
});
if (res.error?.code === "price_changed") {
// Лот подорожал, ничего не списано. Новая цена — res.error.price:// покажите её покупателю и повторите с новым потолком.
}
GET/gifts/collections
Коллекции
Коллекции подарков, которые мы продаём. Только сверенные нами, поэтому их может быть меньше, чем на Fragment. slug — то, что передаётся в collection у GET /gifts.
count — сколько подарков в коллекции, по данным Fragment. image — картинка коллекции с Fragment, бывает null. Список меняется редко: кэшируйте на десять минут — cache_seconds, и то же в заголовке Cache-Control: private, max-age=600. Лимит — 20 запросов в минуту на аккаунт; сверх него ответ приходит из того, что уже прочитано.
GET/gifts/collections/{collection}
Модели, фоны и узоры
Значения фильтров одной коллекции. У каждого подарка есть модель, фон и узор; value — ровно то, что передаётся обратно в model, backdrop и symbol у GET /gifts.
count — сколько подарков коллекции с этим значением, от других фильтров не зависит. image — картинка с Fragment, бывает null. Коллекции нет или мы её не продаём — 404 not_found. Эти списки почти не меняются: кэшируйте на час (cache_seconds, Cache-Control: private, max-age=3600). Лимит — 20 запросов в минуту на аккаунт; сверх — из уже прочитанного.
GET/gifts
Лента подарков
Лоты, которые можно купить сразу, по фиксированной цене — без аукционов и проданных, только в коллекциях, которые мы продаём. С ориентировочной ценой, картинками и анимацией.
Параметры
collection
Слаг коллекции из GET /gifts/collections. Без него — все наши коллекции вместе
model
Значение из GET /gifts/collections/{collection}. Повторяется: model=A&model=B. Только вместе с collection, до 100 значений
backdrop
Так же: фон, до 100 значений
symbol
Так же: узор, до 300 значений. Каждое значение — не длиннее 100 символов
search
Поиск, до 64 символов
sort
price_asc — сначала дешёвые (по умолчанию), price_desc — сначала дорогие, listed — недавно выставленные
offset
next_offset прошлой страницы. Первая страница — без него или 0; больше 10000 не принимается
price_estimate — ориентир в долларах, а не цена: он считается по цене, которую показывает Fragment, и обычно выше точной. Точную называют GET /gifts/{slug} и POST /quote. Посчитать не из чего — null. Пагинация по offset, а не по page, как у каталога: так листает сам Fragment. Страница — один его шаг, из которого мы убрали всё, что купить нельзя, поэтому она бывает короче и даже пустой при has_more: true. Пустая страница — не конец: листайте по next_offset, пока has_more не станет false, а next_offset — null. sort=ending — 400: эта сортировка про аукционы, а их в ленте нет. Фильтр без collection, слишком много значений или слишком длинное — тоже 400; коллекции нет или мы её не продаём — 404 not_found. Лента у нас в кеше около минуты. Лимит — 30 запросов в минуту на аккаунт; сверх — из уже прочитанного, и 429 rate_limited, только если отдать нечего.
GET/gifts/{slug}
Карточка подарка
Всё о лоте: свойства с редкостью, картинки, анимация, адрес NFT и точная цена для вашего аккаунта — то же, что страница подарка в кабинете.
buyable: false — лот сейчас не купить, причина кодом в reason: not_for_sale — продан или снят с продажи; in_telegram — подарок сейчас в Telegram, а не на продаже; collection_not_supported — коллекцию мы не продаём. buyable: null — цену сейчас прочитать не удалось: reason тогда rate_limited (лимит аккаунта, а в кеше цены нет) или gifts_unavailable, и price, available и reserved тоже null. Карточка при этом приходит — рассмотреть подарок можно и без цены. price — точная цена для вас сейчас, у нас она держится около 30 секунд; заказ читает лот заново. available: false — заказы на этот лот сейчас не принимаются: цену показать можно, заказ получит 503 gifts_unavailable. available: null — не знаем: сверх лимита карточка собрана из уже прочитанного, а свежего ответа о продажах по этому лоту у нас нет; заказ проверит сам. reserved: null — лот свободен; {mine: true, order} — его уже покупает ваш заказ; {mine: false} — чужой. Пока reserved не null, новый заказ на лот получит 409 gift_reserved, даже при buyable: true; когда тот заказ подарок уже купил, приходит buyable: false без цены — лот ждёт передачи по нему. rarity_pct — сколько процентов подарков коллекции с тем же значением; issued — номер выпуска и тираж; address — адрес NFT; links.telegram — где подарок видно в Telegram. Чего о подарке сейчас не знаем, то приходит null. Такого подарка нет — 404 not_found. Тестовым ключом цена настоящая, а reserved всегда null и available — true: так лот увидит тестовый заказ. Лимит — 12 запросов в минуту на аккаунт; сверх — из уже прочитанного.
POST/recipient
Примет ли получатель
Тот же метод, что у звёзд и Premium, с product: telegram_gift — и вопрос другой: примет ли человек уникальный подарок. Имя и аватар покажите покупателю до оплаты.
Поля те же, что у звёзд, — и правило то же: продавайте по can_receive, карточку рисуйте по found, resolution — только в логи. reason: found — примет; not_resolvable — за этим ником никого не удалось получить; unavailable — аккаунт есть, но подарок не примет, например закрыл подарки в настройках приватности; unknown — Fragment не ответил, found и can_receive придут null. Ответ держится у нас минуту, и заказ в эту минуту не ищет получателя заново — поэтому проверяйте перед оплатой, а не на каждое нажатие клавиши. Неверный формат username — 400 bad_request. Лимит — 8 проверок в минуту на аккаунт, в него же идёт username в POST /quote; сверх — ответ только из кеша, а если там пусто — 429 rate_limited с Retry-After: 60.
POST/quote
Цена подарка перед оплатой
Точная цена лота для вас, хватает ли баланса и можно ли его купить — ничего не покупая. Нужна прямо перед тем, как брать деньги с покупателя: эту цену и передавайте в max_price заказа.
Параметры
product
telegram_gift
gift
Слаг лота из GET /gifts
username
Необязательно: получатель — тогда он проверяется заодно
enough — хватает ли balance, без овердрафта: у подарков его нет. Лот, который купить нельзя, — не ответ, а 409 gift_not_buyable с кодом в reason (те же коды, что в карточке), поэтому в ответе 200 buyable — true, а reason — null, кроме одного случая ниже. Лот под заказом и закрытые продажи — не отказ, а поля, как в карточке, и заказ на такой лот не пройдёт. reserved не null — по лоту идёт заказ ({mine: true, order} — ваш, следите за ним), новый получит 409 gift_reserved; пока тот заказ подарок не купил, цена приходит как обычно, а после покупки — buyable: false, reason, price: null и enough: false. available: false — продажи на этот лот закрыты; null — котировка собрана из уже прочитанного (сверх лимита), а свежего ответа о продажах нет. kind — то же значение под тем именем, которым /quote называет товар у звёзд и Premium. С username получатель проверяется так же, как в POST /recipient: не примет — 422 recipient_refused с reason (not_resolvable или unavailable) и recipient внутри error; с боевым ключом name и photo в нём всегда null — Fragment не называет того, кто подарок не примет. Имя и аватар там бывают только в песочнице, у gs_sandbox_nogifts: не стройте на них экран. Fragment не ответил — recipient: null, а цена приходит. Без username поля recipient нет. Слаг не того вида — 400 bad_request, такого подарка нет — 404 not_found, подарки временно недоступны — 503 gifts_unavailable. Лимит — 10 котировок в минуту на аккаунт; сверх — цена из уже прочитанного, пусто — 429 rate_limited с Retry-After: 60. Тестовым ключом: цена настоящего лота, balance — игровой, reserved всегда null, available — true, и в ответе sandbox: true.
POST/orders
Покупка подарка
Создаёт заказ и сразу списывает цену лота — не больше вашего max_price. Дальше мы покупаем подарок и передаём его получателю; ход смотрите опросом.
Параметры
product
telegram_gift
gift
Слаг лота из GET /gifts
username
Получатель, без @
max_price
Потолок цены в USD, обязателен: число или строка с числом. Доли цента отбрасываются: 7.019 — это потолок 7.01. Лот дороже — заказа не будет, дешевле — спишется меньшее
client_ref
Ваш номер заказа, обязателен. Повтор с тем же значением и тем же лотом вернёт прежний заказ, а не купит второй, — даже если первый запрос ещё не ответил
test_scenario
Только тестовым ключом: сценарий песочницы. Боевым — 400
Всё проверяется до списания: бронь лота, получатель, лот, ваш потолок и баланс. Лот читается заново, мимо кеша; ответ о получателе — тот же, что у POST /recipient, не старше минуты. 201 — заказ создан и оплачен; 200 — повтор с тем же client_ref и тем же лотом, вернулся прежний заказ и ничего не списано — так же, если повтор пришёл, пока первый запрос ещё шёл. Тот же client_ref на заказе другого вида (звёзды, Premium, Steam, каталог) или на подарке с другим лотом — 409 client_ref_conflict. Отказы, которые стоит обработать: 409 price_changed — лот дороже max_price, новая цена в поле price (покажите её покупателю и повторите с новым потолком); 409 gift_not_buyable — лот купили или сняли, код в reason; 409 gift_reserved — по лоту идёт заказ, и так до его конца, в том числе когда подарок уже куплен и ждёт передачи: mine: true и номер в order — это ваш заказ, следите за ним; mine: false — чужой; 422 recipient_refused — reason not_resolvable или unavailable и recipient внутри error (с боевым ключом name и photo в нём null); 402 insufficient_funds — balance меньше цены, overdraft в нём 0; 429 recipient_daily_limit — суточный лимит на получателя; 503 gifts_unavailable — подарки временно не принимаются, в том числе когда Fragment не ответил о получателе. Заказ ходит к Fragment за лотом, а бывает, и за получателем, и это может занять до минуты: таймаут на своей стороне ставьте не меньше 60 секунд, а после обрыва повторяйте с тем же client_ref. Лимит — 6 заказов-подарков в минуту на аккаунт сверх общего лимита заказов; сверх — 429 rate_limited с Retry-After: 60.
GET/orders/{id}
Ход покупки
Заказ-подарок читается тем же GET /orders/{id} и приходит в GET /orders — с блоком gift вместо item и delivery. status — как у всех: processing, delivered или failed; gift.stage — подробнее, для прогресса на экране покупателя.
{
"order": {
"id": "ord_XXXXXXXX",
"status": "processing",
"product": "telegram_gift",
"username": "ivan",
"price": 6.48,
"updated_at": "2026-09-26 12:05:02",
"delivered_at": null,
"error": null,
"gift": {
"slug": "winterwreath-4614",
"title": "Winter Wreath #4614",
"link": "https://t.me/nft/WinterWreath-4614",
"recipient_name": "Sergey Drozdov",
"stage": "bought",
"needs_action": {
"reason": "Получатель не может принять подарок — возможно, закрыл их в настройках приватности или сменил username. Укажите другого получателя.",
"can_change_recipient": true
},
"refunded": false
}
}
}
Подробности
gift.stage: paid — оплачен и ждёт очереди; buying — покупаем; bought — куплен, ждёт передачи; sending — передаём получателю; delivered — у получателя, status: delivered; failed — не вышло, status: failed. Всё до delivered и failed — status: processing. needs_action не null, только когда передача остановлена и ждёт решения: reason — текст для человека; can_change_recipient: true — дело в получателе, назовите другого через POST /orders/{id}/recipient; false — задержка на нашей стороне, делать ничего не нужно. Подарок в это время уже куплен, и деньги сами не возвращаются. У failed в error написано, вернулись ли деньги, а gift.refunded: true значит, что возврат уже на балансе; false при failed — он в пути и придёт в течение нескольких минут. link — где подарок видно в Telegram, recipient_name — имя получателя, как его назвал Fragment. quantity у подарка всегда 1.
POST/orders/{id}/recipient
Сменить получателя
Когда передача остановлена из-за получателя — закрыл подарки, сменил username, — назовите другого. Подарок уже куплен и ждёт; новый получатель проверяется так же, как при заказе.
{
"order": {
"id": "ord_XXXXXXXX",
"status": "processing",
"product": "telegram_gift",
"username": "petr",
// остальные поля — как в GET /orders/{id}"gift": {
"slug": "winterwreath-4614",
"title": "Winter Wreath #4614",
"link": "https://t.me/nft/WinterWreath-4614",
"recipient_name": "Petr Ivanov",
"stage": "bought",
"needs_action": null,
"refunded": false
}
}
}
Подробности
Только пока у заказа gift.needs_action.can_change_recipient: true. В остальное время — 409 bad_state: передача не остановлена из-за получателя (идёт сама или ещё не начиналась — вмешательство только сбило бы её), заказ уже закрыт (delivered или failed) или только что изменился. Перечитайте GET /orders/{id} и решайте по нему. Чужой заказ, не подарок или заказ другого режима — 404 not_found. Новый получатель не примет — 422 recipient_refused с reason; упёрся в суточный лимит — 429 recipient_daily_limit; неверный формат username — 400 bad_request; Fragment не ответил — 503 gifts_unavailable, повторите позже. После смены передача продолжается сама — следите за заказом как обычно. Лимит — 5 вызовов в минуту на аккаунт; сверх — 429 rate_limited с Retry-After: 60. То же самое можно сделать в кабинете, на странице подарка.
Песочница
Второй ключ, gs_test_…, к тому же аккаунту. Тот же каталог, те же цены, те же коды ошибок — но игровые деньги, и ни одного обращения к поставщику или к Fragment, кроме чтения витрины и цен Telegram-подарков: они в песочнице настоящие.
Режим решает ключ, а не запрос.
Поле в теле, которое отменяет списание, — слишком дешёвая вещь, чтобы на ней держалась граница. Какой ключ прислали, такой и режим; подделать нельзя.
Миры не пересекаются.
Тестовый ключ не видит боевых заказов и не может ими управлять, боевой не видит тестовых. Один и тот же client_ref можно прогнать сначала в песочнице, потом в бою.
Подарки: витрина настоящая, покупка — нет.
Лента, карточка и цена лота читаются живьём, из той же доли аккаунта, что у боевого ключа. Заказ списывает игровые деньги по цене настоящего лота, лот не бронирует, и настоящий подарок не покупается никогда.
Заказ не мгновенный.
Обычный тестовый заказ идёт около десяти секунд и проходит те же промежуточные статусы, что боевой. Мгновенная выдача научила бы вас коду, который никогда не видел processing.
Сценарии — поле test_scenario при создании заказа
okпо умолчанию: выдача примерно через 10 секунд
fastвыдача на ближайшем тике, 1–5 секунд — для CI
slowвыдача через 3 минуты: проверить, что долгий processing не считается ошибкой
supplier_refusedпровал с кодом SUPPLIER_ERROR, игровые деньги возвращены
invalid_fieldsпровал: «Данные покупателя не прошли проверку»
recipient_unavailableпровал с кодом RECIPIENT_NOT_FOUND
out_of_stockсинхронный 409 в ответе на POST — заказ вообще не создаётся
stuckвисит в работе; через час закрывается по таймауту с возвратом
needs_recipientтолько для telegram_gift — см. ниже; у остальных товаров 400
Те же сценарии у Telegram-подарка — product: telegram_gift
okвсе этапы gift.stage — оплачен, покупаем, куплен, отправляем, доставлен — примерно за 10 секунд
fastвсе этапы на ближайшем тике
slowте же этапы за 3 минуты
supplier_refused«лот купили раньше нас»: failed с текстом «Подарок уже купили или сняли с продажи, деньги возвращены», игровые деньги вернулись
out_of_stockсинхронный 409 gift_not_buyable, reason: not_for_sale — заказ не создаётся
stuckвисит на этапе «покупаем»; через час — failed с возвратом
needs_recipientкуплен и остановлен: needs_action с can_change_recipient: true, пока вы не вызовете POST /orders/{id}/recipient; потом отправляем и доставлен. Час без смены — failed с возвратом
invalid_fields400 bad_request: полей покупателя у подарка нет
Зарезервированные получатели
В песочнице /recipient отвечает по таблице и никуда не ходит.
gs_sandbox_okнайден и можно выдать
gs_sandbox_missingне разрешается
gs_sandbox_haspremiumнайден, но Premium уже есть
gs_sandbox_nogiftsнайден, выдать нельзя
gs_sandbox_silentисточник не ответил
gs_sandbox_badдля Steam
Любой другой корректный ник считается найденным.
У Telegram-подарка те же ники: gs_sandbox_missing и gs_sandbox_nogifts в котировке и заказе дают 422 recipient_refused с reason not_resolvable и unavailable. Два отличия: gs_sandbox_haspremium подарок примет — Premium ему не помеха; gs_sandbox_silent в котировке даёт recipient: null, а в заказе — 503 gifts_unavailable: без ответа о получателе заказ на подарок не принимается и в бою.
Управление: GET и POST /sandbox
GET/sandboxбаланс, список сценариев и ников
POST/sandbox {"action":"topup","amount":100}долить игровых денег
POST/sandbox {"action":"advance","order":"ord_test_…"}протолкнуть заказ сейчас
Боевым ключом эти методы отвечают 403 sandbox_only. Чтобы воспроизвести 402 insufficient_funds в CI с первой попытки: выставьте остаток меньше цены и создайте обычный заказ — придёт та же ошибка с теми же полями required и balance, что в бою.
Заказ-подарок advance двигает на один этап gift.stage (со сценарием fast — сразу до конца). Остановку needs_recipient он не проходит: это решение за вами, POST /orders/{id}/recipient. В ответе advance — status после толчка, тот же, что в GET /orders/{id}: processing, delivered или failed; у подарка ещё gift.stage. Закрытый заказ — 409 already_closed.
Подробности
Выданные коды каталога выглядят как GS-TEST-XXXX-01 — так их видно на экране, если магазин печатает выдачу покупателю. Звёзды, Premium, Steam и Telegram-подарки выдачи не возвращают ни в песочнице, ни в бою; тестовый подарок проходит те же этапы gift.stage, что боевой.
Как узнать о выдаче
Опросом. Вебхуков мы не шлём — и это решение, а не задел на будущее.
Заказ создаётся мгновенно, а выдача идёт в фоне — обычно секунды, у Telegram-подарков — минута-две. Чем кончилось, узнаёте сами, и способа два.
Один заказ
GET /orders/{id} — коды приходят там же, как только появятся. Годится, пока заказов немного.
Всё, что изменилось
GET /orders?updated_since=… — что поменялось с этого момента, старые изменения первыми. Это и есть замена вебхуку.
Как следить
// Раз в несколько секунд, начиная с того, что уже видели.
let since = "2026-09-15 08:30:00";
const res = await fetch(
BASE + "/orders?updated_since=" + encodeURIComponent(since) + "&per_page=100",
{ headers: { Authorization: "Bearer gs_live_ваш_ключ" } },
);
const { orders } = await res.json();
for (const order of orders) {
// Обрабатывайте по client_ref — он ваш и переживает что угодно.
handle(order);
// Курсор двигаем по ПОСЛЕДНЕМУ обработанному: ответ отсортирован по// updated_at по возрастанию, поэтому пропустить ничего нельзя.
since = order.updated_at;
}
Подробности
Курсор берите из поля updated_at последнего обработанного заказа, а не из своих часов: наше время и ваше не совпадают, и на расхождении в секунду теряется ровно то изменение, ради которого вы опрашиваете. Сдвигайте его только после обработки — тогда обрыв посреди страницы стоит повтора, а не пропажи.
Подробности
Одно и то же изменение может прийти дважды: курсор по времени, а не по номеру. Делайте обработку идемпотентной по client_ref — тот же приём, что защищает вас от двойной покупки.
Подробности
Заказ-подарок меняет updated_at на каждом этапе gift.stage и когда передача останавливается ради нового получателя — курсор это увидит. Остановку с can_change_recipient: true покажите человеку: сама она не пройдёт.
Ошибки
Формат один на все методы.
Формат
{ "error": { "code": "unauthorized", "message": "Ключ не найден или проект отключён" } }
У insufficient_funds рядом с message лежат числа — разбирать текст не нужно и не стоит: формулировки мы правим, поля нет. Так же устроены recipient_daily_limit (recipient, orders_today, spent_today, max_orders, max_amount, retry_after_hours), recipient_refused (recipient с name и photo, у подарка ещё reason, а name и photo у боевого подарка — null) и ошибки Telegram-подарков — их поля описаны в том разделе. У остальных ошибок числа пока только в тексте; будем добавлять по мере надобности.
409already_closedПесочница: advance для заказа, который уже закрыт
404not_foundТовара, подарка или заказа нет — или заказ не ваш
400invalid_fieldsНе заполнено или слишком длинное поле из fields
409out_of_stockОстаток меньше, чем просите
409client_ref_conflictЭтот client_ref уже стоит на заказе другого вида товара или на подарке с другим лотом. Остальное тело не сравнивается: повтор с другим номиналом, количеством, сроком или суммой вернёт прежний заказ
409gift_not_buyableЛот сейчас не купить — код причины в reason
409gift_reservedПо лоту идёт заказ, до его конца — в mine и order, чей
409price_changedЛот дороже вашего max_price — новая цена в price
409bad_stateСменить получателя сейчас нельзя: передача не ждёт нового получателя, заказ закрыт или только что изменился
422not_deliverableЭтот товар через API пока не заказать
422recipient_refusedПолучатель не примет выбранное — причина в message, у подарка ещё и в reason
422term_unavailableЭтот срок Premium сейчас не продаётся
500pricing_errorЦена настроена неверно — напишите нам
500internalСбой на нашей стороне — повторите с тем же client_ref
503upstream_unavailableЦена у Fragment временно недоступна
503no_ratesКурс для пополнения Steam сейчас неизвестен
503gifts_unavailableПодарки временно не продаются — повторите позже
429rate_limitedСлишком часто — см. заголовок Retry-After
429recipient_daily_limitСуточный лимит на одного получателя — числа в полях ошибки
400bad_requestНекорректный параметр запроса
Деньги на счету
Пополнения по API не будет — это решение, а не очередь. Пополнение живёт в кабинете: USDT в сети TON или TRC20, GRAM, зачисление автоматическое по точной сумме. Планируйте интеграцию из того, что баланс пополняет человек.
Поэтому и о низком балансе мы пишем в бота, а не вебхуком: сообщение приходит туда, где этот человек уже есть — в @gamesend_bot, тот же, через который вы входите. Предупреждаем один раз на подходе к нулю и повторяем только после того, как баланс восстановился. О заказах узнаёте опросом — см. «Как узнать о выдаче».