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:
mode | Meaning | Valid amount |
|---|---|---|
delta (default) | A signed movement: positive credits, negative debits. | Any non-zero integer |
set | The 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.
| Field | Type | Meaning |
|---|---|---|
currency | "cash" | "gold" | Which in-game currency to move. |
amount | integer | Signed delta, or the target balance in set mode. |
mode | "delta" | "set" | Defaults to "delta". |
reason | string (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.
| Query | Meaning |
|---|---|
steam_id | Only this player's entries. |
asset | cash, gold or coins. |
source | Where the movement came from: api, panel, discord_bank, discord_cmd, pack, claim, task, leaderboard, system. |
scope | player, all or online. |
start_date / end_date | Bound the window (YYYY-MM-DD HH:MM:SS). |
limit, cursor | Page 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"
}playerisnullon a bulk entry — a server-wide change has no single subject.actoris who initiated the movement,nullwhen it was automatic (a kill reward, a scheduled charge).balance_before/balance_afterarenullon 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_countis the number of players touched —nullon a per-player entry, andnullon 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
| Status | Code | When |
|---|---|---|
400 | validation_error | Bad currency, mode, amount (0 in delta, negative in set), or an empty reason. |
403 | insufficient_scope | The key lacks currency:write, currency:write_all or economy:read. |
404 | not_found | The {ref} doesn't resolve — a malformed Steam64, or a Discord id with no linked player. |
409 | conflict | The player is offline, or no one is connected to relay a bulk change. |
503 | server_unavailable | The game server is not connected. |