Первый заказ за 15 минут
Platina отдаёт вашему магазину пополнения Steam, Telegram Stars, игровую валюту и гифт-карты по одному API. Вы держите рублёвый баланс, мы выбираем поставщика, выдаём код или зачисляем на аккаунт и присылаем вебхук. Ниже путь от тестового ключа до первого успешного заказа в песочнице.
pk_test_…pk_test_, тестовый баланс, заказы ненастоящие. Бой — ключ pk_live_, настоящие деньги и заказы. Права ключа (в API — scope): read «Чтение», orders «Заказы», topups «Пополнения», payments «Оплаты покупателей». Готовность к бою — 14 проверок интеграции в кабинете; подсказка, а не допуск: боевой ключ можно выпустить сразу. Номер запроса — request_id, он же заголовок X-Request-Id.1. Ключ и песочница
Ключ выпускаете вы сами в кабинете, на странице «Ключи API». Тестовый ключ создаётся при регистрации и показывается один раз на «Ключах API»; не успели сохранить — выпустите новый там же. Адрес API — в строке «Адрес API» на странице «API и тестер» и в готовом curl под новым ключом (в примерах ниже он условный). Тестовый начинается с pk_test_, боевой с pk_live_. Передавайте его в заголовке Authorization: Bearer <ключ>. Ключ показывается один раз, у нас хранится только хеш.
Тестовый ключ видит только поставщика «песочница»: заказы не исполняются по-настоящему, баланс песочницы отдельный и пополняется бесплатно. Проверить ключ:
curl https://api.platina.example/v1/me \
-H "Authorization: Bearer pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{"partner":{"slug":"demo-shop","brand":"Demo Shop","tariff":"start","status":"active"},
"key":{"prefix":"pk_test_xxxx","sandbox":true,"scopes":["read","orders","topups"]}}
| Scope | Что разрешает |
|---|---|
read | каталог, курс, заказы и баланс на чтение. Кода товара в ответе нет: вместо него "code": null и "code_hidden": "scope_orders_required" |
orders | создание заказов с баланса, коды товаров в карточке заказа, операции с картами (в том числе PAN, CVV и коды 3-DS), настройка вебхуков. Ключ, которым вы опрашиваете заказы ради кода, должен иметь это право |
topups | заявки на пополнение |
payments | приём оплаты от ваших покупателей (доп. модуль, раздел 6b) |
Кабинет добавляет read к любому ключу с orders, topups или payments: без чтения ключ не увидел бы статус своего же заказа, платежа или заявки.
Лимит 10 запросов в секунду на ключ, всплеск до 20. К ключу можно привязать список IP-адресов, тогда с других адресов придёт 403.
2. Каталог и цена
Баланс и цены списания — в USDT, уже с вашей долей тарифа, без курса: закупка в $ × (1 + доля). У товаров с фиксированным номиналом (коды, пакеты валюты) поле price_usdt за штуку — ровно столько спишется; у товаров с произвольной суммой (пополнение Steam) поле price_usdt_per_usd за 1 $ номинала (4 знака, сумма заказа округляется вверх до цента). Рядом — цена покупателя с вашими наценками из кабинета и округлением, как в боте и витрине, в рублях: retail_rub (за штуку) или retail_rub_per_usd, и сама наценка markup_pct; price_mode: "fixed" — вместо наценки вы задали товару фиксированную цену в рублях, она и стоит в retail_rub; "market" — цена «По рынку»: на 1 ₽ дешевле самой низкой цены на площадках, в границах вашей наценки. hidden: true — вы скрыли товар из бота и витрины, по API он заказывается как обычно. По умолчанию в ответе только то, что есть у поставщиков; с include_unavailable=true придут и остальные — с available: false и без цен. Узнавать об изменениях сразу — событие catalog.updated.
{"sandbox":true,"tariff":"start",
"fx":{"rate":"82.0000","buy_rate":"82.4100","stale":false,"fetched_at":"2026-10-17T12:00:00+00:00"},
"items":[
{"id":"6f1c…","code":"steam_topup_ru","category":"steam_topup","name":"Пополнение Steam (RU)",
"region":"RU","recipient_type":"steam_login","amount_min_usd":"1.0000","amount_max_usd":"500.0000",
"price_usdt_per_usd":"0.9741","retail_rub_per_usd":"86.75","markup_pct":"8","take_pct":"1.000","suppliers":1,
"available":true,"hidden":false},
{"id":"9a2d…","code":"apple_us_10","category":"voucher","name":"Apple Gift Card $10 (US)",
"region":"US","recipient_type":"none","nominal_usd":"10.0000","price_usdt":"9.99","retail_rub":"889.00","markup_pct":"8","take_pct":"4.000",
"suppliers":1,"available":true,"hidden":false}
]}
recipient_type говорит, что спросить у покупателя: steam_login → логин Steam, username → ник Telegram, uid → игровой ID (иногда с сервером), none → ничего, вы получите код.
Курс USDT/RUB — в GET /v1/rates (Rapira; buy_rate уже включает наш запас 0,5 %). На списание заказов он не влияет: по нему считаются розничные цены в рублях и зачисляются рублёвые пополнения и оплаты покупателей.
Там же курс кошелька Steam — сколько рублей или тенге зачислится на аккаунт за 1 $ amount_usd: "steam_wallet":[{"currency":"RUB","per_usd":"79.85","source":"playwallet","fetched_at":"…","stale":false}]. Покупатель хочет пополнить на 1000 ₽ — передайте amount_usd = 1000 / per_usd, округлив до цента, и покажите «зачислится ≈ 1000 ₽»: точную сумму считает Steam в момент зачисления. Курс сверяется раз в час, stale: true — ему больше двух суток.
3. Первый заказ
Передайте свой идентификатор заказа в client_order_id. Повтор запроса с тем же идентификатором вернёт тот же заказ со статусом 200, а не создаст второй. Так безопасно повторять запрос при обрыве сети.
curl -X POST https://api.platina.example/v1/orders \
-H "Authorization: Bearer pk_test_…" -H "Content-Type: application/json" \
-d '{"client_order_id":"shop-10231","product":"steam_topup_ru","amount_usd":"10","recipient":{"login":"gaben"}}'
HTTP 201
{"id":"5b2f…","client_order_id":"shop-10231","status":"held","sandbox":true,
"product":"steam_topup_ru","amount_usd":"10.00","qty":1,"recipient":{"login":"gaben"},
"price_usdt":"10.10","error":null,
"created_at":"2026-10-17T12:01:05+00:00","updated_at":"…","delivered_at":null}
Для товаров с номиналом вместо amount_usd передайте qty (от 1 до 10 000): {"client_order_id":"shop-10232","product":"apple_us_10","qty":2}. Telegram Premium — только qty: 1 (одна подписка на заказ), иначе сразу 422 recipient_invalid. Продукт можно указать кодом (product) или идентификатором (product_id).
В момент ответа price_usdt уже зарезервирована на вашем балансе. При неудаче резерв снимается автоматически.
4. Статус и код
| Статус | Значение | Деньги |
|---|---|---|
held | принят, сумма зарезервирована, ждёт воркер (секунды) | резерв |
sent / pending | передан поставщику, ждём подтверждение (обычно до минуты, опрос каждые 10 с) | резерв |
| success | выполнен; для кодов в карточке появляется поле code (ключу с правом orders; в списке заказов кодов нет, там "code_hidden": "open_order_card") | списано |
| failed | не выполнен, причина в error | резерв возвращён |
refunded | возврат после success по решению менеджера | возвращено |
{"id":"7c0e…","status":"success","product":"apple_us_10","qty":2,"price_usdt":"19.98",
"code":"SBX-3F9A-11C2\nSBX-8D04-7B1E","delivered_at":"2026-10-17T12:01:09+00:00", …}
qty больше 1 в поле code столько кодов, сколько штук, через перевод строки (\n): разбейте строку и выдайте покупателю каждый. Код гифт-карты есть только в карточке заказа и в вебхуке. В списке GET /v1/orders коды не отдаются, чтобы они не попадали в логи и выгрузки.Список от новых к старым, до 200 за раз. Даты — ISO 8601 с часовым поясом (2026-10-01T00:00:00+03:00), created_to не включается. Если в ответе next_before не null, передайте его в before — получите следующую страницу: {"items":[…],"next_before":"7c0e…"}.
Коды ошибок в error.code: recipient_invalid (логин не найден, деньги возвращены), supplier_unavailable (все поставщики недоступны после переключений и повторов), supplier_rejected, supplier_timeout (поставщик не подтвердил за 10 минут; заказ остаётся в pending, резерв не снимаем, разбирает менеджер), premium_active (у получателя уже есть Telegram Premium — деньги возвращены).
Проверка получателя до оплаты. POST /v1/recipients/check с телом {"product":"stars_100","recipient":{"username":"durov"}} ничего не покупает и не резервирует. Ответ status: ok (найден, в name — имя в Telegram), not_found, premium_active (Premium уже есть — подарить нельзя), invalid (неверный формат), format_ok (товар заранее не проверяется), unknown (проверка не успела — заказ можно создавать, получатель проверится при покупке). Telegram отвечает до 5 секунд — спрашивайте до счёта покупателю.
Подтверждение выдачи. У выполненного заказа Telegram Stars или Premium через собственный канал Platina в карточке и вебхуке есть поле delivery: {"type":"ton_tx","tx_hash":"…","url":"https://tonviewer.com/transaction/…","recipient_name":"…"} — ссылку можно отправить покупателю как подтверждение.
5. Вебхуки
{"url":"https://shop.example/platina/hook"}
→ {"url":"https://shop.example/platina/hook","secret":"whsec_…","events":["order.updated","balance.low","payment.updated","shop_order.updated","catalog.updated","webhook.test"]}
Секрет показывается один раз. Потом POST /v1/webhooks/test отправит пробное событие и вернёт ответ вашего сервера.
Удалить адрес песочницы: DELETE /v1/webhooks тестовым ключом — адрес и секрет стираются, события больше не создаются. Боевой адрес удаляют, выключают и включают в кабинете. Выключенный адрес копит события и отдаёт их после включения, старые первыми; адрес, который 5 суток подряд не отвечает 2xx, приостанавливаем сами — за сутки предупредим уведомлением.
Адрес через API — только для песочницы. PUT /v1/webhooks тестовым ключом pk_test_ задаёт адрес песочницы. Боевой адрес и боевой секрет меняются только в кабинете, в «Вебхуках»: с двухфакторной защитой и кодом подтверждения, как боевой ключ. Боевой ключ получит 403 webhook_live_cabinet_only; читать настройки (GET /v1/webhooks) и слать пробное он может. Адрес — только https://, без логина и пароля в нём.
Смена секрета без простоя. В кабинете — «Выпустить новый секрет» с выбором, сколько ещё действует прежний: сутки, 7 дней или гаснет сразу. В песочнице то же делает {"url":"…","rotate_secret":true}, прежний действует сутки. Всё это время в заголовке две подписи, t=…,v1=<новым>,v1=<прежним>, и previous_secret_until в ответе говорит, до какого момента. Проверяйте подпись так, чтобы подходила любая из v1 — как в примерах ниже. Пока прежний действует, следующий секрет не выпускается (409 webhook_rotation_in_progress): погасите прежний в кабинете, когда сервер уже проверяет новым.
Что приходит
POST /platina/hook
X-Platina-Event: order.updated
User-Agent: Platina-Webhooks/1.0
X-Platina-Delivery: 2a9f…
X-Platina-Attempt: 1
X-Platina-Mode: live
X-Platina-Signature: t=1760702469,v1=8f3c…
{"id":"2a9f…","event":"order.updated","api_version":"2026-10-29","created_at":"2026-10-17T12:01:09+00:00","livemode":true,
"data":{"id":"7c0e…","client_order_id":"shop-10232","status":"success","code":"SBX-3F9A-11C2", …}}
order.updated приходит на переходах в pending, success и failed; data это та же карточка, что в GET /v1/orders/{id}. balance.low приходит не чаще раза в сутки, когда боевой баланс опускается ниже порога (по умолчанию 50 USDT): {"currency":"USDT","balance_usdt":"41.20","threshold_usdt":"50"}.
Проверка подписи
Подпись это HMAC-SHA256 от строки t + "." + тело с вашим секретом; подписей v1 может быть несколько (после смены секрета), подходит любая. Отбрасывайте события старше 5 минут. Отвечайте 2xx за пару секунд; обработку делайте после ответа.
# Python
import hmac, hashlib, time
def verify(secret: str, body: bytes, header: str) -> bool:
pairs = [p.split("=", 1) for p in header.split(",") if "=" in p]
t = next((v for k, v in pairs if k == "t"), None)
if t is None or abs(time.time() - int(t)) > 300:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return any(k == "v1" and hmac.compare_digest(mac, v) for k, v in pairs)
// Node.js
const crypto = require("crypto");
function verify(secret, rawBody, header) {
const pairs = header.split(",").map(s => s.split("="));
const t = (pairs.find(([k]) => k === "t") || [])[1];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const mac = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
return pairs.some(([k, v]) => k === "v1" && v && v.length === mac.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v)));
}
// PHP
function platina_verify(string $secret, string $rawBody, string $header): bool {
$t = null; $sigs = [];
foreach (explode(',', $header) as $part) {
[$k, $v] = array_pad(explode('=', $part, 2), 2, '');
if ($k === 't') $t = $v; elseif ($k === 'v1') $sigs[] = $v;
}
if ($t === null || abs(time() - (int)$t) > 300) return false;
$mac = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
foreach ($sigs as $v) if (hash_equals($mac, $v)) return true;
return false;
}
Сырое тело. Подпись считается от байтов тела как они пришли: в Express — express.raw({type: "application/json"}), во Flask — request.get_data(), в FastAPI — await request.body(), в PHP — file_get_contents('php://input'). Пересобранный JSON отличается пробелами и порядком полей, и подпись не сойдётся.
Тестовый вектор (проверку возраста на время теста отключите): секрет whsec_test_vector_platina, заголовок t=1760702469,v1=8d457a89a3936ccd6b9534e7c7d0ccea05abfe98c0aa1909529ccf63b4f7a4d4, тело
{"id":"00000000-0000-4000-8000-000000000000","event":"webhook.test","api_version":"2026-10-29","created_at":"2026-10-17T12:01:09+00:00","livemode":false,"data":{"partner":"demo-shop","source":"cabinet","sandbox":true,"updated_at":"2026-10-17T12:01:09+00:00"}}
Заголовки: X-Platina-Event — событие, X-Platina-Delivery — номер доставки (одинаков у всех повторов; то же, что поле id в теле — дубли отбрасывайте по id, оно под подписью), X-Platina-Attempt — номер попытки (1, 2, …), User-Agent: Platina-Webhooks/1.0, X-Platina-Mode — sandbox или live (то же, что поле livemode в теле), X-Platina-Signature — подпись. Перенаправления 3xx мы не выполняем — укажите конечный адрес. Порядок событий не гарантирован: повтор старого события может прийти после нового, поэтому сверяйте статус заказа, а не порядок прихода.
Если ваш сервер ответил не 2xx или не ответил за 10 с, мы повторим через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч и ещё пять раз через 12 ч — около 3 суток, затем остановимся (в песочнице — через 1, 5 и 30 мин). К паузам добавляем разброс ±10 %; ответ 429 или 503 с Retry-After в секундах удлиняет паузу до 12 ч; ответ 410 Gone выключает адрес — события копятся, пока вы его не включите в кабинете или пока не дойдёт пробное (тогда включим сами). Номер попытки — в заголовке X-Platina-Attempt, версия формата — поле api_version в теле, у каждого объекта в data есть updated_at. Журнал доставок хранится 90 дней. Повторы идут на адрес, сохранённый на момент попытки: поменяли адрес — недоставленное придёт уже туда. Последние 20 доставок — в GET /v1/webhooks, весь журнал страницами — GET /v1/webhooks/deliveries?status=failed&limit=50 (следующая страница — before=next_before), каждая попытка с ответом вашего сервера — в кабинете («Вебхуки» → «попытки и данные»). Событие может прийти дважды: отбрасывайте дубли по полю id в теле — оно под подписью и совпадает с X-Platina-Delivery.
6. Пополнение баланса
{"method":"usdt","amount_usdt":"500","client_ref":"topup-2026-10-28-1"} // usdt — в USDT 1:1; sbp и invoice — amount_rub
{"method":"invoice","amount_rub":"50000"} // рубли зачислятся в USDT по курсу на момент зачисления
→ {"id":"c41a…","status":"pending","currency":"USDT","amount_rub":"50000.00","amount_usdt":null,
"requisites":{"company":"…","inn":"…","account":"…","payment_code":"PLT-C41A9F02","comment":"Пополнение Platina PLT-C41A9F02"}}
Укажите payment_code в назначении платежа. После поступления денег менеджер подтверждает заявку, баланс растёт одной записью в леджере, вы видите это в GET /v1/balance и GET /v1/ledger. Рублёвая заявка конвертируется в USDT по курсу Rapira с запасом 0,5 % на момент зачисления (курс — в note записи леджера, сумма — в amount_usdt заявки). Для USDT в реквизитах приходит свой адрес TRC-20 под эту заявку: он действует 60 минут, перевод зачисляется 1:1 автоматически, без менеджера.
client_ref защищает от дублей: повтор запроса с тем же значением вернёт ту же заявку (200), а не создаст вторую; тот же client_ref с другой суммой или способом — 409 idempotency_conflict. Пока кабинет на паузе, боевые заявки не принимаются (403).
В песочнице заявку подтверждаете сами: POST /v1/topups/{id}/confirm. Минимальная сумма — 10 USDT или 1 000 ₽.
{"sandbox":false,"currency":"USDT","balance_usdt":"566.91","credit_limit_usdt":"0.00","available_usdt":"566.91","reserve_usdt":"0.00","reserve_orders":0}
Все движения (amount_usdt со знаком): topup, hold (резерв под заказ), release (возврат резерва), refund, payin и fee (оплата покупателя и комиссия, раздел 6b). Баланс всегда равен сумме записей, записи не редактируются.
6a. Виртуальные карты
Карты Visa USD/EUR для оплаты зарубежных сервисов. Выпуск сразу с начальным балансом (initial_amount, целые единицы валюты карты) — так дешевле, чем выпустить пустую и пополнить. Цены в USDT с учётом тарифа — в GET /v1/cards/products. В песочнице карты мок-режима: те же ответы, тестовые реквизиты, деньги у поставщика не тратятся.
{"client_card_id":"buyer-42-1","product":"product_01m3s0p1fcfm99mp6qy3drn5zj","initial_amount":5,"label":"buyer 42"}
→ 201 {"operation":{"id":"…","kind":"issue","status":"success","price_usdt":"9.38"},
"card":{"id":"7","status":"active","currency":"usd","masked_number":"424242******6261","exp_month":6,"exp_year":2029}}
Повтор с тем же client_card_id возвращает ту же карту (200). Статус pending в операции — поставщик ещё выпускает: опрашивайте GET /v1/cards/operations/{id}.
{"pan":"4242…","cvv":"…","exp_month":6,"exp_year":2029,"pin_code":null,"three_ds_password":null}
Platina реквизиты не хранит: ответ приходит от эмитента, показывайте его покупателю и не кэшируйте (Cache-Control: no-store). Коды подтверждения онлайн-оплаты — GET /v1/cards/{id}/3ds-codes, опрашивайте во время оплаты, код живёт до expires_at.
{"client_ref":"buyer-42-top-1","units":10} // сумма в валюте карты, карта должна быть active
Блокировка POST /v1/cards/{id}/block {"confirm":true} — навсегда, остаток на карте не возвращается. Баланс и операции: GET /v1/cards/{id}/balance, GET /v1/cards/{id}/transactions. Списание с вашего баланса — одной записью hold на операцию; при отказе поставщика резерв снимается (release).
6b. Приём платежей (доп. модуль)
Принимайте оплату от своих покупателей через Platina: QR СБП, USDT TRC20, криптовалютой в нескольких сетях или наличными в кассе. Покупатель платит в рублях, вам на баланс зачисляется USDT за вычетом комиссии метода (credited_usdt и курс credit_rate в карточке после оплаты), с этого же баланса оплачиваются заказы. Нужен ключ со scope payments. В песочнице модуль доступен всем, в бою его подключает менеджер; что доступно прямо сейчас, показывает GET /v1/payments/methods.
{"client_payment_id":"order-981","method":"sbp_qr","amount_rub":"1490","customer_ref":"tg:5512"} // sbp_qr | usdt_trc20 | crypto | cash
→ 201 {"id":"…","status":"pending","amount_rub":"1490.00","fee_pct":"3.000","expires_at":"…",
"instructions":{"qr_payload":"https://qr.nspk.ru/…"}}
Что показать покупателю, лежит в instructions:
- sbp_qr —
qr_payload: нарисуйте из него QR или откройте ссылкой на телефоне. Срок по умолчанию 15 минут. - usdt_trc20 —
addressиamount_usdt(например,120.0137). Покупатель переводит ровно эту сумму одним переводом: по ней платёж находится в сети и зачисляется сам после подтверждения. Курс фиксируется на 30 минут, рубли переводятся в USDT по курсу без запаса. Другая сумма не теряется: её привязывает менеджер. - crypto — в запросе нужен
asset:usdt_trc20,usdt_bep20,usdt_solи другие (какие доступны, показываетGET /v1/payments/methodsв полеassets). Сумму можно задать прямо в USDT —amount_usdt(1 актив = 1 USD, от 1 до 3500), тогда покупатель переводит ровно её, аamount_rubв карточке справочный; или в рублях —amount_rub, пересчёт по курсу Rapira без запаса. Пример:{"client_payment_id":"fp-981","method":"crypto","asset":"usdt_trc20","amount_usdt":"10"}. В ответеamount_usdtиinstructions:address,network,tokenиamount. Сколько уже пришло, показываетreceived_usdt. Адрес выдаётся на один платёж, поэтому сумма не обязана быть уникальной: если покупатель недоплатил, он может доплатить на тот же адрес до конца срока. Платёж зачисляется после проверки поступления и его AML-риска; деньги с высоким риском уходят на разбор менеджеру (review). Счёт и курс действуют 60 минут. На баланс встаёт переведённая сумма в USDT минус комиссия метода (0,5 %): счёт 10 USDT →credited_usdt9.95. - Страница оплаты. У платежей
cryptoиusdt_trc20в карточке естьpay_url— готовая страница Platina для покупателя: сеть, адрес, QR, сумма, таймер, доплата при недоплате и статус, который обновляется сам. Можно отдать покупателю ссылку вместо своей страницы. - Кто платит комиссию. За USDT (
cryptoиusdt_trc20) комиссию можно переложить на покупателя: кабинет → «Настройки» → «Приём оплат в USDT». Тогда в карточкеfee_payer: "buyer", покупатель платитamount_due_rub(сумма плюс комиссия), а после оплатыcredited_rubравенamount_rub. По умолчаниюfee_payer: "merchant"иamount_due_rubравенamount_rub. - cash —
payment_code(вида7KQ2-M9XP) и адрес кассы. Покупатель называет код кассиру, кассир вносит принятую сумму, баланс пополняется сразу. Код действует сутки.
Повтор с тем же client_payment_id возвращает тот же платёж (200), с другой суммой, методом или активом — 409. Статусы: pending → paid | expired | canceled | failed; review — деньги пришли после срока или отмены, не полностью или с высоким AML-риском, решает менеджер. expired у crypto и usdt_trc20 не окончательный: перевод в течение 120 минут после срока зачисляется, и платёж переходит в paid — не выдавайте товар повторно и не считайте такой счёт потерянным. На каждую смену статуса приходит вебхук payment.updated с карточкой платежа и вашим customer_ref; зачислено credited_rub = paid_rub − fee_rub.
Если вы продаёте через витрину Platina Apps, на каждую смену состояния покупки приходит shop_order.updated: номер покупки, который видит покупатель (number), статус, оплачено и комиссия (paid_rub, fee_rub), связь с заказом (order_id, client_order_id = shop:<id>) и отметка возврата (refund_rub, refunded_at). Кода товара в этом событии нет — он приходит в order.updated.
catalog.updated — изменился каталог: закупка товара сдвинулась на 0,5 % и больше, товар появился, пропал у поставщиков или вернулся. Проверка — раз в 5 минут, в одном событии до 500 изменений (count, changes). У каждого изменения: код и название товара (product, name), что случилось (change: new, price, available, unavailable), ваша цена до и после (old_price_rub, price_rub, по вашему тарифу и курсу на момент события) и за что она (per: item — за штуку, usd — за $1 пополнения). Событие по умолчанию выключено — включите его в кабинете, раздел «Вебхуки»: так интеграция, которая не знает нового события, не получит его неожиданно.
Отменить ожидающий платёж: POST /v1/payments/{id}/cancel. В песочнице «оплачиваете» сами: POST /v1/payments/{id}/confirm (можно с {"amount_rub":"…"}, чтобы проверить недоплату). В леджере оплата видна двумя записями: payin (+сумма) и fee (−комиссия).
7. Ошибки
Любая ошибка приходит в одном формате: {"code":"…","message":"…","retryable":true|false,"request_id":"req_…"}. Сообщения на русском и для человека, код для программы. У ошибки проверки (422 validation_error) есть ещё fields: [{"field":"qty","message":"слишком большое значение","type":"less_than_equal"}].
request_id есть в заголовке X-Request-Id каждого ответа (можно прислать свой, 8–64 символа: буквы, цифры, ._:-). Пишите его в свои логи: по нему запрос находится в кабинете на странице «Журнал API» (там все запросы вашего ключа за 14 дней, с ответом и временем) и у поддержки.
Лимит: 10 запросов в секунду на ключ, всплеск до 20. В каждом ответе: X-RateLimit-Limit — ёмкость (20), X-RateLimit-Remaining — сколько запросов можно сделать прямо сейчас, X-RateLimit-Reset — через сколько секунд снова доступны все 20, X-RateLimit-Policy: 10;w=1;burst=20. При 429 — Retry-After в секундах.
| HTTP | code | Когда | Что делать |
|---|---|---|---|
| 400 | bad_request | запрос не подходит этому методу | прочитать message и исправить запрос |
| 400 | confirm_required | необратимое действие без confirm=true (например, блокировка карты) | повторить с confirm=true, если действие нужно |
| 400 | webhook_https_required | адрес вебхука не https | указать адрес https:// |
| 400 | webhook_url_blocked | адрес вебхука ведёт во внутреннюю сеть или не резолвится | указать публичный адрес |
| 401 | unauthorized | нет ключа, ключ неверный, отозван или истёк | проверить заголовок Authorization и ключ |
| 402 | insufficient_funds | не хватает денег на балансе под резерв заказа | пополнить баланс |
| 403 | forbidden | у ключа нет нужного права, запрос пришёл не с разрешённого IP или кабинет на паузе | в message сказано, что именно; права и IP ключа — в «Ключах API» |
| 403 | blocked | получатель заблокирован антифродом | проверить получателя; если ошибка — написать в поддержку |
| 403 | spend_limit | заказ упёрся в ваш лимит расходов: на день, на один заказ или на ключ API в день (поле limit — какой именно) | дождаться следующих суток по Москве или поднять лимит (владелец или администратор кабинета) |
| 403 | payments_disabled | приём оплат не подключён | подключить модуль оплат у менеджера |
| 403 | webhook_live_cabinet_only | боевой ключ меняет или удаляет адрес вебхука | боевой адрес задать в кабинете |
| 404 | not_found | нет такого метода или объекта | проверить адрес и id |
| 405 | method_not_allowed | адрес не принимает этот HTTP-метод | смотреть заголовок Allow |
| 409 | idempotency_conflict | тот же client_order_id с другим составом заказа | новый client_order_id или прежний состав |
| 409 | product_unavailable | товара нет в продаже или нет поставщика | обновить каталог |
| 409 | card_not_active | карта заблокирована или ещё не выпущена | проверить статус карты |
| 409 | conflict | платёж уже в другом состоянии | перечитать платёж |
| 409 | webhook_not_configured | пробное событие без адреса вебхука | сначала задать адрес |
| 409 | webhook_rotation_in_progress | новый секрет, пока прежний ещё действует | дождаться конца окна или погасить прежний |
| 422 | validation_error | поле запроса не прошло проверку — какое, сказано в fields | исправить запрос |
| 422 | recipient_invalid | получатель или сумма не проходят проверку | исправить данные получателя или сумму |
| 422 | premium_active | у получателя уже активен Telegram Premium — подарить подписку нельзя | предложить покупателю Stars или подарить Premium после окончания его подписки |
| 422 | invalid_cursor | курсор before не из этого списка | передать next_before из прошлого ответа |
| 422 | topup_limit | уже несколько заявок в USDT ждут оплаты | оплатить или отменить одну из них, затем создать новую |
| 422 | topup_invalid | заявку на пополнение нельзя создать (сумма, способ, курс) | прочитать message и поправить заявку |
| 422 | payment_invalid | платёж нельзя создать (сумма, способ) | прочитать message и поправить платёж |
| 422 | method_unavailable | способ оплаты сейчас недоступен | выбрать другой способ |
| 429 | rate_limited | больше 10 запросов в секунду на ключ (всплеск до 20) | подождать Retry-After секунд и повторить |
| 429 | too_many_bad_keys | с вашего IP подряд шли запросы с неверным ключом — временно отвечаем 429 всем ключам с этого адреса | исправить ключ в настройках сервера и подождать Retry-After секунд |
| 429 | limit_exceeded | превышен лимит партнёра: сумма заказа или оборот за сутки | написать менеджеру |
| 429 | customer_limit | превышен лимит покупок на одного получателя | повторить позже |
| 500 | server_error | сбой на стороне Platina | повторить; если не проходит — прислать request_id в поддержку |
| 503 | fx_stale | курс USDT/RUB устарел — заказы временно не принимаются | повторить через пару минут (retryable) |
| 503 | payment_provider_unavailable | платёжный провайдер не ответил при создании платежа | повторить позже (retryable) |
| 503 | supplier_unavailable | все поставщики товара недоступны | повторить позже (retryable) |
8. Правила песочницы
Песочница детерминирована, сценарий выбирается префиксом получателя. Для товаров без получателя (коды) заказ всегда успешен. Какие сценарии вы уже прошли — на странице кабинета «Готовность к бою».
| Получатель | Результат |
|---|---|
любой, например gaben | success сразу; код вида SBX-XXXX-XXXX для гифт-карт |
fail… | отказ сразу: ответ 422 recipient_invalid, заказ не создаётся, деньги не резервируются |
failasync… | заказ принят (201, pending), затем failed с error.code = recipient_invalid, вебхук order.updated, резерв возвращён — так отказывает настоящий поставщик |
down… | поставщик недоступен: переключение на второго поставщика, потом повторы, в итоге failed с supplier_unavailable |
slow… | pending несколько секунд (до следующего опроса поставщика), затем success; вебхук на каждом переходе |
Цены в песочнице близки к боевым, но не равны им. Баланс песочницы не связан с боевым.
9. Переход в бой
Когда тестовые заказы проходят до success, а вебхук проверяет подпись, сверьтесь с 14 проверками на странице «Готовность к бою» в кабинете. Боевой API открыт сразу, запрашивать его у менеджера не нужно: боевой ключ pk_live_… выпускает любой сотрудник с правом на ключи (владелец, администратор, разработчик), у которого включена двухфакторная защита. Владельцу 2FA нужна в любом случае: это одна из 14 проверок. Для него адрес вебхука обязан быть https. Остальное не меняется: те же эндпоинты, те же коды ошибок. Мы рекомендуем ограничить боевой ключ списком IP и поднять balance.low до двух дневных оборотов.
Справочник эндпоинтов
| Метод | Путь | Scope | Назначение |
|---|---|---|---|
| GET | /v1/meta | — | версия API, без ключа |
| GET | /v1/me | read | проверка ключа |
| GET | /v1/catalog | read | продукты и цены; фильтры category, region |
| GET | /v1/products/{id} | read | один продукт |
| GET | /v1/rates | read | курс USDT/RUB |
| POST | /v1/orders | orders | создать заказ (идемпотентно) |
| GET | /v1/orders/{id} | read | карточка заказа; код товара — только ключу с orders |
| GET | /v1/orders | read | список; фильтры status, client_order_id |
| POST | /v1/recipients/check | read | проверка получателя до оплаты: найден, нет такого, Premium уже есть |
| PUT | /v1/webhooks | orders | адрес и секрет |
| GET | /v1/webhooks | read | настройки и 20 последних доставок |
| DELETE | /v1/webhooks | orders | удалить адрес песочницы |
| GET | /v1/webhooks/deliveries | read | журнал доставок; фильтры status, event |
| POST | /v1/webhooks/test | orders | пробное событие |
| POST | /v1/topups | topups | заявка на пополнение |
| GET | /v1/topups, /v1/topups/{id} | read | заявки |
| POST | /v1/topups/{id}/confirm | topups | только песочница |
| GET | /v1/cards/products | read | типы карт и цены выпуска/пополнения |
| POST | /v1/cards | orders | выпуск карты с начальным балансом (идемпотентно) |
| GET | /v1/cards, /v1/cards/{id} | read | карты; ?refresh=true перечитать статус |
| GET | /v1/cards/{id}/credentials | orders | PAN, CVV, срок (не хранятся) |
| GET | /v1/cards/{id}/balance, …/transactions | read | баланс, операции |
| GET | /v1/cards/{id}/3ds-codes | orders | коды 3-DS |
| POST | /v1/cards/{id}/topup | orders | пополнение (идемпотентно) |
| POST | /v1/cards/{id}/block | orders | блокировка навсегда |
| GET | /v1/cards/operations/{id} | read | операция выпуска/пополнения |
| GET | /v1/payments/methods | read | методы приёма, комиссии, сроки |
| POST | /v1/payments | payments | выставить платёж покупателю (идемпотентно) |
| GET | /v1/payments, /v1/payments/{id} | read | платежи; фильтры status, client_payment_id, customer_ref |
| POST | /v1/payments/{id}/cancel | payments | отменить ожидающий |
| POST | /v1/payments/{id}/confirm | payments | только песочница |
| GET | /v1/balance | read | баланс и доступно |
| GET | /v1/ledger | read | движения по счёту |
Полная схема запросов и ответов: /docs (Swagger) и /openapi.json.