On this page

← All documentation

Map index API

The public, read-only JSON API of the map index: chart identity, sources, chartsets, the .rox file, online scores, limits.

The map index behind the Browse page has a small public API. It is read-only, needs no account and returns JSON (the chart file excepted). Please be gentle: answers are cached, and requests are limited per address.

Chart identity: the note hash

A chart is identified by its note hash: 64 lowercase hexadecimal characters, a BLAKE3 digest of the key count and of every note (start time, column, kind, end time of a long note) in whole milliseconds, relative to the first note. The same notes give the same hash whether they come from an .osu, .sm, .ssc or .qua file, and whatever the file is called; mines and bursts are ignored; the audio and the metadata do not count. The hash version is returned as noteHashVersion. The index only holds osu!mania and Etterna charts of 4 to 7 keys.

GET /api/charts/{hash}

Where can this chart be downloaded? A malformed hash answers 400; a well-formed hash the index does not know answers 200 with status: "unknown" (so that a client can ask without error handling).

{
  "chartHash": "9f2c…64 hex…",
  "status": "known",
  "sources": [
    { "kind": "osuSet", "ref": "4001", "confidence": "verified", "checkedAtMs": 1791600000000 }
  ],
  "ttlMs": 300000,
  "chart": {
    "hash": "9f2c…", "noteHashVersion": 1, "keys": 7,
    "bpmMin": 120.0, "bpmMax": 180.0, "bpmMain": 180.0,
    "lengthMs": 144000, "notes": 1200, "lns": 40,
    "densityAvg": 8.3, "densityPeak": 14.0, "roxBytes": 5120,
    "title": "Song", "artist": "Artist", "creator": "Mapper", "difficulty": "7K Hard"
  }
}

kind is an open set (osuSet, etternaPack, ...): ignore kinds you do not know. ref is an identifier, never a URL: an osu! set id, an EtternaOnline pack id. The page of the source is https://osu.ppy.sh/beatmapsets/<ref> or https://etternaonline.com/packs/<ref>. The answer carries an ETag and Cache-Control: public, max-age=300.

GET /api/charts/{hash}/rox

The chart in the compact ROX format (the open exchange format of the game), served on request as an attachment (application/octet-stream, ETag: "<hash>", immutable). 404 if the chart is unknown or was removed. Never audio, never images.

GET /api/charts/{hash}/scores

The online board of a chart: each player once, with their best play. The game server verified every play and replayed it on its own copy of the chart before writing it into the site's database, and it reads every play under the Prism judgement (the judgement a player chose is only the record of what they played with: there is no judgement dimension on the boards). Accuracy, combo and misses are the Prism ones, never the player's claim. Best rating first (the MinaCalc 515 rating the server computed; a play without one last), then the Prism accuracy, then the highest combo, then the play recorded first. The plays of a banned account, or of an account scheduled for deletion, are not listed.

Query parameter Meaning
rate absent or all: THE board of the chart, each player once with their best play whatever its speed. An exact playback rate, 0.5 to 2.0: the same ranking over the plays of that speed, each player once too (total counts players)
limit, offset limit is 1 to 100 (default 50), offset at most 10000

There is no judgement parameter any more; an unknown parameter (an old client's judgement=) is ignored, not an error.

{
  "chartHash": "9f2c…64 hex…", "rate": 1.0,
  "total": 132, "offset": 0, "limit": 50,
  "items": [
    { "id": 4821, "hasReplay": true, "rank": 1, "username": "Ada", "userId": "0190…uuid…", "accuracy": 99.12, "maxCombo": 842, "misses": 1,
      "counts": [610, 120, 60, 30, 12, 6, 3, 1], "rating": 24.5, "playedAtMs": 1791600000000, "playbackRate": 1.0 }
  ]
}

A chart nobody played is total: 0 with no items, never an error; a malformed hash or parameter answers 400 and names it. rank counts over the whole board, not the page. playedAtMs is the date the player's game claims. userId is the account's UUID (lowercase): the game compares it to its own account to spot "my score". counts (the eight Prism tiers, the Miss last) and rating are absent from a play stored before the server kept them.

GET /api/charts/{hash}/boards

The rates a chart has, so that a page can offer only the choices that lead to a board with plays: one entry per playback rate with at least one visible play, by rate ascending (at most 200). There is no judgement in it.

{
  "chartHash": "9f2c…64 hex…",
  "boards": [
    { "rate": 1.0, "plays": 132 }
  ]
}

plays counts the players of that rate (the total of GET /api/charts/{hash}/scores?rate=). Plays of banned accounts and of accounts scheduled for deletion are not counted. A chart nobody played is an empty list, never an error; a malformed hash answers 400.

GET /api/chartsets

The chartsets (one per song folder of an osu! set or an Etterna pack), newest first by default.

Query parameter Meaning
q text found in the title, artist, creator or tags
keys comma-separated key counts, e.g. 4,7
source osuSet or etternaPack
creator, artist, title part of the name (with creatorMatch, artistMatch, titleMatch = exact for an exact, case-insensitive match)
difficulty a difficulty name of one of the set's charts (difficultyMatch as above)
tags text found in the tags
bpmMin, bpmMax BPM range overlapping the set's charts
lengthMin, lengthMax length in seconds
densityMin, densityMax peak notes per second
ratingCalc, ratingMin, ratingMax difficulty range by calculator: minacalc (default), osu, quaver or sunny, at 1.0×
sort updated (default), title, artist, bpm, length, density, rating (by ratingCalc)
page, perPage perPage is at most 60

The answer is { "items": [...], "total": n, "page": p, "perPage": n }; each item carries id, title, artist, creator, sourceKind, keys, bpmMin, bpmMax, lengthMinMs, lengthMaxMs, densityMax, chartCount and downloadUrl (/go/chartset/<id>, absent for a set with no public source).

GET /api/chartsets/{id}

One chartset: the fields above plus tags, sources and charts (each with its chart object, its difficulty name in this set and, for osu!, its osuBeatmapId).

GET /go/chartset/{id}

A 302 redirect to the page of the original source, where the map is downloaded. 404 for a set without a public source.

Limits and manners

  • Requests are limited per address (240 per minute by default for each of the four groups: chart lookups, .rox files, boards, the chartset list); beyond it the answer is 429 with Retry-After.
  • Use ETag / If-None-Match; do not poll.
  • Put a way to reach you in your User-Agent.
  • Creators are credited wherever a chart is shown. To have an entry removed, use the Contact page.