Паки
Каталог паков, прямая выдача пака игроку и история выдач.
Читайте каталог паков, выдавайте пак напрямую игроку (бесплатно — коины не списываются) и смотрите историю выдач по всем источникам (выдачи через API, погашения клейм-кодов, покупки в магазине, ручные админ-выдачи).
Ссылки на пак
У пака два названия: name — слаг каталога (starter, стабильный, под него пишут скрипты) — и
display_name — человекочитаемый заголовок (Starter Pack, его видят игроки).
Где бы пак ни встречался — в самом каталоге, в результате выдачи, в строке леджера, в клейм-коде — это
одна и та же форма { id, name, display_name }. Для пака, который всё ещё есть в каталоге, оба
названия заполнены. id — главный: используйте его в GET /v1/packs/{id}, чтобы получить полную запись.
Единственное исключение — строка истории выдач, чей пак уже удалён: у неё оба названия равны null.
Сама выдача в леджере остаётся, исчезает только запись каталога, на которую она ссылалась.
GET /v1/packs
Список каталога паков.
Скоуп: packs:read.
| Параметр | Тип | Смысл |
|---|---|---|
enabled | true | false | Только включённые / выключенные паки. |
category_id | integer | Только паки этой категории. |
search | string | Поиск по имени пака. |
cursor | string | Непрозрачный курсор из предыдущего next_cursor. |
limit | integer (1–100) | Размер страницы. По умолчанию 50. |
{
"data": [
{
"id": 5,
"name": "starter",
"display_name": "Starter Pack",
"description": "Помощь новичкам.",
"category": { "id": 2, "name": "Bundles" },
"price": 100,
"enabled": true,
"shop_enabled": true,
"buy_limit": { "count": 0, "window_minutes": 0 },
"image_url": "/api/packs/5/image?raw=1",
"created_at": "2026-01-01 12:00:00",
"updated_at": "2026-01-02 09:30:00"
}
],
"has_more": false,
"next_cursor": null,
"total": 1
}buy_limit.count равный 0 означает «без ограничений». price — стоимость в коинах при покупке
через магазин; прямая выдача ниже её игнорирует.
GET /v1/packs/{id}
Один пак по числовому id. 404 not_found, если его нет.
Скоуп: packs:read.
POST /v1/packs/{id}/deliver
Выдать пак напрямую игроку и бесплатно — содержимое выдаётся без списания коинов. Получателя
задавайте ровно одним из steam_id или discord_id.
Скоуп: packs:deliver.
| Поле | Тип | Смысл |
|---|---|---|
steam_id | string (17 цифр) | Steam64 получателя. Укажите это или discord_id. |
discord_id | string (17–20 цифр) | Discord-id получателя; резолвится в привязанного игрока. Укажите это или steam_id. |
quantity | integer (1–100) | Сколько копий. По умолчанию 1. |
POST /v1/packs/5/deliver
Content-Type: application/json
{ "steam_id": "76561198000000000", "quantity": 1 }{
"pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
"player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"quantity": 1,
"delivered": true,
"status": "completed",
"purchase_id": 42,
"error_message": null
}delivered — общий итог; status — точный результат (completed, partial, failed,
refund_failed, ledger_failed). Выдача попадает в историю ниже с source: "api".
Выдача НЕ идемпотентна — шлите Idempotency-Key
Каждый вызов заново спавнит предметы пака, поэтому повтор при сбое может выдать дважды. Шлите
Idempotency-Key, чтобы повтор воспроизвёл первый результат вместо новой
выдачи.
Получатель должен быть реальным игроком
discord_id, не привязанный ни к одному игроку, вернёт 404 not_found. Корректный, но ни разу не
заходивший steam_id принимается, но предметы могут не дойти (выдача покажет status: "failed" /
"partial").
GET /v1/packs/deliveries
История выдач (журнал покупок паков) — каждый пак, дошедший до игрока, независимо от источника:
прямые выдачи через API (api), погашения клейм-кодов (claim), ручные админ-выдачи (admin),
покупки в магазине и другое.
Скоуп: packs:read.
| Параметр | Тип | Смысл |
|---|---|---|
steam_id | string (цифры) | Только выдачи этому игроку. Укажите это или discord_id. |
discord_id | string (цифры) | Резолвится в привязанного игрока. Непривязанный id → пустая страница. |
pack_id | integer | Только выдачи этого пака. |
source | string | Только этот источник (api, claim, admin, …). |
status | string | Только этот итог (completed, partial, failed, …). |
cursor | string | Непрозрачный курсор. |
limit | integer (1–100) | Размер страницы. По умолчанию 50. |
{
"data": [
{
"id": 7,
"pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
"player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"quantity": 1,
"total_price": 0,
"status": "completed",
"source": "api",
"error_message": null,
"delivered_at": "2026-01-03 18:05:00"
}
],
"has_more": false,
"next_cursor": null,
"total": 1
}player.discord_id — текущая привязка Discord получателя (Discord едет рядом со Steam — null,
когда не привязан), независимо от того, что было записано в момент выдачи.
Ошибки
Стандартный конверт ошибки.
| Код | Статус | Когда |
|---|---|---|
insufficient_scope | 403 | У ключа нет packs:read (чтение) или packs:deliver (выдача). |
premium_required | 403 | План игрового сервера не включает фичу паков/магазина. |
not_found | 404 | Неизвестный id пака; или discord_id-получатель/фильтр не привязан ни к одному игроку (только для выдачи — фильтры возвращают пустую страницу). |
validation_error | 400 | Плохой id, оба/ни одного из steam_id и discord_id, или отказ игрового сервера (его сообщение в error.details.mod_message). |
conflict | 409 | Выдача отклонена: подключённый игровой сервер слишком старый для бесплатной выдачи — он списал бы коины с получателя. Обновите игровой сервер и повторите. |
idempotency_conflict | 409 | Присланный Idempotency-Key уже использовался с другим телом запроса. Переиспользуйте ключ только для повтора той же самой выдачи. |
server_unavailable | 503 | Игровой сервер офлайн или недоступен. |
gateway_timeout | 504 | Сервер не ответил вовремя. |