{"openapi":"3.0.3","info":{"title":"KRAB.GG API: Telegram Stars, Premium, GRAM и Steam","version":"1.0.0","description":"Выдавайте **Telegram Stars**, **Premium** и **GRAM** по `@username`, оплачивайте **розыгрыши** в каналах, пополняйте кабинеты **Telegram Ads** по ссылке и **Steam** по логину. Вы пополняете USDT-баланс и отправляете `POST /orders`, мы выдаём товар.\n\n**Цена.** Stars, Premium, GRAM, розыгрыши и Telegram Ads: оптовая цена Fragment плюс комиссия 1%, у `premium_giveaway` она умножается на число подписок. Steam: без комиссии, спишем ровно столько долларов, сколько вы заказали. Точную сумму до заказа покажет `GET /price`.\n\n## Быстрый старт\n\n1. Откройте бота [@apistarsbot](https://t.me/apistarsbot?start=api) и нажмите «Тестовый ключ сейчас»: ключ `sk_test_…` придёт сразу. Боевой `sk_live_…` выдаём по кнопке «Боевой доступ» после проверки заявки.\n2. Отправьте заказ:\n\n```bash\ncurl https://api.krab.gg/v1/orders \\\n  --request POST \\\n  --header 'Authorization: Bearer sk_live_…' \\\n  --header 'Content-Type: application/json' \\\n  --data '{\"type\":\"stars\",\"qty\":100,\"username\":\"@johnsmith\",\"orderId\":\"order-123\"}'\n```\n\n3. Посмотрите на `status` в ответе. `completed`: товар выдан. `processing`: выдача идёт, итог придёт вебхуком, или спросите `GET /orders/{id}`. `failed`: выдать не получилось, причина в `reason`.\n\nС тестовым ключом мы ничего не списываем и не выдаём: ответ придёт с ценой, но без `status`.\n\n## Товары\n\n| `type` | `qty` | `username` |\n| --- | --- | --- |\n| `stars` | звёзды, 50…10 000 000 | `@username` в Telegram |\n| `premium` | месяцы: 3, 6 или 12 | `@username` в Telegram |\n| `gram` | целые TON | `@username` в Telegram |\n| `steam` | доллары, можно с центами: `10.5` | логин Steam без `@` |\n| `stars_giveaway` | всего звёзд на розыгрыш, один из пакетов; победителей в `winners` | `@handle` канала |\n| `premium_giveaway` | срок каждой подписки: 3, 6 или 12; подписок столько, сколько `winners` | `@handle` канала |\n| `ads_recharge` | целые GRAM | ссылка «Add Funds on Fragment» или токен из неё |\n\n`@` в имени можно не писать, число можно прислать строкой. Границы у `gram`, `ads_recharge` и `steam` меняются: за пределами придёт `min_quantity` или `max_quantity` с полем `min` или `max`.\n\n`orderId` это ваш номер заказа, до 128 символов из `A-Za-z0-9._:-`. Повторите запрос с тем же `orderId`, и мы вернём тот же заказ, не списав деньги второй раз. Если в повторе другой `type`, `qty` или `username`, придёт `409 order_id_conflict`.\n\n## Деньги\n\nПока идёт выдача, сумма заказа лежит в резерве на вашем балансе. Что с ней, показывает `settlement`:\n\n- `charged`: списали, товар выдан;\n- `pending`: деньги в резерве, выдача идёт;\n- `refunded`: вернули на баланс;\n- `fail_hold`: выдача не подтверждена, деньги держим до проверки оператором. Если спишем, придёт `order.completed`; если вернём, придёт `order.updated` с `settlement: refunded`.\n\nПовторяйте неудачную выдачу новым заказом с новым `orderId`, и только когда в заказе `retryable: true`. Это значение приходит, когда деньги уже вернулись на баланс.\n\n## Ошибки\n\nОшибка приходит телом `{\"ok\": false, \"error\": \"код\", \"message\": \"текст\"}`. Ветвитесь по `error`, а `message` пишите в лог.\n\n- `4xx`: исправьте запрос;\n- `429`: подождите столько секунд, сколько в заголовке `Retry-After`. Лимит: 60 в минуту на весь аккаунт, боевой и тестовый ключи тратят общий счётчик;\n- `5xx`: повторите позже.\n\n## Контакты\n\nБазовый адрес: `https://api.krab.gg/v1`. В ответах могут появиться новые поля, незнакомые пропускайте. Выпуская боевой ключ, вы принимаете [условия для API-партнёров](https://krab.gg/ru/api-terms). Вопросы и спорные заказы: [@heIIo_stars](https://t.me/heIIo_stars) или hello@krab.gg, укажите `orderId`.\n\n\n## Идентификатор запроса\n\nВ КАЖДОМ ответе приходит заголовок `X-Request-Id` (например `req_mu88via8_53d8cd`). Сохраняйте его в своих логах: по нему мы находим ровно тот запрос, о котором вы спрашиваете, — со всеми деталями отказа. Без него поиск идёт по времени и адресу и занимает несоизмеримо дольше.\n\n```\nX-Request-Id: req_mu88via8_53d8cd\n```\n","contact":{"name":"Поддержка KRAB.GG","url":"https://t.me/heIIo_stars","email":"hello@krab.gg"}},"servers":[{"url":"https://api.krab.gg/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Заказы","description":"Создать заказ и узнать его статус."},{"name":"Цены и проверка","description":"Цена и проверка получателя до заказа, без списаний."},{"name":"Баланс и пополнение","description":"Баланс и реквизиты пополнения."},{"name":"Вебхуки","description":"Уведомления о завершении заказа на ваш URL."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_… | sk_test_…","description":"Ключ в заголовке `Authorization: Bearer sk_live_…` (боевой) или `sk_test_…` (тестовый). Мы показываем ключ один раз, при выпуске. У аккаунта один боевой и один тестовый ключ: чтобы сменить ключ, отзовите старый в кабинете и выпустите новый. Там же («Ключи» → «IP-доступ») можно разрешить запросы только с перечисленных адресов: до 50 точных IPv4/IPv6, список действует на все ключи аккаунта."}},"responses":{"Error":{"description":"Ошибка, тело по схеме `Error`. Кроме кодов, описанных у метода, на любой запрос могут прийти:\n\n- `401` missing_api_key, invalid_api_key: ключа нет, он неверный или отозван, либо доступ к аккаунту приостановлен или закрыт;\n- `403` ip_not_allowed: запрос пришёл с адреса вне IP-списка аккаунта; в теле ответа поле `seen_ip` — адрес, который увидели мы (именно его и вносите в список: за NAT или балансировщиком он отличается от того, что вы видите у себя);\n- `429` rate_limited: превышен лимит аккаунта (по умолчанию 60 в минуту) или с одного IP пришло больше 120 запросов в минуту, тогда адрес блокируем на 5 минут. Ждите `Retry-After` секунд, остаток лимита видно в заголовках `X-RateLimit-Limit` и `X-RateLimit-Remaining`;\n- `400` bad_request, `413` payload_too_large: тело не JSON или больше 32 КБ;\n- `404` not_found: такого метода нет (неверный путь или HTTP-метод);\n- `404` partner_not_found: аккаунт закрыли, пока шёл запрос; следующие запросы получат 401;\n- `500`, `503`: сбой у нас или API остановлен (service_unavailable), повторите позже.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"missing_api_key","message":"Missing API key. Send header 'Authorization: Bearer sk_...'."}},"examples":{"missing_api_key":{"summary":"missing_api_key","value":{"ok":false,"error":"missing_api_key","message":"Missing API key. Send header 'Authorization: Bearer sk_...'."}},"rate_limited":{"summary":"rate_limited","value":{"ok":false,"error":"rate_limited","message":"Too many requests. Retry after the indicated delay (see retryAfter).","retryAfter":30}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}}},"schemas":{"Error":{"type":"object","required":["ok","error","message"],"example":{"ok":false,"error":"min_quantity","message":"Quantity is below the allowed minimum (see min).","min":50},"properties":{"ok":{"type":"boolean","example":false},"error":{"type":"string","description":"Код ошибки. 400, исправьте запрос: unsupported_type, invalid_quantity, min_quantity, max_quantity, invalid_premium_duration, username_required, invalid_username, order_id_required, invalid_order_id, invalid_dry_run, hold_settled, hold_amount_mismatch, url_required, invalid_url, unsupported_protocol, private_address_not_allowed, invalid_stars_package, winners_required, invalid_winners, winners_not_supported, recipient_not_channel, recipient_not_found, invalid_ads_account, ads_account_not_found, fragment_rejected, bad_request. 401: missing_api_key, invalid_api_key. 402: insufficient_funds. 403: ip_not_allowed, test_key_readonly, sandbox_no_deposit. 404: not_found, partner_not_found. 409: in_progress, order_id_conflict, hold_requires_resolution. 413: payload_too_large. 429: rate_limited. 502: fetch_failed. 503, повторите позже: service_unavailable, category_disabled, plan_unavailable, pricing_unavailable, rate_unavailable, order_create_failed, hold_failed. 500, повторите: internal_error, auth_error, tenant_lookup_failed."},"message":{"type":"string","description":"Фраза на английском для логов. Логику стройте по `error`."},"min":{"type":"number","description":"Нижняя граница `qty`. Приходит с `min_quantity`."},"max":{"type":"number","description":"Верхняя граница `qty`. Приходит с `max_quantity`."},"allowed":{"type":"array","items":{"type":"integer"},"description":"Допустимые сроки Premium. Приходит с `invalid_premium_duration`."},"whole":{"type":"boolean","description":"Приходит с `invalid_quantity`, когда у stars, premium или gram прислали дробное `qty`: нужно целое."},"step":{"type":"number","description":"Приходит с `invalid_quantity`, когда у steam больше двух знаков после запятой: сумма с точностью до цента."},"retryable":{"type":"boolean","description":"Приходит с `in_progress` и `rate_unavailable`: запрос можно повторить."},"balance":{"type":"number","description":"Ваш баланс в долларах. Приходит с `insufficient_funds`."},"required":{"type":"number","description":"Сколько нужно для заказа, в долларах. Приходит с `insufficient_funds`."},"retryAfter":{"type":"integer","description":"Через сколько секунд повторить. Приходит с `rate_limited`."},"type":{"type":"string","description":"Товар, для которого шла проверка. Приходит с ошибками `GET /check` о получателе: invalid_username, invalid_premium_duration, not_found, fetch_failed."}}},"Price":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"type":{"type":"string","example":"stars"},"qty":{"type":"number","example":100},"price":{"type":"number","example":1.515,"description":"Сумма к списанию в долларах USDT, до шести знаков после запятой."}}},"Recipient":{"type":"object","example":{"ok":true,"type":"stars","accepts":true},"properties":{"ok":{"type":"boolean","example":true},"type":{"type":"string","enum":["stars","premium","gram","steam","stars_giveaway","premium_giveaway","ads_recharge"],"example":"stars"},"accepts":{"type":"boolean","example":true,"description":"`true`: товар можно отправлять. `false`: не отправляйте, причина в `reason`."},"reason":{"type":"string","enum":["already_subscribed","recipient_rejects"],"description":"Только при `accepts: false`. `already_subscribed`: у получателя уже есть Premium. `recipient_rejects`: получатель есть, но заказ на него не пройдёт (для Steam: логин нельзя пополнить). Заказ на такого получателя упадёт с той же причиной в `reason`."}}},"Balance":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"balance":{"type":"number","example":42.5,"description":"Доступный баланс в долларах USDT."}}},"OrderRequest":{"type":"object","required":["type","qty","username","orderId"],"properties":{"type":{"type":"string","enum":["stars","premium","gram","steam","stars_giveaway","premium_giveaway","ads_recharge"],"example":"stars","description":"Товар: `stars`, `premium`, `gram`, `steam`, `stars_giveaway`, `premium_giveaway` или `ads_recharge`."},"qty":{"type":"number","example":100,"description":"Сколько: звёзды, месяцы Premium (3, 6, 12), целые TON для gram или доллары для steam. У `stars_giveaway` — ВСЕГО звёзд на розыгрыш (один из пакетов Fragment, см. `allowed` в ошибке): площадка сама делит их между победителями. У `premium_giveaway` — срок КАЖДОЙ подписки (3, 6, 12), а подписок столько, сколько победителей. У `ads_recharge` — целые GRAM. См. границы ниже."},"winners":{"type":"integer","minimum":1,"description":"Только для розыгрышей: сколько человек делят приз. У `stars_giveaway` потолок зависит от объёма (Fragment даёт не меньше 100 звёзд на человека, то есть не больше чем `qty / 100` победителей), у `premium_giveaway` — до 24000 подписок. У остальных товаров поле не принимается.","example":5},"username":{"type":"string","example":"@johnsmith","description":"Получатель: `@username` в Telegram, для steam логин аккаунта Steam. У розыгрышей — `@handle` КАНАЛА (в бота или личный аккаунт розыгрыш не проводится). У `ads_recharge` — токен из ссылки Telegram Ads «Add Funds on Fragment» (`fragment.com/ads/pay?account=…`); можно прислать ссылку целиком. Это не @имя и не числовой id."},"orderId":{"type":"string","example":"order-123","description":"Ваш номер заказа, до 128 символов из `A-Za-z0-9._:-`. Повтор того же orderId вернёт существующий заказ и не спишет деньги второй раз; с другим `type`, `qty` или `username` придёт 409 `order_id_conflict`."},"dryRun":{"type":"boolean","example":false,"description":"`true`: песочница. Мы только считаем цену: не списываем, не выдаём и не создаём заказ. Тестовый ключ всегда работает так. В ответе будут цена, `dryRun: true` и `mode`, но не будет `status` и `settlement`; `GET /orders/{id}` по такому orderId ответит 404, вебхуков не будет. Кроме `true`/`false` понимаем `1`/`0` и строки `\"true\"`/`\"false\"`; другое значение → 400 `invalid_dry_run`."}}},"Order":{"type":"object","example":{"ok":true,"orderId":"order-123","status":"completed","type":"stars","qty":100,"username":"@johnsmith","price":1.515,"settlement":"charged"},"properties":{"ok":{"type":"boolean","example":true},"orderId":{"type":"string","example":"order-123"},"status":{"type":"string","enum":["completed","processing","failed","refunded","cancelled"],"example":"completed","description":"`completed`: выдано. `processing`: выдача идёт. `failed`: выдать не удалось. `cancelled`, `refunded`: заказ закрыт без выдачи. В ответе песочницы поля нет."},"reason":{"type":"string","nullable":true,"enum":["already_subscribed","partial_delivery","recipient_not_found","recipient_rejects","unsupported_type","under_review","temporary"],"description":"Почему выдача не удалась. Приходит вместе с `retryable`, в том числе когда заказ потом закрыли как `cancelled` или `refunded`.\n\n- `recipient_not_found`: получателя нет;\n- `recipient_rejects`: получатель не может принять этот товар;\n- `already_subscribed`: у получателя уже есть Premium;\n- `unsupported_type`: товар сейчас не выдаётся;\n- `temporary`: сбой до отправки; повторяйте новым orderId, когда `retryable: true`;\n- `under_review`: отправка могла пройти, мы проверяем, новый заказ не создавайте;\n- `partial_delivery`: выдали часть, остаток довыдадим вручную, не повторяйте.\n\nПри `recipient_not_found`, `recipient_rejects`, `already_subscribed` и `unsupported_type` деньги возвращаются сами, обычно в течение минуты. Что с деньгами сейчас, показывает `settlement`."},"retryable":{"type":"boolean","description":"`true`: деньги по заказу вернулись на баланс, повторите новым orderId. `false`: повтор не поможет, или деньги ещё в резерве и выдача может завершиться."},"type":{"type":"string","example":"stars"},"qty":{"type":"number","example":100,"description":"Для steam в долларах, округлено до цента."},"username":{"type":"string","example":"@johnsmith","description":"В том виде, в каком вы прислали его в заказе."},"price":{"type":"number","example":1.515,"description":"Цена в долларах USDT. Только в ответе `POST /orders`."},"idempotent":{"type":"boolean","description":"`true`: такой orderId уже был, мы вернули существующий заказ и ничего не списали."},"settlement":{"type":"string","enum":["charged","refunded","fail_hold","pending"],"description":"Что с деньгами: `charged`, `pending`, `refunded` или `fail_hold` (см. «Деньги» во введении). В песочнице поля нет."},"mode":{"type":"string","enum":["live","test"],"description":"Только в песочнице: `test` для тестового ключа, `live` для боевого с dryRun."},"dryRun":{"type":"boolean","description":"Только в песочнице: `true`."}}}},"headers":{"RequestId":{"description":"Идентификатор запроса. Приходит в каждом ответе, включая ошибки; назовите его в обращении в поддержку — по нему мы найдём именно этот запрос.","schema":{"type":"string","example":"req_mu88via8_53d8cd"}}}},"paths":{"/price":{"get":{"tags":["Цены и проверка"],"operationId":"getPrice","summary":"Цена","description":"Сколько мы спишем за заказ. Ничего не создаёт и не резервирует; при создании заказа цену считаем заново.","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["stars","premium","gram","steam"]},"description":"Товар.","required":true,"example":"stars"},{"name":"qty","in":"query","required":true,"schema":{"type":"number"},"example":100,"description":"То же, что `qty` в `POST /orders`."}],"responses":{"200":{"description":"Цена","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Price"}],"example":{"ok":true,"type":"stars","qty":100,"price":1.515}},"examples":{"stars":{"summary":"100 Stars","value":{"ok":true,"type":"stars","qty":100,"price":1.515}},"premium":{"summary":"Premium на 3 месяца","value":{"ok":true,"type":"premium","qty":3,"price":12.1099}},"steam":{"summary":"Steam на $10.50, без комиссии","value":{"ok":true,"type":"steam","qty":10.5,"price":10.5}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"unsupported_type, invalid_quantity, min_quantity, max_quantity, invalid_premium_duration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"min_quantity","message":"Quantity is below the allowed minimum (see min).","min":50}},"examples":{"min_quantity":{"summary":"min_quantity","value":{"ok":false,"error":"min_quantity","message":"Quantity is below the allowed minimum (see min).","min":50}},"invalid_premium_duration":{"summary":"invalid_premium_duration","value":{"ok":false,"error":"invalid_premium_duration","message":"Premium term must be 3, 6, or 12 months (see allowed).","allowed":[3,6,12]}},"invalid_quantity":{"summary":"invalid_quantity","value":{"ok":false,"error":"invalid_quantity","message":"Invalid quantity. Stars/premium/gram take a whole number (see whole); steam takes dollars with at most 2 decimals (see step).","whole":true}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"503":{"description":"Повторите позже: category_disabled (продажи товара на паузе), plan_unavailable (нет Premium на этот срок), pricing_unavailable, rate_unavailable (нет свежего курса: Steam или TON для GRAM).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"rate_unavailable","message":"The exchange rate is temporarily unavailable. Please retry.","retryable":true}},"examples":{"rate_unavailable":{"summary":"rate_unavailable","value":{"ok":false,"error":"rate_unavailable","message":"The exchange rate is temporarily unavailable. Please retry.","retryable":true}},"category_disabled":{"summary":"category_disabled","value":{"ok":false,"error":"category_disabled","message":"Sales for this product type are temporarily paused. Please retry later."}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/check":{"get":{"tags":["Цены и проверка"],"operationId":"checkRecipient","summary":"Проверка получателя","description":"Проверяет получателя до заказа, чтобы опечатка не заморозила деньги. Stars, Premium и GRAM проверяет Fragment тем же поиском, что и выдача, Steam проверяет платёжный шлюз. Ответ совпадает с тем, чем кончится заказ на этого получателя.\n\n`accepts: false` приходит с `reason`.","parameters":[{"name":"username","in":"query","required":true,"schema":{"type":"string"},"example":"@johnsmith","description":"`@username` в Telegram или логин Steam."},{"name":"type","in":"query","schema":{"type":"string","enum":["stars","premium","gram","steam","stars_giveaway","premium_giveaway","ads_recharge"],"default":"stars"},"description":"Товар: `stars` (по умолчанию), `premium`, `gram` или `steam`.","required":false},{"name":"qty","in":"query","required":false,"schema":{"type":"integer"},"description":"Только для premium: срок 3, 6 или 12, по умолчанию 3."}],"responses":{"200":{"description":"Проверка выполнена","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Recipient"}],"example":{"ok":true,"type":"stars","accepts":true}},"examples":{"accepts":{"summary":"Можно отправлять","value":{"ok":true,"type":"stars","accepts":true}},"already_subscribed":{"summary":"Premium: у получателя уже есть Premium","value":{"ok":true,"type":"premium","accepts":false,"reason":"already_subscribed"}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"username_required, invalid_username, unsupported_type, invalid_premium_duration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"invalid_username","message":"The recipient username is invalid. Use an @handle of 5-32 characters (letters, digits, _; not starting or ending with _) for stars/premium/gram, or a Steam account login without @ (letters, digits, _ and -) for steam.","type":"stars"}},"examples":{"invalid_username":{"summary":"invalid_username","value":{"ok":false,"error":"invalid_username","message":"The recipient username is invalid. Use an @handle of 5-32 characters (letters, digits, _; not starting or ending with _) for stars/premium/gram, or a Steam account login without @ (letters, digits, _ and -) for steam.","type":"stars"}},"username_required":{"summary":"username_required","value":{"ok":false,"error":"username_required","message":"A recipient username is required."}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"not_found: такого пользователя нет, или это канал либо бот.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"not_found","message":"Not found.","type":"stars"}},"example":{"ok":false,"error":"not_found","message":"Not found.","type":"stars"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"502":{"description":"fetch_failed: Fragment или шлюз Steam не ответили либо ответили непонятно. О получателе это ничего не говорит, повторите.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"fetch_failed","message":"Temporary failure checking the recipient. Please retry.","type":"stars"}},"example":{"ok":false,"error":"fetch_failed","message":"Temporary failure checking the recipient. Please retry.","type":"stars"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/balance":{"get":{"tags":["Баланс и пополнение"],"operationId":"getBalance","summary":"Баланс","description":"Доступный баланс в долларах USDT. История движений есть в кабинете.","responses":{"200":{"description":"Баланс","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Balance"}],"example":{"ok":true,"balance":42.5}},"example":{"ok":true,"balance":42.5}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/deposit":{"get":{"tags":["Баланс и пополнение"],"operationId":"getDeposit","summary":"Реквизиты пополнения","description":"Куда отправить деньги. Мы принимаем только **USDT в сети TON**: переведите от 100 USDT на `address` и **обязательно укажите `memo`** в комментарии. Адрес общий, memo закреплён за вашим аккаунтом. Зачисляем обычно в течение минуты, проверяйте `GET /balance`. Забыли memo: пришлите в поддержку хэш транзакции, зачислим вручную.","responses":{"200":{"description":"Реквизиты","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"asset":{"type":"string","example":"USDT"},"network":{"type":"string","example":"TON"},"address":{"type":"string","description":"Наш кошелёк."},"memo":{"type":"string","description":"Комментарий к переводу. Без него мы не узнаем, чей перевод."},"minRecommendedUsdt":{"type":"number","description":"Минимальная сумма пополнения в USDT."},"note":{"type":"string","description":"Короткая подсказка, всегда на русском."}},"example":{"ok":true,"asset":"USDT","network":"TON","address":"EQ…","memo":"d1a2b3c4d5e6f7081","minRecommendedUsdt":100,"note":"Отправьте от 100 USDT (TON) на address с указанным memo. Без memo средства зачислить нельзя."}},"example":{"ok":true,"asset":"USDT","network":"TON","address":"EQ…","memo":"d1a2b3c4d5e6f7081","minRecommendedUsdt":100,"note":"Отправьте от 100 USDT (TON) на address с указанным memo. Без memo средства зачислить нельзя."}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"sandbox_no_deposit: в песочнице пополнения нет, сначала получите боевой доступ.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"sandbox_no_deposit","message":"Deposits are not available in sandbox. Request live access first."}},"example":{"ok":false,"error":"sandbox_no_deposit","message":"Deposits are not available in sandbox. Request live access first."}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/webhook":{"get":{"tags":["Вебхуки"],"operationId":"getWebhook","summary":"Текущий вебхук","description":"URL вебхука и `secret` для проверки подписи. Тестовому ключу приходит `secret: null`.","responses":{"200":{"description":"Настройки вебхука","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"url":{"type":"string","nullable":true},"secret":{"type":"string","nullable":true}},"example":{"ok":true,"url":"https://shop.example.com/webhooks/krab","secret":"whsec_4f1c…"}},"example":{"ok":true,"url":"https://shop.example.com/webhooks/krab","secret":"whsec_4f1c…"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}},"post":{"tags":["Вебхуки"],"operationId":"setWebhook","summary":"Задать вебхук","description":"Задаёт URL, куда мы отправим POST, когда меняется итог заказа. `url: null` выключает вебхук. В ответе придёт `secret`: он появляется при первой установке URL и не меняется при смене URL. Новый секрет выпускает кнопка «Перевыпустить секрет» в кабинете, старый сразу перестаёт подходить.\n\n**Событие.** JSON с полями `event`, `orderId`, `status`, `type`, `qty`, `username`, `settlement`, `sequence` и `occurredAt` (unix-время в секундах, когда мы зафиксировали это состояние), а если выдача пробовалась и не удалась, ещё `reason` и `retryable`. Событие приходит, когда меняется итог заказа: `order.completed` (выдано), `order.failed` (не выдано или закрыто без выдачи) или `order.updated` (итог прежний, но изменились `settlement`, `reason` или `retryable`, например резерв вернули после `fail_hold`). `sequence` растёт на 1 с каждым событием заказа: пришло событие с номером меньше уже обработанного, пропустите его. Кнопка «Отправить тест» в кабинете шлёт `{\"event\":\"ping\",\"at\":<unix-время>}` с `X-Webhook-Id: test`.\n\n**Подпись.** Мы шлём заголовки `X-Webhook-Id`, `X-Webhook-Timestamp` (unix-время в секундах) и `X-Webhook-Signature: v1,<подпись>`, где подпись = base64(HMAC-SHA256(`id.timestamp.body`, secret)), а `body` это сырое тело запроса. Отклоняйте доставки старше нескольких минут.\n\n```js\nconst expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${id}.${ts}.${rawBody}`).digest('base64'));\nconst got = Buffer.from(signature.replace(/^v1,/, ''));\nconst ok = got.length === expected.length && crypto.timingSafeEqual(expected, got);\n```\n\n**Доставка.** Ответьте 2xx за 10 секунд, тело ответа не больше 64 КБ, иначе попытка считается неудачной. Редиректы не выполняем: ответ 3xx тоже неудача. Повторяем через 1м→5м→30м→2ч, всего 5 попыток, потом присылаем уведомление в кабинет. Одно событие может прийти дважды: `X-Webhook-Id` при повторах тот же, отбрасывайте дубли по нему. Порядок доставки не гарантирован, сверяйте `sequence`. Событие, не доставленное за 5 попыток или пропущенное при выключенном вебхуке, может не прийти (после включения вебхука могут прийти события по заказам, изменившимся за последние сутки): итоговый статус всегда сверяйте через `GET /orders/{id}`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","nullable":true,"example":"https://shop.example.com/webhooks/krab","description":"Публичный https или http URL. `null` выключает вебхук. Если имя хоста указывает во внутреннюю сеть, доставки на него не пройдут."}}},"example":{"url":"https://shop.example.com/webhooks/krab"}}}},"responses":{"200":{"description":"Сохранено","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"url":{"type":"string","nullable":true},"secret":{"type":"string","nullable":true}},"example":{"ok":true,"url":"https://shop.example.com/webhooks/krab","secret":"whsec_4f1c…"}},"example":{"ok":true,"url":"https://shop.example.com/webhooks/krab","secret":"whsec_4f1c…"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"url_required, invalid_url, unsupported_protocol, private_address_not_allowed.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"private_address_not_allowed","message":"The webhook URL must be a public address."}},"examples":{"private_address_not_allowed":{"summary":"private_address_not_allowed","value":{"ok":false,"error":"private_address_not_allowed","message":"The webhook URL must be a public address."}},"unsupported_protocol":{"summary":"unsupported_protocol","value":{"ok":false,"error":"unsupported_protocol","message":"The webhook URL must use http or https."}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"test_key_readonly: тестовый ключ настройки не меняет, нужен боевой.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"test_key_readonly","message":"A test key (sk_test_) cannot change configuration. Use a live key (sk_live_)."}},"example":{"ok":false,"error":"test_key_readonly","message":"A test key (sk_test_) cannot change configuration. Use a live key (sk_live_)."}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/orders":{"post":{"tags":["Заказы"],"operationId":"createOrder","summary":"Создать заказ","description":"Создаёт заказ и сразу отправляет товар. Stars, Premium и GRAM обычно выдаём за несколько секунд, и ответ приходит уже с `status: completed`. Steam обычно отвечает `processing`: итог придёт вебхуком или через `GET /orders/{id}`. Если выдать не получилось сразу, придёт `status: failed` с причиной в `reason`. Если у шлюза Steam кончились деньги и заказ до него не дошёл, заказ ждёт до двух часов, потом завершается `failed` с `reason: temporary`, а деньги возвращаются на баланс. Если отправка могла пройти, решение принимает оператор.\n\nПовтор запроса с тем же `orderId` безопасен: вернём тот же заказ и не спишем деньги второй раз. После `503 order_create_failed` повторите тот же `orderId`: при неудачном создании новый резерв откатывается, деньги не остаются в холде без заказа. Старый резерв без записанных условий возвращает `409 hold_requires_resolution`: обратитесь в поддержку для сверки или освобождения средств. Закрытый резерв по-прежнему возвращает `hold_settled`, резерв другой суммы — `hold_amount_mismatch`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"},"examples":{"stars":{"summary":"100 Stars","value":{"type":"stars","qty":100,"username":"@johnsmith","orderId":"order-123"}},"premium":{"summary":"Premium на 3 месяца","value":{"type":"premium","qty":3,"username":"@johnsmith","orderId":"order-124"}},"gram":{"summary":"GRAM: 5 TON","value":{"type":"gram","qty":5,"username":"@johnsmith","orderId":"order-127"}},"steam":{"summary":"Steam: $10.50 на логин","value":{"type":"steam","qty":10.5,"username":"john_doe","orderId":"order-125"}},"sandbox":{"summary":"Песочница","value":{"type":"stars","qty":100,"username":"@johnsmith","orderId":"order-126","dryRun":true}},"stars_giveaway":{"summary":"Розыгрыш звёзд в канале: 2500 звёзд на 5 победителей (по 500 каждому)","value":{"type":"stars_giveaway","qty":2500,"winners":5,"username":"@mychannel","orderId":"gw-stars-1001"}},"premium_giveaway":{"summary":"Розыгрыш Premium: 10 подписок по 3 месяца","value":{"type":"premium_giveaway","qty":3,"winners":10,"username":"@mychannel","orderId":"gw-prem-1001"}},"ads_recharge":{"summary":"Пополнение рекламного кабинета Telegram Ads на 50 GRAM","value":{"type":"ads_recharge","qty":50,"username":"Qm7Tz2VxK9pLc4Nw8RbF3sYd6HgJ1uEa5WkZ0oXnC2tMv7PqB4yLr9SiD8fGh3Ue","orderId":"ads-1001"}}}}}},"responses":{"200":{"description":"Заказ. В песочнице (тестовый ключ или `dryRun: true`) заказ не создаётся: поля `status` и `settlement` отсутствуют, вместо них приходят `dryRun: true` и `mode`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Order"}],"example":{"ok":true,"orderId":"order-123","status":"completed","type":"stars","qty":100,"username":"@johnsmith","price":1.515,"settlement":"charged"}},"examples":{"stars":{"summary":"Звёзды выданы","value":{"ok":true,"orderId":"order-123","status":"completed","type":"stars","qty":100,"username":"@johnsmith","price":1.515,"settlement":"charged"}},"steam":{"summary":"Steam: выдача идёт","value":{"ok":true,"orderId":"order-125","status":"processing","type":"steam","qty":10.5,"username":"john_doe","price":10.5,"settlement":"pending"}},"sandbox":{"summary":"Песочница","value":{"ok":true,"orderId":"order-126","type":"stars","qty":100,"username":"@johnsmith","price":1.515,"dryRun":true,"mode":"test"}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Исправьте запрос: username_required, invalid_username, order_id_required, invalid_order_id, unsupported_type, invalid_quantity, min_quantity, max_quantity, invalid_premium_duration, invalid_dry_run. hold_settled, hold_amount_mismatch: этот orderId уже занят, возьмите новый.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"order_id_required","message":"An orderId is required."}},"examples":{"order_id_required":{"summary":"order_id_required","value":{"ok":false,"error":"order_id_required","message":"An orderId is required."}},"min_quantity":{"summary":"min_quantity","value":{"ok":false,"error":"min_quantity","message":"Quantity is below the allowed minimum (see min).","min":50}},"hold_settled":{"summary":"hold_settled","value":{"ok":false,"error":"hold_settled","message":"This orderId was already settled (charged or refunded). Use a new orderId."}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"402":{"description":"insufficient_funds: не хватает денег. В ответе `balance` и `required` в долларах.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"insufficient_funds","message":"Insufficient balance for this order (see balance and required).","balance":0.5,"required":1.515}},"example":{"ok":false,"error":"insufficient_funds","message":"Insufficient balance for this order (see balance and required).","balance":0.5,"required":1.515}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"409":{"description":"in_progress: этот orderId обрабатывается параллельным запросом, повторите через секунду, дубля не будет. order_id_conflict: этот orderId уже занят заказом с другим `type`, `qty` или `username`, возьмите новый. hold_requires_resolution: у существующего резерва нет подходящих записанных условий; нужна сверка или освобождение средств через поддержку. Новый заказ и выдача не запускаются.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"order_id_conflict","message":"This orderId already belongs to a different order (type, quantity or recipient). Use a new orderId."}},"examples":{"order_id_conflict":{"summary":"order_id_conflict","value":{"ok":false,"error":"order_id_conflict","message":"This orderId already belongs to a different order (type, quantity or recipient). Use a new orderId."}},"in_progress":{"summary":"in_progress","value":{"ok":false,"error":"in_progress","message":"This orderId is already being processed. Retry shortly.","retryable":true}},"hold_requires_resolution":{"summary":"hold_requires_resolution","value":{"ok":false,"error":"hold_requires_resolution","message":"An existing reservation cannot be matched to this order. Contact support to reconcile or release it."}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"503":{"description":"Повторите позже: category_disabled, plan_unavailable, pricing_unavailable, rate_unavailable, order_create_failed, hold_failed.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"order_create_failed","message":"Could not create the order. Please retry."}},"examples":{"order_create_failed":{"summary":"order_create_failed","value":{"ok":false,"error":"order_create_failed","message":"Could not create the order. Please retry."}},"rate_unavailable":{"summary":"rate_unavailable","value":{"ok":false,"error":"rate_unavailable","message":"The exchange rate is temporarily unavailable. Please retry.","retryable":true}}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}},"/orders/{id}":{"get":{"tags":["Заказы"],"operationId":"getOrder","summary":"Статус заказа","description":"Заказ по вашему `orderId`: `status`, `settlement`, а после неудачной выдачи ещё `reason` и `retryable`. Цены здесь нет, она приходит в ответе `POST /orders`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"order-123","description":"Ваш orderId."}],"responses":{"200":{"description":"Заказ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Order"}],"example":{"ok":true,"orderId":"order-123","status":"completed","type":"stars","qty":100,"username":"@johnsmith","settlement":"charged"}},"example":{"ok":true,"orderId":"order-123","status":"completed","type":"stars","qty":100,"username":"@johnsmith","settlement":"charged"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"invalid_order_id: неверный формат orderId.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"invalid_order_id","message":"The orderId format is invalid. Use up to 128 characters from A-Z, a-z, 0-9, dot, underscore, colon or hyphen."}},"example":{"ok":false,"error":"invalid_order_id","message":"The orderId format is invalid. Use up to 128 characters from A-Z, a-z, 0-9, dot, underscore, colon or hyphen."}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"not_found: такого заказа нет. Заказы из песочницы не сохраняются.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"}],"example":{"ok":false,"error":"not_found","message":"Not found."}},"example":{"ok":false,"error":"not_found","message":"Not found."}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"default":{"$ref":"#/components/responses/Error"}}}}}}