18-LAB / Documentation

HTTP API

Overlay/data base URL: http://127.0.0.1:24050 (SERVER_IP:SERVER_PORT). Dashboard/management base URL: http://127.0.0.1:24051 (DASHBOARD_IP:DASHBOARD_PORT), separate in vosu 1.1.0. Methods and paths are exact and case-sensitive. Unsupported combinations return 404, not 405; there are no implicit HEAD or OPTIONS handlers. WebSocket upgrades remain on the overlay origin; the dashboard refuses all upgrades. See the WebSocket reference.

All requests pass the configured trusted-peer policy; rejection is 403 Not Found. Dashboard mutations additionally require exact dashboard-origin provenance and a per-start X-Vosu-CSRF token injected only into dashboard pages. Origin is authoritative, or Referer only when Origin is absent; missing, duplicate, malformed, or opaque provenance otherwise returns 403. Overlay-origin browser management writes fail with 403; explicitly scoped API keys and the loopback native broker credential still authorize value/profile writes there. POST bodies are capped at 1 MiB; larger bodies return 413 {"error":"payload too large"}. CORS response headers do not bypass these checks. See security.

GET /api/dashboard on the overlay origin discovers the effective dashboard origin. Old overlay-origin GET links to /, /settings, and /available return 302, preserving path and query. Redirect authorities come from the effective listener, not arbitrary Host values; wildcard dashboard binds map to loopback for local discovery. Overlay URLs and their browser storage stay unchanged; no overlay files are served by the dashboard.

API keys

vosu only Added in vosu 1.1.0. Create a named key in Settings → API keys and copy its secret when shown; it cannot be recovered later. Native HTTP clients send Authorization: Bearer <key>.

Scope Authorized writes
settings:write Command-socket writes and POST /api/counters/settings/<name> value saves, not ?update=yes schema saves
profiles:manage All POST /api/counters/profiles actions

Neither scope grants updates, counter installation/deletion, reports, daemon settings, or key management; those remain dashboard-origin provenance plus CSRF only. Scoped key writes remain on the overlay origin. Invalid/revoked keys and insufficient scopes return 401, with JSON for HTTP POSTs. POSTs re-check the granted scope after receiving the body and before dispatch, so revocation during an upload prevents the pending mutation.

For a native value save, set VOSU_API_KEY to your saved settings:write secret:

curl -H "Authorization: Bearer $VOSU_API_KEY" -H 'Content-Type: application/json' \
  --data '[{"uniqueID":"title","value":"My overlay"}]' \
  http://127.0.0.1:24050/api/counters/settings/my-overlay

Keys never authenticate through URL query parameters. Cross-origin browser HTTP is not supported: CORS does not allow Authorization. For browsers, use the command WebSocket subprotocols. Remote peers must still be allowed by ALLOWED_IPS; a key never bypasses that policy. Plain HTTP/WS on a LAN exposes the secret to network observers: keep loopback by default and use a trusted TLS reverse proxy for remote access.

Keys persist across restarts, unlike the per-start loopback broker.json token. The file token provides zero-config native broker integration and authorizes command-socket writes, HTTP settings value saves, and profile actions from loopback. It never authorizes schema saves or management.

Dashboard-only key management

Both GET /api/keys and POST /api/keys live on the dashboard origin and require exact dashboard-origin provenance plus X-Vosu-CSRF; an API key cannot manage itself or other keys. GET returns {keys:[...]} metadata without secrets or hashes. POST accepts:

Action JSON body Result
Create {action:"create",name,scopes} {key,entry}; secret returned once
Rename {action:"rename",id,name} Changes the name, not scopes
Revoke {action:"revoke",id} Immediately closes associated command sockets with 1008, even while idle

Names are 1–64 printable characters excluding Unicode bidirectional controls. scopes contains one or both scope values above. To change permissions, revoke and recreate the key.

Sources: API key README and examples, ADR 0015.

State and calculation

Method + path Parameters 200 response Other statuses
GET /json None gosuMemory-compatible v1 object 500 readiness/serialization error
GET /json/sc None streamCompanion-compatible token object 500 readiness/serialization error
GET /json/v2 None v2 document 500 readiness/serialization error
GET /json/v2/precise None precise document 500 readiness/serialization error
GET /api/calculate/pp Optional query parameters below Legacy rosu-pp performance object 400 input/calculation error; 500 readiness/serialization error

Readiness errors are {"error":"osu is not ready/running"}. Calculation errors use {"error":"message"}. An explicit valid path can select a file without a current live beatmap.

Calculation query

Parameter Type / meaning
path Canonical regular .osu file to calculate; otherwise current map. vosu honors the supplied file; the upstream tosu implementation checks path but reads the current map.
lazer vosu only Boolean text true or false; score-system selection
mode Integer: 0 osu!, 1 taiko, 2 catch, 3 mania
ar, cs, hp, od Numeric difficulty overrides
clockRate Finite number in 0.01–100
passedObjects, combo, nMisses, n100, n300, n50, nGeki, nKatu Unsigned 32-bit integer counts
acc Numeric accuracy
sliderEndHits, smallTickHits, largeTickHits Unsigned 32-bit integer counts
hitresultPriority 0 best case (default), 1 worst case
mods Legacy unsigned bitmask; vosu also accepts JSON mod objects/arrays and two-letter acronyms vosu only

All parameters are optional. Unknown query keys are ignored. Counts accept integral numeric text (including exponent and hexadecimal/binary/octal notation), reject negatives/fractions/nonfinite values, and must fit u32. Acronym strings may contain whitespace or commas, for example HD,DT; structured mods contain acronym and optional settings.

Inputs are capped at 64 MiB, including opened-file checks and bounded reads. The live calculation facade additionally limits parsed objects to 250,000 with finite start times within ±10,000,000 ms; live graph output is capped at 1,000,000 aggregate bins. These do not constitute a global concurrent-memory ceiling.

The performance response contains difficulty, state, and numeric pp; optional numeric fields include ppAim, ppFlashlight, ppSpeed, ppAccuracy, ppDifficulty, effectiveMissCount, estimatedUnstableRate, speedDeviation, comboBasedEstimatedMissCount, scoreBasedEstimatedMissCount, aimEstimatedSliderBreaks, and speedEstimatedSliderBreaks. state contains numeric maxCombo, osuLargeTickHits, osuSmallTickHits, sliderEndHits, nGeki, nKatu, n300, n100, n50, and misses.

difficulty always contains numeric mode, stars, maxCombo, and boolean isConvert. Ruleset-dependent numeric fields are omitted when absent: aim, aimDifficultSliderCount, speed, flashlight, sliderFactor, aimTopWeightedSliderFactor, speedTopWeightedSliderFactor, speedNoteCount, aimDifficultStrainCount, speedDifficultStrainCount, nestedScorePerObject, legacyScoreBaseMultiplier, maximumLegacyComboScore, hp, nCircles, nSliders, nLargeTicks, nSpinners, stamina, rhythm, color, reading, nFruits, nDroplets, nTinyDroplets, nObjects, nHoldNotes, ar, preempt, greatHitWindow, okHitWindow, mehHitWindow, monoStaminaFactor, mechanicalDifficulty, and consistencyFactor.

Current game files

Method + path Resource / path base
GET /backgroundImage Selected map background (legacy alias)
GET /files/beatmap/background Selected map background
GET /files/beatmap/audio Selected map audio
GET /files/beatmap/file Selected .osu file
GET /Songs/{path} Relative to the current songs root
GET /files/beatmap/{path} Relative to the current songs root, not just the selected map folder
GET /files/skin/{path} Relative to the current stable skin root; lazer returns 500 {"error":"This endpoint is unavailable for the lazer client."}

Paths are percent-decoded once (+ stays literal). Absolute paths, parent traversal, NULs, malformed encoding, and escapes from the selected root return 404. Missing/unavailable game state returns 500 readiness; missing ready resources return 404. Successful files return bytes with a MIME Content-Type and Content-Length. Directory requests can return 200 HTML listings or 301 trailing-slash redirects.

File serving accepts a validated single Range: bytes=... request: 206 with Content-Range for a valid range; 416 with Content-Range: bytes */<length> for invalid/unsatisfiable ranges. Accepted forms are bytes=start-end, bytes=start-, and bytes=-suffixLength. Multiple ranges, embedded whitespace, zero suffixes, and explicit end positions beyond EOF are rejected (not clamped). Full responses are 200; media extensions mp3, wav, ogg, gif, webm, mp4, avi, and webp additionally advertise Accept-Ranges: bytes. Stream/seek failures can return 500.

Pages and static content

/, /settings, /available, and embedded /assets/{path} belong to the dashboard origin. Counter-static fallback belongs only to the overlay origin. Dashboard read APIs serve its pages, while read-only state, calculation, game files, settings/profiles/catalog APIs remain on the overlay origin as documented by ADR 0016.

Method + path Parameters / response
GET / Dashboard HTML
GET /settings Daemon settings HTML; a nonempty ?overlay=<name> selects the hosted counter-settings page
GET /available Third-party catalog HTML
GET /favicon.ico Icon bytes; adds X-Vosu-Identity: vosu vosu only
GET /assets/{path} Embedded dashboard assets, 200 bytes or 404
GET /{counter}/{path} (fallback) Counter/static-root files; /{counter}/ selects index.html

Dynamic page rendering can return 500 JSON errors. Counter fallback uses 200 bytes/HTML, 301 directory redirects, 404 missing/unsafe paths, and the file range behavior above. Successful full counter HTML responses append a script setting window.COUNTER_PATH to the logical parent directory. Use counter packages for its timing and settings contract. API and WebSocket reserved paths are not counter-static fallbacks.

Removed in-game page deprecated

GET /api/ingame is not served: the router returns 404. Dedicated __ingame__ storage and catalog commands were removed in ADR 0008. Do not depend on this tosu endpoint, including for your own settings/configuration. In-game rendering belongs to vosulay; use per-counter settings instead.

Counters and settings

The management write routes below live on the dashboard origin and require dashboard provenance plus X-Vosu-CSRF management. The overlay origin retains read-only settings/profile/catalog APIs and explicitly scoped API-key or loopback native broker value/profile writes, not schema edits or general management. They are not an overlay's general-purpose API. Most domain errors deliberately use HTTP 200 with an error field: check the body, not just response.ok.

Method + path Input Response / status
GET /api/counters/search/{query} Percent-encoded search text; source=installed (default) or source=available vosu only 200 card HTML; 400 Error: unknown search source; 502 escaped Error: HTML on lookup failure
POST /api/counters/download/{url} vosu only management Encoded HTTPS ZIP URL; required name single folder component; update=true permits replacement; body unused 200 {"status":"Finished"} or {"error":"..."}; 429 install capacity full
POST /api/counters/open/{name} vosu only management Encoded counter folder name; body unused 200 {"status":"opened"} or error
POST /api/counters/open-root vosu only management No parameters; body unused 200 {"status":"opened"} or error; opens configured static root
POST /api/counters/delete/{name} vosu only management Encoded counter folder name; body unused 200 {"status":"deleted"} or error
GET /api/counters/settings/{name} Encoded counter name 200 {"settings":[...],"values":{...}} or error
POST /api/counters/settings/{name} management JSON array of { "uniqueID": "id", "value": ... } 200 {"result":"success"} or error; debounced value persistence and settings refresh
POST /api/counters/settings/{name}?update=yes management JSON array of complete CounterSetting descriptors 200 {"result":"success"} or error; updates package schema
POST /api/settingsSave management JSON daemon configuration binding map 200 {"status":"updated"}; 500 JSON parse/save/validation error

Search retains letters, digits, and whitespace; any word can match name, author, or compatible clients. See package/settings descriptors for descriptor types and storage. Install admission is four shared concurrent jobs; HTTPS/private-destination/ZIP limits are documented there. Unlike tosu, download/open/delete are POST-only; their former GET forms return 404 with no redirect.

Daemon saves reject the whole request if interval/port values are nonfinite or outside: POLL_RATE 100–60000 ms, PRECISE_DATA_POLL_RATE 1–60000 ms, V2_SUBSCRIPTION_DEBOUNCE 1–60000 ms vosu only, and both SERVER_PORT and DASHBOARD_PORT integral 1–65535. Network fields require _network_action: "apply", followed by _network_action: "confirm" from the new dashboard origin within 30 seconds, or server-side rollback restores the previous network fields/listeners even after the browser closes. Only one edit can be pending; other settings autosave. See configuration.

Settings profiles

vosu only Added in vosu 1.1.0. Profiles group per-overlay values while package schemas stay shared.

Method + path Input Response
GET /api/counters/profiles None Initially {"active":"default","profiles":["default"]}
POST /api/counters/profiles management One JSON action below Updated registry on success; HTTP 200 {"error":"..."} on domain errors, including unknown actions
Action JSON body
Create empty profile {"action":"create","name":"stream"}
Create by copying {"action":"create","name":"stream","copyFrom":"default"}
Rename {"action":"rename","name":"stream","target":"live"}
Delete {"action":"delete","name":"live"}
Set as default {"action":"activate","name":"stream"}
Copy one overlay's values {"action":"copyOverlay","name":"counter-folder","source":"default","target":"stream"}

POST requires dashboard-origin provenance plus CSRF, an API key with profiles:manage on the overlay origin, or the loopback native broker credential on that origin, and uses the same body limits as other management writes. Credential authorization is rechecked after reading the body. Names are case-sensitive references but case-insensitively unique: 1–48 ASCII letters, digits, _ or -. At most 32 profiles including reserved default are allowed. default cannot be renamed/deleted; deleting the default profile selects default.

GET /api/counters/settings/<name>?profile=stream keeps the existing {"settings":[...],"values":{...}} response. POST to that URL accepts the usual [{"uniqueID":"field","value":...}] and returns {"result":"success"}. Omit profile to use the global active profile. Explicit unknown profiles return a JSON error, not a write to a fallback profile. update=yes still edits the shared developer schema for every profile.

default keeps <config>/settings/<counter>.settings.values.toml; other profiles use <config>/settings/profiles/<profile>/<counter>.settings.values.toml. <config>/settings/profiles.json stores the names and active selection; files are atomically replaced within their own directory. Missing overlay values use schema defaults, never another profile's saved values.

/settings?overlay=<name>&profile=<p> on the dashboard origin opens the inline overlay editor for a profile; without profile, it uses the default profile (API field active). It and the dashboard card editor share a profile dropdown and ⋯ menu with Create, Copy, Rename, Delete, and Set as default (API action activate), plus title/description/ID search and header navigation (absent for schemas with no headers). The dropdown adds (default) to the profile used by unpinned overlays, except the reserved default profile. Matching sections retain their headers.

All dashboard pages forbid framing with frame-ancestors 'none'; no query parameter enables it. Native editors such as vosulay use the settings and profiles HTTP API through their Rust host. Dashboard pages send no parent-window notifications.

For an individual overlay page, use ?vosuProfile=<name>, not the editor's profile query. The injected qualified COUNTER_PATH selects the profile in commands sockets. Activating profiles refreshes unpinned clients; saving/copying values refreshes clients pinned to that profile and unpinned clients when it is active.

Reports and updates

Both routes below are dashboard-origin management APIs, requiring its provenance and X-Vosu-CSRF. The UI labels report download Diagnostics report and uses an authenticated fetch.

Method + path Input Response
POST /api/generateReport vosu only management None; body unused 200 HTML attachment (diagnostic report); 500 JSON error
POST /api/runUpdates vosu only management None; body unused 200 JSON status: updated, latest, or noFiles; or error

Report generation is serialized (one active report). It escapes inserted machine/configuration/counter/log data. Logs: ten newest files, at most 1 MiB/10,000 content lines each, and 8 MiB/100,000 lines overall, with truncation notices. A report can contain private diagnostics; never generate or export one silently from an overlay.

Externally managed installs return a 200 JSON error instead of replacing the binary. Updated responses are flushed before the deferred replacement. These POST methods intentionally differ from tosu's GET routes.

Implementation sources

Edit this page on GitHub