neomod

Custom leaderboards

General

Players pick a leaderboard provider in their settings. With "Custom", they paste the URL of any provider.

Everything a player sees then comes from their provider: map leaderboards, the global leaderboard, profiles, and the stats of every player in-game.

A provider is a JSON-over-HTTPS server. The neomod server calls it on the player's behalf, appending the paths below to the URL the player entered.

Requests time out after 5 seconds. A provider that fails or times out shows as empty: neomod never falls back to its own leaderboards.
The URL must be https:// and resolve to a public address. Redirects are not followed, and responses over 2 MB are dropped.

Score Feed

Pull every neomod score from the public feed, to rank them however you like:

GEThttps://neomod.net/api/v1/scores?after={score_id}&limit={limit}

URL Parameters
  • after: int, id of the last score you got. 0 starts from the first score
  • limit: int, 1 to 500, default 100

Returns scores oldest first. Poll with the last id you got until the list comes back empty.

{"scores": [score, ...]}

Score ids count down from -1: newer scores have lower ids.

Players who hide their scores in their privacy settings are left out.

Score object

{
  "id": -39,
  "user_id": 11485313,
  "username": "example",
  "beatmap_md5": "050cf495f923947b859368de1ac33c36",
  "country": 239,
  "team": null,
  "pp": 201.6,
  "pp_version": 20251008,
  "score": 2624294,
  "max_combo": 293,
  "n300": 531,
  "n100": 70,
  "n50": 2,
  "ngeki": 77,
  "nkatu": 28,
  "nmiss": 7,
  "passed": true,
  "perfect": false,
  "has_replay": true,
  "speed": 1.2,
  "flags": "1073741824",
  "mods_extra": {"ar_override": 9.4},
  "client": "neomod-release-43.07",
  "created_at": "2025-09-17T22:15:12.439Z"
}
  • id: int, negative for neomod scores
  • country: int, osu! country id
  • team: int | null, osu! team id
  • pp: float, computed by neomod
  • speed: float, 1 without speed mods
  • flags: string, 64-bit mod flags in decimal
  • mods_extra: object, other mod settings. Empty when unused
  • created_at: string, ISO 8601

Other fields are as in osu!.

Stats

Stats are shown in-game, next to each player:

{
  "pp": 4029,
  "rank": 1,
  "accuracy": 0.9871,
  "play_count": 100,
  "ranked_score": 0,
  "total_score": 0
}
  • pp: int, 0 to 65535
  • rank: int, global rank. 0 when unranked
  • accuracy: float, 0 to 1
  • play_count, ranked_score, total_score: int
  • seconds_played: int, optional. Time played shown on profiles, neomod's own count when left out

Values out of range are clamped. neomod caches stats for 5 minutes.

Provider Endpoints

Respond with JSON and a 2xx status. Any other status counts as a failure.

Map leaderboard

POST{url}/v1/beatmap/{md5}/scores

Body
{
  "type": "country",
  "viewer_id": 11485313,
  "limit": 50,
  "country": 239
}
  • type: string, global, country, friend or team
  • viewer_id: int, player looking at the leaderboard
  • limit: int, up to 50
  • country: int, osu! country id. Only for country
  • team: int, osu! team id. Only for team
  • user_ids: [int], sorted ids of the viewer and their friends. Only for friend
  • mods: object, only for modded leaderboards. speed and flags as in scores, plus the mods_extra fields

A modded leaderboard request:

{
  "type": "global",
  "viewer_id": 11485313,
  "limit": 50,
  "mods": {"speed": 1.5, "flags": "16"}
}
Response

Best first, one score per player. With mods, only scores set with the same mods.

{"scores": [score, ...]}

Profile

GET{url}/v1/users/{id}/profile

Response
{
  "stats": stats,
  "top_scores": [score, ...],
  "recent_scores": [score, ...],
  "rank_history": [{"date": "2025-10-02", "rank": 1234}, ...]
}

Up to 20 scores each. stats is null for unknown players.

rank_history is optional: the player's global rank at the end of each of the last 90 days, oldest first. It draws the rank graph on their profile.

Profile scores also carry the map they were set on:

  • beatmapset_id: int
  • osu_json: object, the beatmap as returned by the osu! API, with beatmapset

Player stats

POST{url}/v1/users/stats

Body
{"user_ids": [11485313, 2]}

Up to 256 ids.

Response
{"stats": {"11485313": stats}}

Leave out players you don't know.

Global leaderboard

GET{url}/v1/leaderboard?limit={limit}&offset={offset}

Response
{
  "rows": [
    {
      "id": 11485313,
      "username": "example",
      "total_pp": 4029.5
    },
    ...
  ]
}

Best first.

Score notifications

POST{url}/v1/scores/notify

Sent when a player using your provider submits a score, so their leaderboards update right away. Scores from other players only come from the feed.

Body
{
  "score": score,
  "beatmap": {
    "beatmap_md5": "050cf495f923947b859368de1ac33c36",
    "beatmap_id": 1179925,
    "beatmapset_id": 556834
  }
}

score has no username. Reply with any JSON, e.g. {"ok": true}.