WardenCoredocs
APIEndpoints

In-game currency

Credit or debit a player's in-game cash and gold, in bulk or one player at a time, and read the economy ledger.

Move in-game SCUM currency — the cash and gold a player carries in the game world.

Currency is not coins

Cash and gold are the player's money inside the game. Changing them runs a command on the game server.

Coins are the Warden shop currency, held in Warden's own database. Separate endpoints, separate scopes.

delta vs set

mode decides how amount is read:

modeMeaningValid amount
delta (default)A signed movement: positive credits, negative debits.Any non-zero integer
setThe balance to write.Any integer >= 0

0 is rejected in delta mode and accepted in set mode, where it zeroes the balance.

Send an Idempotency-Key

A delta applied twice moves the balance twice, so a network retry can double-apply. With an Idempotency-Key the retry replays the first result instead. (set is idempotent by nature, but the header costs nothing.)

Use a new key for each operation — one per payout, per fee, per reward — and reuse it only when retrying that same operation. A key hard-coded in your client turns every later call into a replay of the first.

POST /v1/players/{ref}/currency

Adjust one player. {ref} is a Steam64 or a prefixed Discord id (discord:123456789012345678).

Scope: currency:write.

FieldTypeMeaning
currency"cash" | "gold"Which in-game currency to move.
amountintegerSigned delta, or the target balance in set mode.
mode"delta" | "set"Defaults to "delta".
reasonstring (1–500)Required. Recorded on the ledger entry.
POST /v1/players/76561198000000000/currency
Content-Type: application/json
Idempotency-Key: <unique-key-per-operation>

{ "currency": "cash", "amount": -500, "reason": "auction fee" }

The response carries the balances on both sides of the change, plus the ledger entry id:

{
  "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"
}

Both balances come back, whichever one you moved.

The player must be online

The game applies a per-player change through that player's own session, so an offline player returns 409 conflict. Check GET /v1/players/online first, or retry once they reconnect.

`balance` is null if the player leaves mid-change

Both balances are read from the game just before and just after the change. If the player disconnects in between, the change still applied but the new balance cannot be read — so balance comes back null rather than repeating balance_before, which is already stale. The ledger entry records it as unknown too.

Debits are not checked against the balance

Debiting 500 from a player holding 0 leaves them at -500 and still returns 200. The game server applies the change without looking at the funds available.

Coins work the other way: an overdraft is rejected with 409 conflict, because there the check and the debit happen in one database transaction.

Reading the balance first does not hold it

GET /v1/players/{ref} gives you money and gold, but the read and the write are two separate calls. In between, the player can spend or earn in game, and the Discord bank, packs and other operators can move the same balance.

So a delta sized against that read can still overdraft, and a set can overwrite a newer balance. The game server offers no conditional debit, so the gap cannot be closed from here. Size charges so an overdraft is acceptable, keep the authoritative balance in coins, or reconcile afterwards from the ledger — which needs a reconciler that tolerates unknowns, since balance_before and balance_after are both null on a bulk entry and on a per-player entry where the player disconnected before the balance could be read back.

POST /v1/economy/currency

Adjust everyone at once.

Scope: currency:write_all.

Takes the same fields plus target: "all" (default — every player in the game database, online and offline) or "online" (the currently-connected players only).

POST /v1/economy/currency
Content-Type: application/json
Idempotency-Key: <unique-key-per-operation>

{ "currency": "gold", "amount": 10, "target": "online", "reason": "event reward" }
{ "currency": "gold", "mode": "delta", "amount": 10, "target": "online",
  "affected_count": null, "transaction_id": "8413" }

A bulk change is relayed through any connected player, so with nobody online it returns 409 conflict.

`affected_count` is always null here

The game server's bulk currency commands report no player count. The bulk coin endpoint does report one, since that operation runs in Warden's own database.

GET /v1/economy/transactions

The economy ledger: one entry per balance movement on your server, coins and in-game currency alike, whatever caused it. Adjustments through this API, panel actions, Discord bank deposits, pack purchases, kill rewards and subscription charges all land here.

Scope: economy:read.

QueryMeaning
steam_idOnly this player's entries.
assetcash, gold or coins.
sourceWhere the movement came from: api, panel, discord_bank, discord_cmd, pack, claim, task, leaderboard, system.
scopeplayer, all or online.
start_date / end_dateBound the window (YYYY-MM-DD HH:MM:SS).
limit, cursorPage size (1–100, default 50) and the opaque cursor from 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": "auction fee",
      "pack_name": null
    }
  ],
  "has_more": true,
  "next_cursor": "eyJvZmZzZXQiOjUwfQ"
}
  • player is null on a bulk entry — a server-wide change has no single subject.
  • actor is who initiated the movement, null when it was automatic (a kill reward, a scheduled charge).
  • balance_before / balance_after are null on a bulk entry, since a server-wide update has no per-player figures, and also on a per-player entry where the player disconnected before the balance could be read back. Treat them as unknown rather than as zero.
  • affected_count is the number of players touched — null on a per-player entry, and null on a bulk currency entry (see above).

The ledger is never cached, so a read straight after a write shows that write. Every request reaches the game server: poll at a sensible interval rather than in a tight loop.

Errors

StatusCodeWhen
400validation_errorBad currency, mode, amount (0 in delta, negative in set), or an empty reason.
403insufficient_scopeThe key lacks currency:write, currency:write_all or economy:read.
404not_foundThe {ref} doesn't resolve — a malformed Steam64, or a Discord id with no linked player.
409conflictThe player is offline, or no one is connected to relay a bulk change.
503server_unavailableThe game server is not connected.

On this page