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/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 |
part of the creator's name |
bpmMin, bpmMax |
BPM range overlapping the set's charts |
lengthMin, lengthMax |
length in seconds |
densityMin |
peak notes per second |
sort |
updated (default), title, artist, bpm, length, density |
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 three groups: chart lookups,
.roxfiles, the chartset list); beyond it the answer is429withRetry-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.