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.
Authorization: Bearer lgb_your_game_keycurl https://your-app.vercel.app/api/leaderboards \
-H "Authorization: Bearer lgb_your_game_key"Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/leaderboards | List the game's leaderboards |
| POST | /api/leaderboards | Create a leaderboard |
| POST | /api/leaderboards/:slug/scores | Submit a score |
| GET | /api/leaderboards/:slug/scores | Fetch top scores |
| GET | /api/leaderboards/:slug/rank/:playerId | Get a player's rank |
List leaderboards
/api/leaderboardsReturns every leaderboard owned by the authenticated game.
curl https://your-app.vercel.app/api/leaderboards \
-H "Authorization: Bearer lgb_your_game_key"{
"leaderboards": [
{
"id": 1,
"gameId": 1,
"slug": "classic",
"name": "Classic Mode",
"sortOrder": "desc",
"createdAt": "2026-01-01T12:00:00.000Z"
}
]
}Create a leaderboard
/api/leaderboardsCreates a board for the authenticated game. Reusing an existing slug returns 400.
| Name | Type | Description |
|---|---|---|
| slug * | string | URL-safe identifier: lowercase letters, numbers, and hyphens (for example classic-mode). |
| name * | string | Display name, up to 128 characters. |
| sortOrder | "desc" | "asc" | Whether higher or lower scores win. Defaults to "desc". |
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"
}'{
"leaderboard": {
"id": 1,
"gameId": 1,
"slug": "classic",
"name": "Classic Mode",
"sortOrder": "desc",
"createdAt": "2026-01-01T12:00:00.000Z"
}
}Submit a score
/api/leaderboards/:slug/scoresRecords 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.
| Name | Type | Description |
|---|---|---|
| playerId * | string | Stable identifier for the player, up to 128 characters. |
| score * | number | A finite number. Higher or lower wins by board order. |
| playerName | string | Optional display name, up to 128 characters. |
| metadata | object | Optional JSON object for extra context (level, character, ...). Up to 4 KB serialized. |
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 }
}'{
"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
/api/leaderboards/:slug/scoresReturns the highest-ranked entries. By default each player appears once, with their best score; ties share the same rank.
| Name | Type | Description |
|---|---|---|
| limit | number | Entries to return, 1–100. Defaults to 10. |
| offset | number | Number of entries to skip, for pagination. Defaults to 0. |
| unique | 0 | 1 | 1 (default) keeps only each player's best entry. 0 returns every submission. |
curl "https://your-app.vercel.app/api/leaderboards/classic/scores?limit=10&offset=0&unique=1" \
-H "Authorization: Bearer lgb_your_game_key"{
"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
/api/leaderboards/:slug/rank/:playerIdLooks up a single player's best entry. When the player has no scores the response still succeeds with ranked: false.
curl https://your-app.vercel.app/api/leaderboards/classic/rank/player-123 \
-H "Authorization: Bearer lgb_your_game_key"Response
{
"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:
{
"playerId": "zed",
"ranked": false,
"entry": null,
"totalPlayers": 42
}Errors
Failures return a non-2xx status and a JSON body with an error message.
{ "error": "score must be a finite number" }| Status | When |
|---|---|
| 400 | Invalid JSON, missing required fields, bad slug, or a duplicate board. |
| 401 | Missing, malformed, or unknown API key. |
| 404 | The leaderboard does not exist for this game. |
| 500 | Unexpected 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.