GitHub: kaysting/osu-score-cache
Website: osc.kaysting.dev
A JSON API and real-time WebSocket providing access to osu!'s recently submitted passing scores. oSC stores days of score history, letting your app sync back up after downtime with no hiccups.
This project was created primarily for use with osu!complete, but other devs might find it useful.
Please use this service for good, not evil. Do not attempt to circumvent rate limits or abuse the service in any other way. Please reach out to Kayla with any questions or concerns. Bad actors will be IP-banned.
This project mirrors and expands on osu!'s Get Scores API endpoint by:
- Providing a real-time websocket that broadcasts new scores, allowing clients to receive events passively instead of polling osu! servers.
- Exposing a deeper history (up to 30 days) as opposed to only a couple days.
- Allowing navigation forwards and backwards in recent score history using timestamps instead of opaque query strings.
- Allowing you to fetch recent scores in all modes or a specific mode with a single request.
- Allowing you to filter recent scores for specific players or beatmaps (or both).
While oSC does poll the osu! API's Get Scores endpoint every 5 seconds to save and broadcast new scores, it aims to prevent other devs from doing the same, thus lightening the load on osu!'s own infrastructure.
oSC is accessible as a real-time WebSocket or a traditional JSON API.
The WebSocket uses Socket.io, which supports all major languages.
The Socket.io server is exposed under https://osc.kaysting.dev/ws. You may need to specify the path (/ws) separately from the base URL (https://osc.kaysting.dev).
oSC only exposes a blanket scores event through the websocket, which broadcasts all new scores. If you need to filter recent scores by user/mode/map, use the JSON API documented below.
scores
Emits a scores event each time new passing scores in any mode are saved (roughly every 5 seconds). The event includes an array of Score objects from the osu! API.
This event does NOT send a backlog of scores on first connection. Use the JSON API documented below to get scores missed while offline.
updates
Emits an update event with the following object:
- integer
count: The number of new scores saved - integer
timestamp: The current millisecond-based Unix timestamp
The intent is that you use this event as a signal to fetch the data you need from the API, or perform some other action.
// Install and require the module
// On web, use the Socket.io CDN
const { io } = require('socket.io-client');
// Initialize client
const socket = io('https://osc.kaysting.dev', {
path: '/ws',
transports: ['websocket'] // avoid http polling
});
// Connect to socket
socket.on('connect', () => {
console.log(`Connected to osu! score cache!`);
// Subscribe to start receiving scores
socket.emit('subscribe', 'scores');
});
// Listen for scores
socket.on('scores', scores => {
// Do something with the new scores
// Score objects don't contain user/map metadata, so you'll have to request
// those details from the osu! API afterwards
for (const score of scores) {
console.log(
`User ${score.user_id} just got a ${(score.accuracy * 100).toFixed(2)}% ${score.rank} rank on map ${score.beatmap_id} in mode ${score.ruleset_id}`
);
}
});All API endpoints are exposed under the base URL:
https://osc.kaysting.dev/api
If self-hosting, use your hostname but keep the /api part.
Unsuccessful requests will be given the appropriate HTTP status code and served an error object:
- boolean
success:false, indicating that the request failed. - string
message: A human-readable error message.
GET /
Returns global stats and configuration for osu-score-cache. Try it!
Successful response
{
"success": true,
"oldest": 1778365140182,
"newest": 1778394368632,
"count": 291078,
"current_cached_days": 0.338,
"config": {
"max_list_length": 32,
"max_score_request_limit": 1000,
"score_cache_days": 30
}
}- integer
count: The total number of scores currently stored. - integer
oldest: A millisecond-based UNIX timestamp representing the time at which the first score currently stored was saved. - integer
newest: A millisecond-based UNIX timestamp representing the time at which the most recent score was saved. - float
current_cached_days: The number of days of recent scores currently stored. - object
config: Contains settings for this instance of oSC.- integer
max_list_length: The max number of users/maps that can be filtered in a single scores request. - integer
max_score_request_limit: The max number of scores that can be requested (via the limit query param) in a single score request. - integer
score_cache_days: The number of days of recent scores that are cached.
- integer
GET /scores
Returns recently submitted passing scores. Try it!
Query parameters
- string?
mode: An osu! game mode (ruleset) to fetch recent scores in. Defaults to all modes.
Valid modes:osuorstdfor osu!standardtaikofor osu!taikofruits,catch, orctbfor osu!catchmaniafor osu!mania
- integer?
limit: The number of scores to return, from1to1000. Defaults to100. - integer?
before: Return scores before this point. Accepts a millisecond-based UNIX timestamp. - integer?
after: Return scores after this point. Accepts a millisecond-based UNIX timestamp. - integer[]?
user: Return scores set by a specific user/users. Accepts between 1 and 32 comma-separated user IDs. - integer[]?
map: Return scores set on a specific beatmap/maps. Accepts between 1 and 32 comma-separated beatmap IDs.
Successful response
{
"success": true,
"meta": {
"oldest": 1778379338446,
"newest": 1778381392278,
"count": 12,
"mode": "all",
"users": [10109518],
"maps": [769831, 4667299, 4974900, 4900123, 5043509, 5063978]
},
"scores": [ ... ]
}- boolean
success:true, indicating the request was successful. - object
meta: Information about the request.- integer
count: The number of scores returned. - integer
newest: The millisecond-based UNIX timestamp of the newest score in this batch. Pass this as theafterquery param to get the next batch of scores. - integer
oldest: The millisecond-based UNIX timestamp of the oldest score in this batch. Pass this as thebeforequery param to get the previous batch of scores. - string
mode: The mode of this batch of scores. One ofosu,taiko,fruits,mania, orallif all modes are included. - integer[]
users: A list of user IDs who have a score in this batch. - integer[]
maps: A list of beatmap IDs that have a score in this batch.
- integer
- array
scores: A list of Score objects from the osu! API in order of oldest to newest.
In responses with no scores (out of range or no filtered matches), newest and oldest are set to the storage time of the newest and oldest scores currently in the database respectively.
The JSON API is strictly limited to 60 requests per minute and will return HTTP status 429 with an error object if exceeded.
The WebSocket currently has no rate limit but one may be added if we run into performance issues.
- The
scores_{mode}socket room has been removed. - The
/scoresAPI endpoint has changed:- The
beforeandafterparams now only accept millisecond-based UNIX timestamps. - The
{mode}path parameter has been removed and themodequery param has been added in its place. - The response no longer contains a
meta.cursorsobject. Usemeta.newestormeta.oldestfor pagination instead.
- The
- The database has been wiped, making scores cached before this point no longer accessible.
While discouraged, you can self-host an instance of osu-score-cache by following these steps:
- Ensure you have a recent version of Node.js installed
- Clone the repo and open it in your Terminal
- Run
npm install - Configure environment variables by creating
.envor directly in your environment:- Set
OSU_CLIENT_IDto an osu! application client ID - Set
OSU_CLIENT_SECRETto an osu! application client secret - Set
PORTto your desired webserver port or leave it blank to use8080 - See
.env.examplefor a comprehensive list of env vars
- Set
- Start the server with
npm start- Alternatively, start the server in the background by installing PM2 and running
pm2 start
- Alternatively, start the server in the background by installing PM2 and running
oSC uses a highly optimized SQLite database to store millions of scores in a small footprint, without the need for a full database server. Score data is Brotli-compressed before being stored to further conserve on storage space. Retrieving scores from the database is a sub-second operation despite millions of entries.
osu! receives roughly 1 million passing score submissions daily. After preliminary testing, the database file appears to grow by roughly 20 GB for every 30 days of scores it holds. Keep this in mind when increasing the cache time on your own instance.
The production instance of osu-score-cache is hosted on a Canada-based cloud server provided by OVHCloud, with Cloudflare as a proxy.
oSC makes no promise of data retention. This project is inherently only intended to be a data cache, not permanent storage. A best effort will be made to ensure the database isn't wiped, but there's always a possibility. Account for this while developing (perhaps by checking the oldest value returned by the stats endpoint).
Feel free to make small changes and submit pull requests, but if you'd like to make larger architectural changes, please reach out to [email protected] first.
Suspected vibe-coded contributions will be rejected without question. If you use AI to assist with writing code, please thoroughly review its output.
This codebase is 99.5% human-written and 100% human reviewed.
Google Gemini assisted in broad architectural research, database optimization, and documentation consistency, but contributed little to no code for this project.