Leaderboard
Multi-metric rankings in isolated namespaces: submit scores for several metrics at once, query top-N by a weighted composite or a single metric, look up one player's rank and neighbors, filter by time window, and batch submissions. Not just for games - sales contests, GitHub stars, and poll results rank the same way. Every example on this page was executed against a live server.
When to reach for it: game season rankings, sales contests, engagement scores, any "who is winning" question over one or more numeric metrics.
When not to: bracket-based competitions (single/double elimination, swiss) belong to Tournament; event-driven reputation scoring (with its own reputation leaderboard) belongs to Reputation.
Concepts
- Namespace - an isolated leaderboard space (
season-1,daily-sales). Created implicitly on first submit, shared by all users in the tenant. - Metric - one scored dimension (
score,accuracy,revenue). A namespace holds several; each submit updates only the metrics it names. - Composite score - rankings combine a namespace's metrics into one number: the weighted sum of each entity's metric values, equal weights by default. Weights are supplied per query, not stored.
- Entity ID - who the score belongs to. Defaults to the JWT subject, so
two users submitting without
-inever collide; pass-ito submit on behalf of any ID. - Manifest - an optional metadata record (name, description, tags) for cataloging leaderboards. Manifests and namespaces are separate systems: creating a manifest does not create a namespace, and submitting scores never requires one.
Submitting scores
snug leaderboard submit -n season-1 -m score:100,accuracy:80 -d '{"clan":"wolves"}'
snug leaderboard submit -n season-1 -i player-bob -m score:120,accuracy:40A submit replaces each named metric's value; it does not add to it.
Unnamed metrics survive - verified: after score:100,accuracy:80 a second
submit of score:50 left accuracy at 80 and the composite at 130. There is
no increment operation; to accumulate, read the current value and resubmit.
The response reports the entity's new standing:
{
"id": "player-bob",
"weighted_score": 160.0,
"rank": 2,
"total_entities": 2
}Batch submission
batch-submit posts a JSON array of {id, metrics, meta} objects in one
request - here every id is required, there is no JWT-subject default.
Entries are applied independently and the batch is capped (500 by default).
snug leaderboard batch-submit -n season-1 -f players.jsonRankings
snug leaderboard top -n season-1 -l 10 # composite, equal weights
snug leaderboard top -n season-1 -l 10 -w score:0.7,accuracy:0.3
snug leaderboard metric-top -n season-1 -m accuracy -l 10 # one metric only
snug leaderboard rank -n season-1 -i player-bob # one entity's standing
snug leaderboard surrounding -n season-1 -i player-bob -w 2 # neighbors (-g for weights here)Weights are relative and need not sum to 1. The arithmetic, verified live
with alice at score:100, accuracy:80 and bob at score:120, accuracy:40:
equal weights rank alice first (180 vs 160), but -w score:0.7,accuracy:0.3
flips the order - bob scores 120*0.7 + 40*0.3 = 96 against alice's 94.
Entities with equal composites both appear, but their relative order is not
guaranteed and can differ between endpoints.
Over HTTP the same query is GET /api/v1/leaderboard/{namespace}/top:
curl -H "Authorization: Bearer $SNUG_API_TOKEN" \
"http://localhost:4000/api/v1/leaderboard/season-1/top?limit=2"{
"status": 200,
"msg": "OK",
"data": [
{
"id": "player-alice",
"rank": 1,
"score": 180.0,
"metrics": { "score": 100.0, "accuracy": 80.0 },
"meta": { "clan": "wolves" }
},
{
"id": "player-bob",
"rank": 2,
"score": 160.0,
"metrics": { "score": 120.0, "accuracy": 40.0 }
}
]
}score is the composite; metrics holds the raw values. Querying a
namespace nobody has submitted to returns an empty array with 200, not a
404. rank for an unknown entity is 200 with data: null over HTTP;
the CLI prints a not-found notice and exits non-zero.
Time windows
time-window ranks only entities whose last submission falls inside a Unix
epoch range, in seconds - millisecond timestamps do not error, they
silently match nothing (reproduced live):
snug leaderboard time-window -n season-1 -s 1787920000 -e 1787923700 -l 25A cross-namespace variant, GET /api/v1/leaderboard/aggregate?namespaces=a,b&metrics=score,
merges standings over several namespaces; it is HTTP-only (no CLI command).
Manifests
snug leaderboard create --name "Season 1" --description "Spring" --tags game:arena,season:1
snug leaderboard list --search "season" --active true --sort "-created_at"
snug leaderboard get --id jgYBepqMbqVduynCZGAp
snug leaderboard update --id jgYBepqMbqVduynCZGAp --active false
snug leaderboard delete --id jgYBepqMbqVduynCZGAp --forceManifests are owned: update/delete by anyone but the creator fails with
403 not_owner, and a missing ID is 404 leaderboard_not_found (both
reproduced). Reads are shared. Tags are key:value pairs, unlike the plain
labels in KV.
Introspection and cleanup
snug leaderboard namespaces # every namespace + entity counts
snug leaderboard metrics -n season-1 # per-metric count/min/max/avg
snug leaderboard delete-entity -n season-1 -i player-bob # from ALL metrics
snug leaderboard delete-metric -n season-1 -m accuracy # for ALL entities
snug leaderboard delete-namespace -n season-1 --force # everything, no undoThere is no way to remove one entity from a single metric. Deleting a metric immediately changes composite rankings - the remaining metrics carry the whole score.
Limits and configuration
By default 100 namespaces, 10 metrics per namespace (an 11-metric submit
fails with 429 metrics_limit_exceeded, verified), 1000 entries per
metric, and 500 submissions per batch - all tunable via the
LEADERBOARD_* variables in the
Leaderboard CONFIG reference.
Reference
- Leaderboard API - score submission and ranking endpoints
- Leaderboard Manifest API - manifest CRUD
- Related: Tournament for brackets, Matchmaking for skill-based pairing, Reputation for event-driven reputation scores