Игровая валюта
Начисление и списание игровых денег и золота одному игроку или всем сразу, плюс журнал экономики.
Изменение игровой валюты SCUM — денег и золота, которые игрок носит с собой в игре.
Валюта — это не коины
Деньги и золото — средства игрока внутри игры. Их изменение выполняет команду на игровом сервере.
Коины — валюта магазина Warden, она лежит в базе Warden. Разные эндпоинты, разные скоупы.
delta и set
От mode зависит, как читается amount:
mode | Смысл | Допустимый amount |
|---|---|---|
delta (по умолчанию) | Знаковое движение: плюс начисляет, минус списывает. | Любое целое, кроме нуля |
set | Баланс, который нужно выставить. | Любое целое >= 0 |
0 в режиме delta отклоняется, в режиме set — принимается и обнуляет баланс.
Присылайте Idempotency-Key
delta, применённая дважды, сдвинет баланс дважды, поэтому повтор при сетевом сбое может
задвоить операцию. С Idempotency-Key повтор вернёт первый результат.
(set идемпотентен сам по себе, но заголовок ничего не стоит.)
На каждую операцию — свой ключ: отдельный на выплату, на комиссию, на награду. Повторяйте его только при ретрае этой же операции. Ключ, вшитый в клиент, превратит каждый следующий вызов в повтор первого.
POST /v1/players/{ref}/currency
Изменить баланс одного игрока. {ref} — Steam64 или Discord-идентификатор с префиксом
(discord:123456789012345678).
Скоуп: currency:write.
| Поле | Тип | Смысл |
|---|---|---|
currency | "cash" | "gold" | Какую игровую валюту двигаем. |
amount | целое | Знаковая дельта либо целевой баланс в режиме set. |
mode | "delta" | "set" | По умолчанию "delta". |
reason | строка (1–500) | Обязательно. Попадает в запись журнала. |
POST /v1/players/76561198000000000/currency
Content-Type: application/json
Idempotency-Key: <уникальный-ключ-на-операцию>
{ "currency": "cash", "amount": -500, "reason": "комиссия аукциона" }В ответе — балансы по обе стороны изменения и идентификатор записи журнала:
{
"player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"currency": "cash",
"mode": "delta",
"amount": -500,
"balance_before": { "cash": 12000, "gold": 30 },
"balance": { "cash": 11500, "gold": 30 },
"transaction_id": "8412"
}Возвращаются оба баланса, какой бы из них вы ни меняли.
Игрок должен быть онлайн
Игра применяет изменение через сессию самого игрока, поэтому для офлайн-игрока вернётся
409 conflict. Сверьтесь с GET /v1/players/online или повторите, когда игрок зайдёт.
`balance` равен null, если игрок вышел во время операции
Оба баланса читаются из игры непосредственно до и после изменения. Если игрок отключился между
этими моментами, изменение всё равно применилось, но новый баланс прочитать уже не у кого —
поэтому balance возвращается как null, а не повторяет заведомо устаревший balance_before.
В записи журнала он по той же причине помечен как неизвестный.
Списание не сверяется с балансом
Списание 500 у игрока с нулём оставит его на -500, и запрос всё равно вернёт 200. Игровой
сервер применяет изменение, не глядя на доступные средства.
С коинами наоборот: списание сверх баланса отклоняется с
409 conflict, потому что там проверка и списание идут одной транзакцией в базе.
Чтение баланса его не удерживает
GET /v1/players/{ref} отдаёт money и gold, но чтение и запись — два отдельных вызова. Между
ними игрок может потратить или заработать в игре, а тот же баланс могут двигать банк в Discord,
паки и другие операторы.
Поэтому delta, рассчитанная по прочитанному балансу, всё равно может уйти в минус, а set —
перезаписать более новый баланс. Условного списания игровой сервер не предлагает, так что закрыть
этот разрыв отсюда нельзя. Считайте списания так, чтобы минус был допустим, держите авторитетный
баланс в коинах либо сверяйтесь постфактум по журналу — но сверка должна уметь работать с
неизвестными значениями: balance_before и balance_after оба равны null у массовой записи и
у записи по игроку, который отключился до того, как баланс успели перечитать.
POST /v1/economy/currency
Изменить баланс всем сразу.
Скоуп: currency:write_all.
Принимает те же поля плюс target: "all" (по умолчанию — все игроки в базе игры, и онлайн, и
офлайн) либо "online" (только те, кто сейчас на сервере).
POST /v1/economy/currency
Content-Type: application/json
Idempotency-Key: <уникальный-ключ-на-операцию>
{ "currency": "gold", "amount": 10, "target": "online", "reason": "награда за ивент" }{ "currency": "gold", "mode": "delta", "amount": 10, "target": "online",
"affected_count": null, "transaction_id": "8413" }Массовое изменение передаётся через любого подключённого игрока, поэтому, если онлайн никого нет,
вернётся 409 conflict.
`affected_count` здесь всегда null
Массовые команды игрового сервера не сообщают, скольких игроков они затронули. Массовый эндпоинт по коинам счётчик отдаёт — та операция идёт в базе Warden.
GET /v1/economy/transactions
Журнал экономики: по записи на каждое движение баланса на вашем сервере — и коины, и игровая валюта, независимо от причины. Сюда попадают правки через этот API, действия из панели, депозиты банка в Discord, покупки паков, награды за убийства и списания за подписку.
Скоуп: economy:read.
| Параметр | Смысл |
|---|---|
steam_id | Только записи этого игрока. |
asset | cash, gold или coins. |
source | Откуда движение: api, panel, discord_bank, discord_cmd, pack, claim, task, leaderboard, system. |
scope | player, all или online. |
start_date / end_date | Границы окна (YYYY-MM-DD HH:MM:SS). |
limit, cursor | Размер страницы (1–100, по умолчанию 50) и непрозрачный курсор из next_cursor. |
{
"data": [
{
"id": "8412",
"at": "2026-08-04 10:11:12",
"player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"actor": { "steam_id": "76561198000000009", "discord_id": null },
"asset": "cash",
"mode": "delta",
"amount": -500,
"balance_before": 12000,
"balance_after": 11500,
"scope": "player",
"affected_count": null,
"source": "api",
"reason": "комиссия аукциона",
"pack_name": null
}
],
"has_more": true,
"next_cursor": "eyJvZmZzZXQiOjUwfQ"
}playerравенnullу массовой записи — у изменения по всему серверу нет одного субъекта.actor— кто инициировал движение;null, если оно автоматическое (награда за убийство, списание по расписанию).balance_before/balance_afterравныnullу массовой записи, потому что у общесерверного изменения нет показателей по отдельному игроку, и у записи по игроку, который отключился до того, как баланс успели перечитать. Считайте их неизвестными, а не нулём.affected_count— сколько игроков затронуто;nullу записи по одному игроку иnullу массовой записи по валюте (см. выше).
Журнал не кэшируется, поэтому чтение сразу после записи покажет именно её. Каждый запрос доходит до игрового сервера: опрашивайте с разумным интервалом, а не в плотном цикле.
Ошибки
| Статус | Код | Когда |
|---|---|---|
400 | validation_error | Неверные currency, mode, amount (0 в delta, отрицательный в set) или пустой reason. |
403 | insufficient_scope | У ключа нет currency:write, currency:write_all или economy:read. |
404 | not_found | {ref} не разрешается: некорректный Steam64 либо Discord-идентификатор без привязанного игрока. |
409 | conflict | Игрок офлайн либо некому передать массовое изменение. |
503 | server_unavailable | Игровой сервер не на связи. |