Platitun · API для продавцов

API для продавцов

Описание путей по разделам

У каждого пути своя страница: поля запроса и ответа, поводы отказа с объяснением, что делать, и пример.

Зачем это

Ключ доступа нужен, чтобы вести товар программой: заводить карточки, включать их, пополнять склад автовыдачи и читать статистику. Всё то же самое делается кнопками в кабинете — API нужен тем, у кого карточек сотни.

Первый ключ выдаётся в кабинете продавца, в разделе «Ключи для программ»; дальше ключи умеет выдавать и сама программа — ключом с областью `keys`. Ключ показывается один раз: у нас его нет — в базе лежит только свёртка, и восстановить ключ мы не можем. Потеряли — отзовите и выдайте новый.

Справочник сервисов только читается. Правит его администратор, и это не оговорка: два «Cursor Pro» с разными названиями покупатель не отличит. Нет нужной позиции — есть заявка в кабинете, её разбирает человек.

Как предъявлять ключ

У ключа есть области действий: catalog, listings, stock, stats, orders, payouts, documents, keys, hooks. Отмечайте только нужные — утёкший ключ умеет ровно то, что отмечено. Путь «кто я» доступен любому ключу с хотя бы одной областью.

Правила, общие для всех путей

Отказ приходит кодом в поле `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 }

Автовыдачу можно включить только при непустом складе: обещание мгновенной выдачи без запаса — ложь покупателю, и по рейтингу автовыдачи она стоит дорого.

Сделка

Отметить выдачу подписки

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` умеет выдавать новые ключи. Утёкший ключ с любой из этих областей — это доступ к вашим деньгам, а не к карточкам. Рабочему ключу, который заливает товар, эти области не нужны: не отмечайте их без нужды.

Порог: ИНН, подтверждение, реквизиты

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
}

Проверка подписи у себя

// Проверка подписи у себя (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 на этот адрес. Отдельного входа для агентов нет: два входа означали бы два места, где отзыв ключа может не сработать.

Разговор с сервером 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 } }

Чего здесь пока нет

Продавцовое — ВСЁ, кроме одного: заявить присутствие нельзя (см. раздел о сделке). Осталось подробное описание каждого пути отдельной страницей — с полями запроса и ответа и полным перечнем поводов отказа; сейчас на каждый путь есть строка в перечне и раздел с правилами, но не страница. Порядок выбран сознательно: каждый кусок выкатывается и проверяется отдельно, чтобы при поломке откатывалась часть, а не всё.

Ключ выдаётся в кабинете продавца: Продажи → Ключи для программ.