WardenCoredocs
APIEndpoints

Packs

Browse your pack catalog, deliver a pack directly to a player, and read the delivery history.

Read your pack catalog, deliver a pack straight to a player (a free grant — no coins charged), and read the delivery history across every source (API grants, claim-code redemptions, shop purchases, admin gives).

Pack references

A pack has two labels: name — the catalog slug (starter, stable, what you script against) — and display_name — the human-readable title (Starter Pack, what players see).

Wherever a pack appears — the catalog itself, a delivery result, a ledger row, a claim code — it is the same { id, name, display_name } shape. For a pack that still exists in the catalog, both labels are populated. id is authoritative; use it against GET /v1/packs/{id} for the full record.

The one exception: a delivery-history row whose pack has since been deleted carries both labels as null — the ledger keeps the delivery, but the catalog entry it named is gone.

GET /v1/packs

List the pack catalog, newest-configured first.

Scope: packs:read.

QueryTypeMeaning
enabledtrue | falseOnly enabled / disabled packs.
category_idintegerOnly packs in this category.
searchstringFree-text match on the pack name.
cursorstringOpaque pagination cursor from a previous next_cursor.
limitinteger (1–100)Page size. Default 50.
{
  "data": [
    {
      "id": 5,
      "name": "starter",
      "display_name": "Starter Pack",
      "description": "A leg-up for new arrivals.",
      "category": { "id": 2, "name": "Bundles" },
      "price": 100,
      "enabled": true,
      "shop_enabled": true,
      "buy_limit": { "count": 0, "window_minutes": 0 },
      "image_url": "/api/packs/5/image?raw=1",
      "created_at": "2026-01-01 12:00:00",
      "updated_at": "2026-01-02 09:30:00"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "total": 1
}

buy_limit.count of 0 means unlimited. price is the coin cost when bought through the shop — direct delivery below ignores it.

GET /v1/packs/{id}

A single pack by its numeric id. 404 not_found when it doesn't exist.

Scope: packs:read.

POST /v1/packs/{id}/deliver

Deliver the pack directly to a player, for free — the pack's contents are granted with no coin charge. Identify the recipient with exactly one of steam_id or discord_id.

Scope: packs:deliver.

FieldTypeMeaning
steam_idstring (17 digits)Recipient Steam64. Provide this or discord_id.
discord_idstring (17–20 digits)Recipient Discord id; resolves to the linked player. Provide this or steam_id.
quantityinteger (1–100)How many copies. Default 1.
POST /v1/packs/5/deliver
Content-Type: application/json

{ "steam_id": "76561198000000000", "quantity": 1 }
{
  "pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
  "player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
  "quantity": 1,
  "delivered": true,
  "status": "completed",
  "purchase_id": 42,
  "error_message": null
}

delivered is the overall outcome; status is the precise result (completed, partial, failed, refund_failed, ledger_failed). A delivery is recorded in the history below with source: "api".

Delivery is NOT idempotent — send an Idempotency-Key

Each call spawns the pack's items again, so a network retry could deliver twice. Send an Idempotency-Key so a retry after a blip replays the first result instead of delivering again.

The recipient must be a real player

A discord_id that isn't linked to any player returns 404 not_found. A well-formed but never-seen steam_id is accepted, but the items may not land (the delivery reports status: "failed" / "partial").

GET /v1/packs/deliveries

The delivery history (the pack-purchase ledger) — every pack that reached a player, whatever the source: direct API grants (api), claim-code redemptions (claim), admin gives (admin), shop purchases, and more.

Scope: packs:read.

QueryTypeMeaning
steam_idstring (digits)Only this player's deliveries. Provide this or discord_id.
discord_idstring (digits)Resolves to the linked player. An unlinked id → empty page.
pack_idintegerOnly deliveries of this pack.
sourcestringOnly this source (api, claim, admin, …).
statusstringOnly this outcome (completed, partial, failed, …).
cursorstringOpaque pagination cursor.
limitinteger (1–100)Page size. Default 50.
{
  "data": [
    {
      "id": 7,
      "pack": { "id": 5, "name": "starter", "display_name": "Starter Pack" },
      "player": { "steam_id": "76561198000000000", "discord_id": "123456789012345678" },
      "quantity": 1,
      "total_price": 0,
      "status": "completed",
      "source": "api",
      "error_message": null,
      "delivered_at": "2026-01-03 18:05:00"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "total": 1
}

player.discord_id is the recipient's current Discord link (Discord travels with Steam — null when they aren't linked), regardless of what was recorded at delivery time.

Errors

Standard error envelope.

CodeStatusWhen
insufficient_scope403The key lacks packs:read (reads) or packs:deliver (delivery).
premium_required403The game server's plan does not include the pack/shop feature.
not_found404Unknown pack id; or a discord_id recipient/filter that isn't linked to any player (delivery only — filters return an empty page instead).
validation_error400Bad id, both/neither of steam_id & discord_id, or a game-server rejection (its message is in error.details.mod_message).
conflict409Delivery refused because the connected game server is too old to honour a free grant — it would charge the recipient. Update the game server and retry.
idempotency_conflict409The Idempotency-Key you sent was already used with a different request body. Reuse the key only for a retry of the identical delivery.
server_unavailable503The game server is offline or unreachable.
gateway_timeout504The server did not respond in time.

On this page