ServicesLeaderboard

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 -i never collide; pass -i to 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:40

A 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.json

Rankings

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 25

A 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 --force

Manifests 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 undo

There 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

On this page