Guide

REST API

A small JSON API over HTTPS. Every request is scoped to the game identified by your bearer key.

All requests and responses are JSON. The base URL is your deployment, for example https://your-app.vercel.app. There is no separate API version prefix.

Authentication

Send the game key as a bearer token on every request. Missing or invalid keys get a 401. Keys are created with the admin CLI (npm run admin game:create) and shown only once.

Header
Authorization: Bearer lgb_your_game_key
terminal
curl https://your-app.vercel.app/api/leaderboards \
  -H "Authorization: Bearer lgb_your_game_key"

Endpoints

MethodPathDescription
GET/api/leaderboardsList the game's leaderboards
POST/api/leaderboardsCreate a leaderboard
POST/api/leaderboards/:slug/scoresSubmit a score
GET/api/leaderboards/:slug/scoresFetch top scores
GET/api/leaderboards/:slug/rank/:playerIdGet a player's rank

List leaderboards

GET/api/leaderboards

Returns every leaderboard owned by the authenticated game.

terminal
curl https://your-app.vercel.app/api/leaderboards \
  -H "Authorization: Bearer lgb_your_game_key"
200 OK
{
  "leaderboards": [
    {
      "id": 1,
      "gameId": 1,
      "slug": "classic",
      "name": "Classic Mode",
      "sortOrder": "desc",
      "createdAt": "2026-01-01T12:00:00.000Z"
    }
  ]
}

Create a leaderboard

POST/api/leaderboards

Creates a board for the authenticated game. Reusing an existing slug returns 400.

NameTypeDescription
slug *stringURL-safe identifier: lowercase letters, numbers, and hyphens (for example classic-mode).
name *stringDisplay name, up to 128 characters.
sortOrder"desc" | "asc"Whether higher or lower scores win. Defaults to "desc".
terminal
curl -X POST https://your-app.vercel.app/api/leaderboards \
  -H "Authorization: Bearer lgb_your_game_key" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "classic",
    "name": "Classic Mode",
    "sortOrder": "desc"
  }'
201 Created
{
  "leaderboard": {
    "id": 1,
    "gameId": 1,
    "slug": "classic",
    "name": "Classic Mode",
    "sortOrder": "desc",
    "createdAt": "2026-01-01T12:00:00.000Z"
  }
}

Submit a score

POST/api/leaderboards/:slug/scores

Records a score and returns the player's best entry and current rank. Every submission is stored, but a player's rank always reflects their best score, so you can submit generously.

NameTypeDescription
playerId *stringStable identifier for the player, up to 128 characters.
score *numberA finite number. Higher or lower wins by board order.
playerNamestringOptional display name, up to 128 characters.
metadataobjectOptional JSON object for extra context (level, character, ...). Up to 4 KB serialized.
terminal
curl -X POST https://your-app.vercel.app/api/leaderboards/classic/scores \
  -H "Authorization: Bearer lgb_your_game_key" \
  -H "Content-Type: application/json" \
  -d '{
    "playerId": "player-123",
    "playerName": "Ada",
    "score": 4200,
    "metadata": { "level": 3 }
  }'
201 Created
{
  "submitted": true,
  "isNewBest": true,
  "bestScore": 4200,
  "entry": {
    "id": 12,
    "leaderboardId": 1,
    "playerId": "player-123",
    "playerName": "Ada",
    "score": 4200,
    "metadata": { "level": 3 },
    "createdAt": "2026-01-01T12:05:00.000Z",
    "rank": 2
  }
}

Fetch top scores

GET/api/leaderboards/:slug/scores

Returns the highest-ranked entries. By default each player appears once, with their best score; ties share the same rank.

NameTypeDescription
limitnumberEntries to return, 1–100. Defaults to 10.
offsetnumberNumber of entries to skip, for pagination. Defaults to 0.
unique0 | 11 (default) keeps only each player's best entry. 0 returns every submission.
terminal
curl "https://your-app.vercel.app/api/leaderboards/classic/scores?limit=10&offset=0&unique=1" \
  -H "Authorization: Bearer lgb_your_game_key"
200 OK
{
  "leaderboard": {
    "slug": "classic",
    "name": "Classic Mode",
    "sortOrder": "desc"
  },
  "scores": [
    {
      "id": 12,
      "leaderboardId": 1,
      "playerId": "player-123",
      "playerName": "Ada",
      "score": 4200,
      "metadata": { "level": 3 },
      "createdAt": "2026-01-01T12:05:00.000Z",
      "rank": 1
    }
  ],
  "totalPlayers": 42,
  "limit": 10,
  "offset": 0
}

Get a player's rank

GET/api/leaderboards/:slug/rank/:playerId

Looks up a single player's best entry. When the player has no scores the response still succeeds with ranked: false.

terminal
curl https://your-app.vercel.app/api/leaderboards/classic/rank/player-123 \
  -H "Authorization: Bearer lgb_your_game_key"

Response

200 OK
{
  "playerId": "player-123",
  "ranked": true,
  "entry": {
    "id": 12,
    "playerId": "player-123",
    "playerName": "Ada",
    "score": 4200,
    "metadata": { "level": 3 },
    "createdAt": "2026-01-01T12:05:00.000Z",
    "rank": 2
  },
  "totalPlayers": 42
}

When the player has no scores:

200 OK
{
  "playerId": "zed",
  "ranked": false,
  "entry": null,
  "totalPlayers": 42
}

Errors

Failures return a non-2xx status and a JSON body with an error message.

Error body
{ "error": "score must be a finite number" }
StatusWhen
400Invalid JSON, missing required fields, bad slug, or a duplicate board.
401Missing, malformed, or unknown API key.
404The leaderboard does not exist for this game.
500Unexpected server error.

Using it from Godot

If you are building in Godot, skip the hand-written HTTP calls and use the bundled addon. It wraps every endpoint in an await-based GDScript client.

Read the Godot addon guide →