Platitun · API для продавцов
API для продавцов
Описание путей по разделам
У каждого пути своя страница: поля запроса и ответа, поводы отказа с объяснением, что делать, и пример.
- Справочник и заявки
catalog· 7 - Карточки товара
listings· 12 - Склад автовыдачи
stock· 2 - Сделка
orders· 16 - Статистика и рейтинг
stats· 3 - Выплаты
payouts· 1 - Документы и реквизиты
documents· 6 - Ключи доступа
keys· 3 - Подписка на события
hooks· 9
Зачем это
Ключ доступа нужен, чтобы вести товар программой: заводить карточки, включать их, пополнять склад автовыдачи и читать статистику. Всё то же самое делается кнопками в кабинете — API нужен тем, у кого карточек сотни.
Первый ключ выдаётся в кабинете продавца, в разделе «Ключи для программ»; дальше ключи умеет выдавать и сама программа — ключом с областью `keys`. Ключ показывается один раз: у нас его нет — в базе лежит только свёртка, и восстановить ключ мы не можем. Потеряли — отзовите и выдайте новый.
Справочник сервисов только читается. Правит его администратор, и это не оговорка: два «Cursor Pro» с разными названиями покупатель не отличит. Нет нужной позиции — есть заявка в кабинете, её разбирает человек.
Как предъявлять ключ
- Заголовком `Authorization: Bearer ptk_…` — так принято почти везде.
- Либо заголовком `X-Api-Key: ptk_…`, если ваше средство не умеет первый.
- Оба способа равнозначны. Ключ в адресе запроса НЕ принимается: адреса попадают в журналы посредников.
У ключа есть области действий: catalog, listings, stock, stats, orders, payouts, documents, keys, hooks. Отмечайте только нужные — утёкший ключ умеет ровно то, что отмечено. Путь «кто я» доступен любому ключу с хотя бы одной областью.
Правила, общие для всех путей
- Частота: 60 запросов в минуту на ключ, короткий всплеск до 120. Сверх — ответ 429 с заголовком `Retry-After`.
- Изменяющий запрос (POST, PATCH, DELETE) обязан нести заголовок `Idempotency-Key`. Повтор с тем же ключом и тем же телом вернёт прежний ответ, а не создаст второе. Тот же ключ с другим телом — отказ, а не молчаливая подмена.
- Списки отдаются курсором: в ответе есть `nextCursor`, его надо передать в следующем запросе. Страница по умолчанию 50, предел 200. Номеров страниц нет намеренно: пока вы листаете, список меняется.
- Деньги — целые копейки (`priceMinor`). Дробных рублей не бывает нигде.
- Время — строки UTC. Местного времени в ответах нет.
- Карточка снаружи опознаётся публичным НОМЕРОМ — тем же, что в её адресе на витрине. Внутренние ключи наружу не отдаются.
Отказ приходит кодом в поле `error`: no_key, bad_key, revoked, not_seller, seller_blocked, scope, rate, idempotency_key_required, idempotency_mismatch, not_found, bad_request. Отказ по правилам карточки приходит как `{"error":"rejected","refusal":{…}}` — внутри назван конкретный повод.
Пути
Все пути, какие есть
GET /api/v1/me
GET /api/v1/catalog/services?q=&category=&limit=&cursor=
GET /api/v1/catalog/services/{slug}
GET /api/v1/catalog/usual-price?service=&productType=
GET /api/v1/catalog/demand?service=&productType=&top=
GET /api/v1/catalog/requests?limit=
POST /api/v1/catalog/requests
GET /api/v1/listings?active=&service=&limit=&cursor=
POST /api/v1/listings
GET /api/v1/listings/{number}
PATCH /api/v1/listings/{number}
DELETE /api/v1/listings/{number}
POST /api/v1/listings/{number}/state
POST /api/v1/listings/{number}/copy
POST /api/v1/listings/{number}/test
PUT /api/v1/listings/{number}/image
DELETE /api/v1/listings/{number}/image
GET /api/v1/listings/{number}/stock
POST /api/v1/listings/{number}/stock
GET /api/v1/orders?status=&needsAction=&updatedSince=&limit=&cursor=
GET /api/v1/orders/{id}
POST /api/v1/orders/{id}/confirm
POST /api/v1/orders/{id}/decline
POST /api/v1/orders/{id}/deliver
POST /api/v1/orders/{id}/messages
GET /api/v1/orders/{id}/handover
POST /api/v1/orders/{id}/handover
POST /api/v1/orders/{id}/handover/reveal
POST /api/v1/orders/{id}/dispute/reply
GET /api/v1/payouts?limit=&cursor=
GET /api/v1/stats?period=today|week|month|all&sort=revenue
GET /api/v1/clients?limit=&skip=
GET /api/v1/rating
GET /api/v1/questions?onlyUnanswered=&limit=
POST /api/v1/questions/{id}/answer
GET /api/v1/reviews?limit=
POST /api/v1/reviews/{id}/reply
GET /api/v1/complaints?limit=
POST /api/v1/complaints
GET /api/v1/work-mode
PUT /api/v1/work-mode
GET /api/v1/papers
PUT /api/v1/papers/nickname
POST /api/v1/papers/offer
PUT /api/v1/papers/status
POST /api/v1/papers/registry-confirm
PUT /api/v1/papers/payout
GET /api/v1/keys
POST /api/v1/keys
DELETE /api/v1/keys/{id}
GET /api/v1/hooks
PUT /api/v1/hooks
DELETE /api/v1/hooks
POST /api/v1/hooks/secret
POST /api/v1/hooks/resume
POST /api/v1/hooks/test
GET /api/v1/hooks/deliveries?limit=&cursor=
GET /api/v1/hooks/probe?limit=
POST /api/v1/hooks/probeЧто чему подходит, справочник говорит сам: в ответе по сервису есть `deliveryMethods` (какие способы выдачи допустимы каждому виду товара), `slaChoices` (допустимые сроки выдачи в минутах) и `guaranteeChoices` (допустимые сроки гарантии в днях). Угадывать не нужно — эти же значения проверяет площадка.
Правка карточки присылается ЦЕЛИКОМ: у карточки правила связанные — способ выдачи зависит от вида товара, объём и курс от типа «токены», — и одно поле в отрыве от остальных проверить не против чего. Сервис и план правкой не меняются: это была бы другая карточка.
Удаление возможно, только пока по карточке не было ни одного заказа: заказ обязан остаться самодостаточным для спора через месяц. Карточка с историей выключается, и ответ говорит об этом прямо — `result` будет `hidden`, а не `deleted`.
Секреты склада наружу не отдаются никогда — ни вам, ни по вашему ключу. Они лежат зашифрованными и открываются только покупателю в момент выдачи. По складу видно число свободных и выданных единиц.
Примеры
Завести карточку
POST /api/v1/listings
Authorization: Bearer ptk_…
Idempotency-Key: my-upload-2026-08-21-0001
Content-Type: application/json
{
"service": "cursor",
"tariff": "pro",
"productType": "subscription",
"deliveryMethod": "no_login_topup",
"priceRub": 2500,
"slaMinutes": 30,
"guaranteeDays": 14,
"stock": 3,
"description": "Оплата на ваш аккаунт, пароль не передаётся.",
"confirmRequired": true
}
→ 201 { "number": 1042, "isActive": false, "isTest": false }Карточка рождается ВЫКЛЮЧЕННОЙ — и по API тоже. Включение остаётся отдельным действием, потому что именно оно делает вашу цену публичной офертой: по ней вы обязаны продать.
Включить карточку
POST /api/v1/listings/1042/state
Idempotency-Key: turn-on-1042
{ "active": true }
→ 200 { "number": 1042, "isActive": true, "autoIssue": false }Пополнить склад автовыдачи
POST /api/v1/listings/1042/stock
Idempotency-Key: stock-1042-batch-7
{ "items": ["CODE-AAA-111", "CODE-BBB-222"] }
→ 201 { "number": 1042, "added": 2, "free": 5 }Автовыдачу можно включить только при непустом складе: обещание мгновенной выдачи без запаса — ложь покупателю, и по рейтингу автовыдачи она стоит дорого.
Сделка
- Область `orders` даёт всё, что делает продавец по заказу: взять, отказать, отметить выдачу, написать покупателю, положить доступ в сейф, прочитать положенное покупателем, ответить в споре.
- Отбор `needsAction=true` возвращает заказы, где ход за вами: ждут вашего подтверждения, ждут выдачи, открыт спор.
- Отбор `updatedSince` — запасной путь к вебхукам: он есть всегда, и опрос по нему надёжнее, чем ожидание доставки.
- У подписки и аккаунта с подпиской при выдаче ОБЯЗАТЕЛЬНА `nextChargeDay` — дата следующего списания. Это доказательство активации, покупатель проверяет его за секунды.
- Ответ выдачи может содержать `noteRejected`: выдача состоялась, а примечание не прошло фильтр контактов. Само примечание в переписку не попало — перепишите его без контактов и отправьте сообщением.
- Решение спора в API нет: это разбор чужих денег человеком. Ваш ответ в споре есть, и он важен — молчание двенадцать часов закрывает спор возвратом само.
Отметить выдачу подписки
POST /api/v1/orders/cmt2x34ms001boz019p80jgzn/deliver
Idempotency-Key: deliver-1042-once
{
"nextChargeDay": "2026-09-20",
"note": "Подписка активна, продление автоматическое."
}
→ 200 { "order": "cmt2…", "status": "delivered", "noteRejected": null }🔴 Сейф: список сейфов заказа сам секретов НЕ содержит — только «свой или чужой» и «открывали ли». Прочитать чужой сейф можно ровно ОДИН раз, отдельным запросом; повтор честно ответит «уже открыт». Свой сейф прочитать нельзя вовсе: содержимое площадка не читает.
🔴 Кто купил, в ответах НЕ называется — ни почтой, ни ником. Площадка не показывает это продавцу и в кабинете: раздел «Покупатели» работает без личных данных. API не является обходным путём к тому, что закрыто в интерфейсе.
🔴 Единственное, чего API не умеет и не будет: заявить, что вы НА МЕСТЕ. Присутствие определяет площадка сама, а ответом на «Позвать продавца» считается факт вашего входа в кабинет. Иначе «на месте» перестало бы что-либо значить: покупатель выбирал бы по обещанию программы, а не по человеку за компьютером. Режим работы — вместимость, «не беспокоить», тихие часы — задавать через API можно.
Документы, реквизиты и ключи
🔴 Прочтите это прежде, чем отмечать области `documents` и `keys`. Ключ с областью `documents` умеет переписать счёт, на который вам платят, и принять оферту от вашего имени. Ключ с областью `keys` умеет выдавать новые ключи. Утёкший ключ с любой из этих областей — это доступ к вашим деньгам, а не к карточкам. Рабочему ключу, который заливает товар, эти области не нужны: не отмечайте их без нужды.
- Проверки — ТЕ ЖЕ, что в кабинете: контрольная сумма ИНН, контрольная сумма счёта по БИК, сверка наименования по реестру ФНС. Ни одна не ослаблена ради удобства программы.
- Наименование организации от запроса НЕ принимается вовсе: площадка достаёт его из реестра по ИНН, а вы подтверждаете, что это вы. Иначе защита от опечатки в цифре ИНН и от чужого номера не работала бы.
- Сверка не сошлась — ответ 409 с поводом внутри `refusal`. У РАБОТАЮЩЕГО продавца при этом прежние данные не трогаются: `keptPrevious` будет `true`, торговля продолжается, а заявка уходит администратору.
- После удачной сверки смотрите `needsConfirm`: пока наименование не подтверждено (`POST papers/registry-confirm`), порог не закрыт и товар на витрину не выйдет.
- Закрытый или приостановленный администратором продавец НЕ снимает запрет сам — ни кнопкой, ни программой. Реестр подтверждает существование организации, а не право торговать у нас.
- Быстрые платежи по телефону доступны только самозанятому: у ООО и ИП выплата идёт на расчётный счёт. У ООО обязателен КПП.
- Номер счёта наружу отдаётся только последними четырьмя знаками. Переписать его можно, прочитать целиком — нет: утечка ответа не должна становиться утечкой реквизитов.
- Ключ не может выдать области, которых нет у него самого. Иначе один утёкший ключ с областью `keys` становился бы полным доступом, и отзыв не спасал бы. Ключ с любыми областями выдаётся из кабинета.
- Ключ вправе отозвать САМ СЕБЯ — так программа, заметившая утечку, закрывает дверь немедленно. Следующий запрос тем же ключом получит отказ.
- Выданный ключ виден ОДИН раз, в ответе на выдачу. Второго показа нет: в базе только свёртка, и мы сами ключа не знаем.
Порог: ИНН, подтверждение, реквизиты
PUT /api/v1/papers/status
Idempotency-Key: status-2026-08-21
{ "sellerType": "ip", "inn": "323402607218" }
→ 200 { "registryName": "ИП Иванов Иван Иванович", "needsConfirm": true }
POST /api/v1/papers/registry-confirm
Idempotency-Key: confirm-2026-08-21
→ 200 { "confirmed": true, "already": false }
PUT /api/v1/papers/payout
Idempotency-Key: payout-2026-08-21
{
"method": "account",
"account": "40802810400000000160",
"bik": "044525225",
"name": "ИП Иванов Иван Иванович"
}
→ 200 { "method": "account", "accountTail": "0160", "filled": true }Выдать ключ и отозвать его
POST /api/v1/keys
Idempotency-Key: key-for-uploader-1
{ "name": "заливка склада", "scopes": ["listings", "stock"] }
→ 201 { "key": "ptk_…", "prefix": "ptk_abcd1234", "scopes": ["listings","stock"] }
DELETE /api/v1/keys/cmt2x34ms001boz019p80jgzn
Idempotency-Key: revoke-uploader-1
→ 200 { "revoked": true, "id": "cmt2x34ms001boz019p80jgzn" }Подписка на события (вебхуки)
Площадка сама постучится в ваш адрес, когда что-то случится: новый заказ, оплата, спор, сообщение покупателя, кончился склад. Опрос при этом остаётся навсегда: `updatedSince` у заказов работает всегда и надёжнее ожидания доставки. Вебхук — удобство, а не обещание: чужой адрес может не отвечать сутками.
Завести подписку
PUT /api/v1/hooks
Idempotency-Key: hooks-2026-08-21
{
"url": "https://hooks.example.com/platitun",
"events": ["order.new", "order.paid", "order.message", "stock.out"]
}
→ 200 {
"url": "https://hooks.example.com/platitun",
"events": ["order.new","order.paid","order.message","stock.out"],
"secret": "whs_…" // показан ОДИН раз, только при создании подписки
}Как выглядит событие
POST https://hooks.example.com/platitun
x-platitun-event: order.paid
x-platitun-delivery: cmt2x34ms001boz019p80jgzn
x-platitun-timestamp: 1787000000
x-platitun-attempt: 1
x-platitun-signature: sha256=9f2a…
{
"event": "order.paid",
"at": "2026-08-21T14:03:11.512Z",
"data": { "order": "cmt2x34ms001boz019p80jgzn", "slaMinutes": 30 },
"delivery": "cmt2x34ms001boz019p80jgzn",
"attempt": 1
}- События: order.new, order.paid, order.auto_issued, order.cancelled, order.confirmed, order.completed, order.message, dispute.opened, dispute.resolved, stock.low, stock.out, call.new, question.new, review.new. Отмечайте только нужные — каждое событие это запрос в вашу службу.
- Секрет подписи выдаёт площадка и показывает ОДИН раз, при создании подписки. Правка адреса секрет не меняет: у вас проверка подписи уже настроена, и менять секрет за вас — сломать её молча. Замена секрета — отдельный запрос, и прежний секрет умирает сразу.
- Подпись: `sha256=HMAC-SHA256(секрет, «время.тело»)` в заголовке `x-platitun-signature`. Время — в `x-platitun-timestamp`, секунды. Проверяйте И подпись, И свежесть: подпись без проверки времени защищает от подделки, но не от повтора.
- Попыток 6, и все они укладываются в 51 минут: сразу, через 10 секунд, минуту, 5, 15 и 30 минут. Успехом считается любой ответ 2xx; перенаправления мы не идём.
- После шестой неудачи доставка ВСТАЁТ НА ПАУЗУ, и вам приходит сообщение. Снимаете паузу вы сами — запросом `POST /api/v1/hooks/resume` или кнопкой в кабинете: адрес, не отвечавший час, чаще всего не отвечает и через сутки.
- События, случившиеся во время паузы, лежат в журнале со состоянием `skipped_paused`. Они НЕ досылаются после включения: лавина старых событий хуже их отсутствия — программа приняла бы отменённые заказы за новые. Доберите опросом.
- Отвечайте 200 СРАЗУ, а работу делайте потом. Мы ждём ответа десять секунд; долгий ответ мы считаем неудачей и повторим запрос — вы получите то же событие дважды.
- Одно и то же событие может прийти дважды (повтор при разрыве связи). Ключ различения — `delivery` в теле и заголовок с ним же: обработали такой номер — второй раз пропускайте.
- Кто купил, в событиях НЕ называется — ни почтой, ни ником. Площадка не показывает это продавцу и в кабинете.
- Адрес принимается только `https` и только публичный. Внутренние и служебные адреса отвергаются, и проверяется это перед КАЖДОЙ отправкой: имя, безобидное при сохранении, к моменту отправки может указывать куда угодно.
Проверка подписи у себя
// Проверка подписи у себя (Node.js). Тот же расчёт, что у площадки.
import { createHmac, timingSafeEqual } from 'node:crypto';
app.post('/platitun', express.raw({ type: '*/*' }), (req, res) => {
const ts = Number(req.get('x-platitun-timestamp'));
const got = req.get('x-platitun-signature') ?? '';
const want = 'sha256=' + createHmac('sha256', process.env.PLATITUN_HOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8')).digest('hex');
// Сравнение постоянным по времени способом: обычное сравнение строк
// заканчивается на первом различии, и по времени ответа подпись подбирается.
const ok = got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want));
// Свежесть — обязательна: подпись без проверки времени защищает от подделки,
// но не от повтора вчерашнего запроса.
const fresh = Math.abs(Date.now() - ts * 1000) < 300000;
if (!ok || !fresh) return res.sendStatus(401);
res.sendStatus(200); // отвечайте СРАЗУ, работайте потом
});🔴 Проверить свою сторону можно, не дожидаясь живого заказа: `POST /api/v1/hooks/test` отправляет пробное событие тем же путём — та же подпись, тот же журнал, та же лестница попыток. В теле такого события стоит `test: true`, чтобы программа не приняла пробу за настоящий заказ. Журнал доставки — `GET /api/v1/hooks/deliveries`: событие, число попыток, код ответа и текст ошибки.
Сервер MCP: те же действия инструментами
Если товар ведёт не ваша программа, а ИИ-агент, ему не нужно объяснять пути — у площадки есть сервер MCP. Адрес один: `https://platitun.ru/api/mcp`, ключ тот же (`Authorization: Bearer ptk_…`), разговор — JSON-RPC 2.0 одним POST на этот адрес. Отдельного входа для агентов нет: два входа означали бы два места, где отзыв ключа может не сработать.
- Инструмент НЕ содержит действия: он обращается к тому же пути API, что и обычная программа. Тот же заслон, та же область действий, та же проверка повторов, тот же журнал — потому что это буквально тот же путь.
- Набор — отобранный: справочник, карточки, склад, сделка, переписка, статистика, вопросы, отзывы, выплаты. Пятьдесят инструментов сразу агент выбирает хуже, поэтому остальное доступно универсальным инструментом `platitun_api_request`, а перечень путей он читает ресурсом `platitun://api/endpoints`.
- У каждого инструмента в описании прямо сказано, ЧИТАЕТ он или МЕНЯЕТ, и какая область ключа ему нужна. Читающие агент вправе звать сам, меняющие — со спросом у человека.
- У изменяющих инструментов есть довод `idempotencyKey`. Не задан — ключ считается из имени инструмента и доводов: случайный повтор того же вызова не создаст второго заказа. Задайте своё значение, если намеренно повторяете одинаковое действие.
- Отказ площадки приходит как ИТОГ инструмента с пометкой ошибки, а не как поломка разговора: агент должен прочитать причину и поправить вызов, а не решить, что сервер сломан.
- Поток событий сервером не открывается: ответы короткие, а открытый поток — это состояние, которое надо чинить при обрыве. О событиях площадка стучится вебхуками (см. раздел выше).
- Заявить, что продавец на месте, инструментом нельзя — такого инструмента нет, как нет и пути. Присутствие определяет площадка сама.
Разговор с сервером MCP
POST https://platitun.ru/api/mcp
Authorization: Bearer ptk_…
Content-Type: application/json
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2025-06-18",
"clientInfo": { "name": "my-agent", "version": "1.0" } } }
→ { "jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2025-06-18",
"capabilities":{"tools":{},"resources":{}},
"serverInfo":{"name":"platitun","version":"1.0.0"},
"instructions":"Инструменты работают от имени продавца, чей ключ предъявлен…" } }
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "platitun_orders", "arguments": { "needsAction": true } } }
→ { "jsonrpc":"2.0","id":3,"result":{
"content":[{"type":"text","text":"{ \"items\": [ … ] }"}],
"isError": false } }Чего здесь пока нет
Продавцовое — ВСЁ, кроме одного: заявить присутствие нельзя (см. раздел о сделке). Осталось подробное описание каждого пути отдельной страницей — с полями запроса и ответа и полным перечнем поводов отказа; сейчас на каждый путь есть строка в перечне и раздел с правилами, но не страница. Порядок выбран сознательно: каждый кусок выкатывается и проверяется отдельно, чтобы при поломке откатывалась часть, а не всё.
Ключ выдаётся в кабинете продавца: Продажи → Ключи для программ.