Claim codes
Mint, list, and revoke redeemable pack claim codes for your players.
A claim code is a redeemable token tied to a pack. Mint one for a specific player, hand it out, and they redeem it in-game or via Discord to receive the pack. Use these endpoints to mint, list (the code history), and revoke codes.
Redemptions land in the pack delivery history with
source: "claim".
POST /v1/claim-codes
Mint a targeted claim code for a pack.
Scope: claim_codes:write.
| Field | Type | Meaning |
|---|---|---|
pack_id | string (positive integer) | The pack to grant. Resolved to the pack by id. |
steam_id | string (17 digits) | The recipient — only this player can redeem the code. |
max_uses | integer (≥ 1) | How many times it can be redeemed. Default 1. |
expiry_days | integer (0–3650) | Days until it expires. 0 = never. Default 0. |
POST /v1/claim-codes
Content-Type: application/json
{ "pack_id": "5", "steam_id": "76561198000000000", "max_uses": 1, "expiry_days": 7 }{
"code": "scrap_50units-VRBC7A",
"pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
"target": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"max_uses": 1,
"expires_at": "2026-01-10 12:00:00",
"dm_status": "sent"
}If the recipient is Discord-linked, the code is also DM'd to them; dm_status reports that delivery.
expires_at is null when the code never expires. The pack object is the standard
pack reference — catalog slug plus display title.
Minting is NOT idempotent — send an Idempotency-Key
Each call mints a new code. A network retry would mint a second one. Send an
Idempotency-Key so a retry after a blip replays the first code instead.
GET /v1/claim-codes
List minted codes with their redemption state (the code history).
Scope: claim_codes:read.
| Query | Type | Meaning |
|---|---|---|
pack_id | string (positive integer) | Only codes for this pack. |
source | wargm | discord | panel | How the code was created. |
status | active | expired | exhausted | revoked | Redemption state. |
target | string (Steam64 digits) | Match on the target Steam64 (partial match allowed). |
cursor | string | Opaque pagination cursor. |
limit | integer (1–100) | Page size. Default 50. |
{
"data": [
{
"code": "scrap_50units-VRBC7A",
"pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
"target": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
"source": "panel",
"status": "active",
"max_uses": 1,
"uses_remaining": 1,
"created_at": "2026-01-03 12:00:00",
"expires_at": "2026-01-10 12:00:00",
"dm": { "channel_id": "111111111111111111", "message_id": "222222222222222222" }
}
],
"has_more": false,
"next_cursor": null,
"total": 1
}target is null for a public (untargeted) code. target.discord_id is the recipient's current
Discord link (Discord travels with Steam — null when they aren't linked).
DELETE /v1/claim-codes/{code}
Revoke a code — it can no longer be redeemed. 404 not_found when the code doesn't exist.
Scope: claim_codes:write.
{ "code": "scrap_50units-VRBC7A", "revoked": true }Errors
Standard error envelope.
| Code | Status | When |
|---|---|---|
insufficient_scope | 403 | The key lacks claim_codes:read (list) or claim_codes:write (mint / revoke). |
premium_required | 403 | The game server's plan does not include the pack/shop feature. |
not_found | 404 | Unknown pack_id (mint) or unknown code (revoke). |
validation_error | 400 | Bad pack_id/steam_id, an out-of-range max_uses/expiry_days, an out-of-enum source/status, or a game-server rejection (its message is in error.details.mod_message). |
idempotency_conflict | 409 | The Idempotency-Key you sent was already used with a different request body. Reuse the key only for a retry of the identical mint. |
server_unavailable | 503 | The game server is offline or unreachable. |
gateway_timeout | 504 | The server did not respond in time. |