WardenCoredocs
APIЭндпоинты

Игровая валюта

Начисление и списание игровых денег и золота одному игроку или всем сразу, плюс журнал экономики.

Изменение игровой валюты 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Только записи этого игрока.
assetcash, gold или coins.
sourceОткуда движение: api, panel, discord_bank, discord_cmd, pack, claim, task, leaderboard, system.
scopeplayer, 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 у массовой записи по валюте (см. выше).

Журнал не кэшируется, поэтому чтение сразу после записи покажет именно её. Каждый запрос доходит до игрового сервера: опрашивайте с разумным интервалом, а не в плотном цикле.

Ошибки

СтатусКодКогда
400validation_errorНеверные currency, mode, amount (0 в delta, отрицательный в set) или пустой reason.
403insufficient_scopeУ ключа нет currency:write, currency:write_all или economy:read.
404not_found{ref} не разрешается: некорректный Steam64 либо Discord-идентификатор без привязанного игрока.
409conflictИгрок офлайн либо некому передать массовое изменение.
503server_unavailableИгровой сервер не на связи.

Содержание