Skip to content

Bantam — Plug a leaderboard into any game. Done.

Authoritative leaderboards for any game. One container. One JSON file. Boom.

Skip the server engineering. Bantam is a single Docker container that owns identity, validation, ranks, and recovery — so your game sends evidence and reads leaderboards. No DB schema. No migration plan. No auth library. No replica math.

One container

Fastify, SQLite, Litestream. The whole thing runs as one non-root container. SQLite WAL means single-writer per game, no distributed locks, no surprises.

Game-agnostic by default

The framework ships zero game-specific code. Drop a .mjs adapter file into a mounted folder, or skip code entirely and declare boards in JSON. Done.

Server-side authority

Players submit evidence. The server only writes what the adapter derives. Tampering, replays, and fabricated scores are caught at the door.

Self-host or close to it

Docker image, persistent volume, S3-compatible bucket. You own the data. You own the keys. You own the backups.

Your first game — 4 commands, no code required

Section titled “Your first game — 4 commands, no code required”

You’re 60 seconds from a leaderboard. The “demo” game below is a real config that ships with the image. It accepts { "high_score": <number> } and ranks players.

Terminal window
docker run -d --name bantam \
-p 3000:3000 \
-v bantam-data:/data \
-v ./my-game:/bantam/adapters \
-v ./games.json:/etc/bantam/games.json:ro \
-e BANTAM_GAME_DEMO_IDENTITY_SALT=$(openssl rand -hex 32) \
-e BANTAM_GAME_DEMO_SESSION_SECRET=$(openssl rand -hex 32) \
-e BANTAM_GAME_DEMO_CURSOR_SECRET=$(openssl rand -hex 32) \
bazokhan/bantam:latest

./games.json:

{
"environment": "production",
"games": [
{
"id": "my-game",
"displayName": "My Game",
"adapters": [
{ "kind": "generic", "boards": [{ "id": "high_score", "direction": "higher", "period": "all_time" }] }
],
"secretPrefix": "BANTAM_GAME_DEMO",
"gracePeriodHours": 24,
"authProvider": "development"
}
]
}
Terminal window
curl -X POST http://localhost:3000/v1/games/my-game/runs/generic:high_score \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{"runId":"run-0001","payload":{"high_score":42}}'
Terminal window
curl http://localhost:3000/v1/games/my-game/leaderboards/high_score \
-H "Authorization: Bearer <access-token>"

Done. You have a real, authoritative, server-validated leaderboard for a real game. Zero database tables touched by hand.

No SQL to write

SQLite migrations are checksummed and applied automatically on first boot. Your game never sees a row.

No auth boilerplate

Drop in authProvider: "pgs-v2" and you get Google Play Games Services v2 server auth codes → per-game HMAC pseudonyms in one config line.

No ops math

Litestream replication to S3-compatible storage runs out of the box. npm run ops -- restore demo brings a game back from a fresh bucket.

No proprietary lock-in

The OpenAPI spec, SDK source, and adapter contract are all open. Move off when you outgrow it.

BLIP
8-bit mascot
BEEP
generica
BOOP
shape co.
BONK
tile shape

Bantam is a small, scrappy chicken. Bantam is also a small, scrappy game backend. The container is small, the dependencies are small, the integration footprint is small. It does the things leaderboards need and skips the rest.

Bantam is structured so an agent can wire a game end-to-end without reading prose.

Surface area (machine-readable):

  • OpenAPI 3.0.3 spec at runtime: GET /openapi.json
  • Adapter contract TypeScript types: @bantam/adapter-contract
  • Generated SDK: @gamercury/bantam-sdk (BantamClient)
  • This site’s typed reference: see API and SDK

Action shapes (one JSON file per surface):

  • games.json schema: TypeBox-validated, see Manifest and PGS v2
  • RunBody.payload: Record<string, unknown>, opaque by design — adapters define their own payload contract
  • Per-game env vars: ${secretPrefix}_IDENTITY_SALT|SESSION_SECRET|CURSOR_SECRET, plus optional PGS_CLIENT_ID|SECRET

Capabilities (what Bantam does and does NOT do):

  • ✓ authenticates players, persists sessions, refreshes them
  • ✓ validates run submissions against a server-side adapter contract
  • ✓ persists leaderboard entries per (board_id, period_key, player_id)
  • ✓ serves paginated leaderboards, player’s rank, around-me
  • ✓ continuous backup to S3-compatible storage
  • ✗ does not validate the player’s gameplay events (your adapter does)
  • ✗ does not provide an admin dashboard beyond read-only inspection

Sandbox expectations:

  • The framework writes only to /data/games/<gameId>/bantam.sqlite.
  • The framework reads only from /etc/bantam/games.json and /bantam/adapters/.
  • No outbound network calls except Litestream replication and the configured auth provider.