Everything in Hi Beatz,
as clean JSON.
The full song catalog, cover art, leaderboards, league standings and player profiles. The catalog needs no account at all. Your own data needs one key, which you can create in about two minutes.
Start without an account
The catalog, the cover art, the ranked seasons and every image are open. Paste this into a terminal and you have data back before you finish reading this sentence.
# every EDM track between 120 and 140 BPM curl "https://api.beatz-nodefox.de/v1/songs?style=4&bpm_min=120&bpm_max=140"
{
"catalog_version": "0.2.4",
"total": 14,
"songs": [
{
"id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"style": { "id": 4, "name": "EDM", "color": "#B145FF" },
"colors": { "primary": "#8BECF5", "gradient": "#0B9ED7" },
"cover": "/v1/songs/10001/cover"
}
]
}
The game's own vocabulary
Styles, difficulty grades and league names come straight out of the game's translation table, so the API speaks the same language the app does.
Timing windows per difficulty
How many milliseconds early or late a tap may be and still count as Perfect.
Available at /v1/levels.
| Level | Name | Perfect | Great | |
|---|---|---|---|---|
| 1 | Standard | ±140 ms | ±190 ms | |
| 2 | Gold | ±120 ms | ±160 ms | |
| 3 | Diamond | ±100 ms | ±130 ms | |
| 4 | Diamond | ±70 ms | ±100 ms |
Items and currencies
Wherever the game hands out an item it sends only a config ID. Every place the API reports one, it adds the same three fields, so you never have to keep a table of your own.
| Field | Meaning |
|---|---|
cfg_id | The game's own number, e.g. 110100002 |
key | Stable machine name, e.g. lottery_coin. Safe to switch on in code |
name | What the game calls it on screen, e.g. Lucky Ticket |
icon | URL of the icon, or null. Only set when the image really exists, so it never 404s |
Tasks
The game server sends a task as a number and a counter, nothing else. The API looks the number up and adds what the task actually asks for, so a task reads on its own:
{
"task_id": 6,
"progress": 3,
"state": { "id": 0, "name": "running" },
"name": "Spin 10 Times",
"target": 10,
"rewards": [
{ "cfg_id": 110500001, "key": "star", "name": "Star", "count": 1 },
{ "cfg_id": 110300002, "key": "wish_box_fragment", "name": "Wish Fragments", "count": 10 }
]
}
A task number the lookup does not know keeps its ID and reports name: null
rather than disappearing, so a game update never breaks a response.
A reward is not always an item
The game pays out three different kinds of thing through the same pairs of numbers, and
every reward list in this API tells you which kind you are looking at through
kind:
| kind | Example | What you get |
|---|---|---|
item | 110100001 | A currency, box or fragment. key, name and the item icon |
song | 120110022 | A song. The title, the artist, its cover and song_id for the catalog |
decoration | 100300009 | An avatar or a banner. Its type and the image |
Two numbers for one chance
/v1/odds reports the prize wheel with two figures per grade, because the
game config carries two and they do not describe the same distribution.
| Grade | Weight | As a share | Shown in the app |
|---|---|---|---|
| 1 · rainbow | 1 / 10000 | 0.01 % | 0.01 % |
| 2 · MYTHIC | 50 / 10000 | 0.5 % | 1.5 % |
| 3 · SPECIAL | 300 / 10000 | 3 % | 5 % |
| 4 · RARE | 3100 / 10000 | 31 % | 30 % |
| 5 · COMMON | 6549 / 10000 | 65.49 % | 63.49 % |
weight and displayed_probability_percent and does not pick
one, because which of the two the draw actually uses cannot be read off the config
alone. The shop vault has the same character: its four shown probabilities add up to
101 %, which the response states as displayed_total_percent.
How tight was that timing
Every placing in a ranked match carries a tolerance. The game calls the
field tolerancePercent and sends it as a whole number, which reads oddly
at first: on a board where all five leaders hit the 100000 cap it says 2000 and 1000.
The game computes it like this:
round(offset / perfectPlusMs * 10000) / 100
So it is a percentage of the Perfect+ window of that match's song level, carried
over the wire scaled by 100. On a Diamond level song the window is 50 ms, so 2000
means 20 % of it, about 10 ms off. The response gives you both:
tolerance as the game sends it and tolerance_percent as the
number that reading gives. The windows themselves are at /v1/levels.
Fields reported without a reading
A few numbers come out of the game with a name that does not match how they behave. Rather than guess, this API keeps the game's own name and leaves the meaning open:
| Field | Where | Why it is left alone |
|---|---|---|
buff | /v1/tags | Three numbers per tag, [15, 10, 5]. The config never says what separates them |
original_count | /v1/shop | Runs 20 to 100 while the diamond price runs 5 to 2500, so it is neither a former price nor an amount |
dummy_size | matchmaking rules | Falls from 11 to 0 of a 15 place board across the twelve rule sets. The config does not say what a dummy place is |
How hard is a song, really
Every song ships with a note chart per difficulty. The API reads those and reports the numbers: how many taps, the maximum combo, how long the chart runs, and how the taps spread across the four lanes. That makes difficulty comparable instead of a matter of opinion.
curl "https://api.beatz-nodefox.de/v1/songs/10001/chart"
{
"song_id": 10001, "name": "Titanium", "bpm": 126,
"levels": [
{
"level": { "id": 1, "name": "Standard", "perfect_ms": 140 },
"taps": 245, "combo": 357, "taps_per_second": 2.56,
"duration_ms": 95714,
"lanes": { "1": 78, "2": 44, "3": 57, "4": 66 }
}
]
}
The five hardest songs in the game
Sorted by taps per second on the hardest difficulty, via
/v1/songs?sort=density.
| Song | Style | BPM | Taps / second | |
|---|---|---|---|---|
| GDFR | Hip Hop | 146 | 8.61 | |
| Bangarang | EDM | 220 | 7.49 | |
| Know Your Enemy | Rock | 130 | 6.83 | |
| Spitfire | EDM | 157 | 6.57 | |
| Whistle | Hip Hop | 104 | 6.56 |
Get your API key
Reaching your own profile, scores and league standing needs an API key, and
that key comes from your game session. Two values live on your phone:
your gamer_id and a token. You send them once and
get a key back. No root, no modified app, and nothing is installed on the phone.
-
Turn on Developer options
On your Android phone open Settings → About phone and tap Build number seven times. A message confirms that developer options are now available. On Samsung devices the entry sits under Settings → About phone → Software information.
-
Turn on USB debugging
Go to Settings → System → Developer options and switch on USB debugging. Connect the phone to your computer with a cable and confirm the dialog that asks whether you trust this computer. Tick Always allow so it stops asking.
-
Install the Android platform tools
You only need
adb. The script never downloads it, so it has to be on the machine already. One command, depending on your system:# macOS brew install --cask android-platform-tools # Windows winget install Google.PlatformTools # Debian / Ubuntu sudo apt install android-sdk-platform-tools
If you already have Android Studio, adb is on your disk and the script finds it by itself. And if you would rather not install anything: download the platform tools zip, unpack it, and drop
hb_token.pyinto that folder next toadb. It picks up the neighbour without any arguments.# check that the phone is visible adb devices # expected output List of devices attached R58MB0EXAMPLE device
If it says
unauthorized, look at the phone screen and confirm the dialog. If the list is empty, try a different cable; some charging cables carry no data lines. -
Read the two values
The script reads the game's save file from
Android/data, where adb is allowed to look without root, and pullsgamer_idandtokenout of it. It only reads; nothing on the phone is changed.Log into the game first and let it reach the main menu, otherwise the save folder for your account does not exist yet.
python3 hb_token.py
Hi Beatz - token reader device R58MB0EXAMPLE gamer_id 100000001 token 481625107 # paste this to create your key: curl -X POST https://api.beatz-nodefox.de/v1/auth \ -H 'Content-Type: application/json' \ -d '{"gamer_id": 100000001, "token": 481625107}'
Newer Android versions lock app folders down harder, so the automatic search can come up empty. Your gamer ID is printed on your profile in the game: pass it in and the script knows exactly what to look for.
python3 hb_token.py --gamer-id 100000001 -
Trade the token for a key
Run that command. The API checks the token against the game server and hands back a key that is yours to keep.
{ "api_key": "b7f3c1a94e2d05f8ab61c7d3e90f24b5", "gamer_id": 100000001, "usage": "Send this key as the X-Api-Key header on every following request." }curl https://api.beatz-nodefox.de/v1/user \ -H 'X-Api-Key: b7f3c1a94e2d05f8ab61c7d3e90f24b5'
/Android/data: most allow it,
some manufacturers lock it down. If yours does, the script says so and walks you
through copying the file across on the phone instead, then reading it with
--file.
/v1/auth again with a fresh token
when that happens. Your API key stays the same. If a key ever leaks, call
/v1/auth/refresh with your current key and a valid token: you get
a new key and the old one stops working immediately.
The token reader
One file, no dependencies beyond the Python standard library. It talks to
your phone through adb, reads the save file, finds the two
values and prints the command. It never writes to the phone.
Read the source
#!/usr/bin/env python3
"""
hb_token.py - reads your Hi Beatz gamer ID and session token from a phone
connected over USB, and prints the command that turns them into an API key.
python3 hb_token.py
The script only reads. Nothing is written to the phone, nothing is
installed, and the game is not modified. No root required.
What it needs
Python 3.8 or newer
adb from Google's Android platform tools, on your PATH
A phone with USB debugging enabled and this computer authorised
Useful options
--gamer-id <n> your ID from the game profile; makes the search exact
--serial <id> pick a device when more than one is attached
--adb <path> adb is not on your PATH
--file <path> parse a save file you already have, skip the phone
--describe describe the save format without writing anything
--json machine readable output
--verbose show every extraction attempt
Nothing is written to disk. The save file is held in memory, read, and
dropped when the script exits.
adb is not downloaded either. It has to be installed already; the script
looks on your PATH and in the usual Android Studio locations.
"""
from __future__ import annotations
import argparse
import json
import re
import shutil
import struct
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path
PACKAGE = "com.funtapx.magic.piano.rhythm.tiles.music.game"
SAVE_NAME = "GlobalData.bytes"
API_BASE = "https://api.beatz-nodefox.de"
# The game keeps one save folder per account, named after the gamer ID,
# under shared storage. adb may read there without root, which is what
# makes this work on a normal phone.
SAVE_ROOT = f"/sdcard/Android/data/{PACKAGE}/files/Save"
# Tail of GlobalData.bytes:
# <int32 playerId> <len> token <len> version
# Token and version are length prefixed ASCII, the ID is a plain int32.
SAVE_TAIL = re.compile(
rb"(.{4})[\x01-\x40]([0-9]{4,12})[\x01-\x20](\d+(?:\.\d+){1,3})\s*$", re.S
)
# Game IDs and tokens observed so far are 8 to 10 digits. Anything outside
# that range is almost certainly a coincidence in the binary.
PLAUSIBLE = range(10_000_000, 4_000_000_000)
# ──────────────────────────────────────────────────────────────── output
class Out:
"""Small console helper. Colours are dropped when piped to a file."""
def __init__(self, colour: bool, verbose: bool, quiet: bool):
self.colour = colour
self.verbose = verbose
self.quiet = quiet
def _c(self, code: str, text: str) -> str:
return f"\033[{code}m{text}\033[0m" if self.colour else text
def head(self, text: str) -> None:
if not self.quiet:
print(self._c("1", text))
def info(self, label: str, value: str) -> None:
if not self.quiet:
print(f"{label:<12}{value}")
def step(self, text: str) -> None:
if self.verbose and not self.quiet:
print(self._c("2", f" · {text}"))
def warn(self, text: str) -> None:
print(self._c("33", f"warning: {text}"), file=sys.stderr)
def fail(self, text: str, *hints: str) -> None:
print(self._c("31", f"error: {text}"), file=sys.stderr)
for h in hints:
print(f" {h}", file=sys.stderr)
sys.exit(1)
# ──────────────────────────────────────────────────────────────── adb
@dataclass
class Adb:
binary: str
serial: str | None = None
out: Out = field(default=None, repr=False)
def run(self, *args: str, timeout: int = 30) -> subprocess.CompletedProcess:
cmd = [self.binary]
if self.serial:
cmd += ["-s", self.serial]
cmd += list(args)
self.out.step("adb " + " ".join(args))
return subprocess.run(cmd, capture_output=True, timeout=timeout)
def devices(self) -> list[tuple[str, str]]:
"""[(serial, state)] for everything adb currently sees."""
proc = subprocess.run([self.binary, "devices"], capture_output=True, timeout=20)
found = []
for line in proc.stdout.decode("utf-8", "replace").splitlines()[1:]:
parts = line.split()
if len(parts) >= 2:
found.append((parts[0], parts[1]))
return found
# Where to look for adb. Two situations cover almost everyone: it is
# installed and on PATH, or the platform tools were unpacked somewhere and
# this script was dropped next to adb. Both work without any arguments.
def adb_search_paths() -> list[Path]:
home = Path.home()
exe = "adb.exe" if sys.platform == "win32" else "adb"
roots = [
Path(__file__).resolve().parent, # script dropped into platform-tools
Path.cwd(), # or run from that folder
home / "Android/Sdk/platform-tools",
home / "Library/Android/sdk/platform-tools", # macOS, Android Studio
home / "AppData/Local/Android/Sdk/platform-tools", # Windows, Android Studio
Path("/usr/lib/android-sdk/platform-tools"), # Debian, Ubuntu
Path("/opt/android-sdk/platform-tools"),
Path("/usr/local/share/android-sdk/platform-tools"),
Path("C:/Android/platform-tools"),
Path("C:/platform-tools"),
Path(__file__).resolve().parent / "platform-tools",
Path.cwd() / "platform-tools",
]
return [r / exe for r in roots]
def install_hint() -> list[str]:
"""The one command that installs adb on the system we are running on."""
if sys.platform == "darwin":
return ["Install it with Homebrew:",
" brew install --cask android-platform-tools"]
if sys.platform == "win32":
return ["Install it with winget:",
" winget install Google.PlatformTools",
"or unzip the platform tools and run this script from that folder."]
return ["Install it from your package manager:",
" sudo apt install android-sdk-platform-tools (Debian, Ubuntu)",
" sudo dnf install android-tools (Fedora)",
" sudo pacman -S android-tools (Arch)"]
def locate_adb(explicit: str | None, out: Out) -> str:
if explicit:
if Path(explicit).is_file():
return explicit
out.fail(f"no adb at {explicit}")
found = shutil.which("adb")
if found:
out.step(f"adb found on PATH: {found}")
return found
for candidate in adb_search_paths():
if candidate.is_file():
out.step(f"adb found at {candidate}")
return str(candidate)
out.fail(
"adb not found",
"This script does not download anything; adb has to be on the machine",
"already. It ships in Google's Android platform tools.",
"",
*install_hint(),
"",
"Or download the zip, unpack it, and point the script at it:",
" https://developer.android.com/tools/releases/platform-tools",
" python3 hb_token.py --adb /path/to/platform-tools/adb",
)
def pick_device(adb: Adb, out: Out) -> str:
try:
devices = adb.devices()
except FileNotFoundError:
out.fail(f"could not run {adb.binary}")
except subprocess.TimeoutExpired:
out.fail("adb did not respond", "Unplug the cable, plug it back in and retry.")
ready = [s for s, state in devices if state == "device"]
waiting = [s for s, state in devices if state == "unauthorized"]
if waiting and not ready:
out.fail(
"the phone has not authorised this computer",
"Look at the phone screen and confirm the USB debugging dialog.",
"Tick 'Always allow from this computer' so it stops asking.",
)
if not ready:
out.fail(
"no phone found",
"Check that USB debugging is on under Developer options.",
"Try another cable; some charging cables carry no data lines.",
"Then run: adb devices",
)
if len(ready) > 1 and not adb.serial:
out.fail(
f"{len(ready)} devices attached",
"Pick one with --serial, for example:",
f" python3 hb_token.py --serial {ready[0]}",
)
return adb.serial or ready[0]
# ────────────────────────────────────────────────────────── extraction
def list_accounts(adb: Adb, out: Out) -> tuple[list[str], str]:
"""
Save folder names. Each is a gamer ID; "0" is the state before login.
Returns the folders and why the list is empty when it is, because the
two reasons need opposite advice: a blocked folder is a phone problem,
a missing folder means the game was never logged into here.
"""
try:
proc = adb.run("shell", f"ls {SAVE_ROOT}")
except subprocess.TimeoutExpired:
return [], "timeout"
text = (proc.stdout + proc.stderr).decode("utf-8", "replace")
if "Permission denied" in text:
return [], "denied"
if "No such file" in text:
return [], "missing"
names = [n.strip() for n in text.split() if n.strip()]
accounts = sorted((n for n in names if n.isdigit() and n != "0"), reverse=True)
if "0" in names:
accounts.append("0") # try the pre-login state last
return accounts, ("empty" if not accounts else "ok")
def read_save(adb: Adb, out: Out) -> tuple[bytes, str | None]:
"""
Reads the save file over adb.
Returns the file contents and, when it could be determined, the gamer ID
taken from the folder name. exec-out is used rather than shell so the
bytes arrive unaltered on Windows.
"""
accounts, why = list_accounts(adb, out)
if why == "denied":
out.fail(
"this phone does not let adb read the app folder",
f"Blocked: {SAVE_ROOT}",
"",
"Some manufacturers lock /Android/data down beyond what stock",
"Android does. The file is still there, it just cannot be reached",
"this way. Copy it across on the phone itself and pass it in:",
"",
" 1. Open the phone's file manager",
f" 2. Go to Android/data/{PACKAGE}/files/Save",
" 3. Open the folder named after your gamer ID",
" 4. Copy GlobalData.bytes to Downloads",
" 5. adb pull /sdcard/Download/GlobalData.bytes",
" python3 hb_token.py --file GlobalData.bytes",
)
if why in ("missing", "empty"):
out.fail(
"no save folder for any account",
f"Looked in {SAVE_ROOT}",
"",
"Log into the game and let it reach the main menu, then run this",
"again. The folder is created on first login.",
"",
"To check by hand:",
f" adb shell ls {SAVE_ROOT}",
)
if not accounts:
out.fail("could not list the save folder", f"Looked in {SAVE_ROOT}")
out.step(f"save folders: {', '.join(accounts)}")
problems = []
for account in accounts:
path = f"{SAVE_ROOT}/{account}/{SAVE_NAME}"
out.step(f"reading {path}")
try:
proc = adb.run("exec-out", f"cat {path}", timeout=60)
except subprocess.TimeoutExpired:
problems.append(f"{account}: timed out")
continue
data = proc.stdout
if len(data) > 32:
out.step(f"got {len(data)} bytes from account {account}")
return data, (account if account != "0" else None)
reason = (proc.stderr or data).decode("utf-8", "replace").strip()
problems.append(f"{account}: {reason[:100] or 'empty file'}")
out.fail(
"save folders exist but none of them could be read",
*[f"- {p}" for p in problems],
)
# ─────────────────────────────────────────────────────────────── parsing
@dataclass
class Credentials:
gamer_id: int
token: int
how: str
def parse_save_tail(data: bytes, out: Out) -> Credentials | None:
"""
The normal case: the game writes ID, token and version at the very end
of the save file, so match there first.
"""
m = SAVE_TAIL.search(data)
if not m:
return None
(gamer_id,) = struct.unpack("<i", m.group(1))
token = int(m.group(2))
version = m.group(3).decode("ascii", "ignore")
out.step(f"save tail matched, game version {version}")
if gamer_id > 0 and token in PLAUSIBLE:
return Credentials(gamer_id, token, f"end of the save file (game {version})")
return None
def parse_text(data: bytes, out: Out) -> Credentials | None:
"""
Unencrypted saves keep their fields as readable text. Look for the
field names the game uses, in any of the spellings seen so far.
"""
text = data.decode("utf-8", "ignore")
id_names = ("gamerId", "gamer_id", "playerId", "player_id", "uid")
tk_names = ("token", "sessionId", "session_id", "loginToken")
def grab(names: tuple[str, ...]) -> int | None:
for name in names:
m = re.search(rf'"{name}"\s*[:=]\s*"?(\d{{6,12}})"?', text)
if m:
value = int(m.group(1))
if value in PLAUSIBLE:
out.step(f"text match: {name} = {value}")
return value
return None
gamer_id = grab(id_names)
token = grab(tk_names)
if gamer_id and token:
return Credentials(gamer_id, token, "field names in the save file")
return None
def find_anchored(data: bytes, gamer_id: int, out: Out) -> Credentials | None:
"""
The reliable route: you read your own ID off your game profile and
pass it in. We locate it in the file and take the 32 bit value next
to it as the token, which is how the game stores the pair.
"""
needle = struct.pack("<I", gamer_id)
at = data.find(needle)
while at != -1:
for label, off in (("after", at + 4), ("before", at - 4)):
if 0 <= off <= len(data) - 4:
(value,) = struct.unpack_from("<I", data, off)
if value in PLAUSIBLE and value != gamer_id:
out.step(f"anchor at 0x{at:x}, token {label} it: {value}")
return Credentials(
gamer_id, value,
f"gamer ID you supplied, token stored {label} it at 0x{off:x}",
)
at = data.find(needle, at + 1)
# Text form of the same idea, for readable saves.
m = re.search(rf"{gamer_id}\D{{1,20}}?(\d{{6,12}})", data.decode("utf-8", "ignore"))
if m and int(m.group(1)) in PLAUSIBLE:
return Credentials(gamer_id, int(m.group(1)),
"gamer ID you supplied, token found next to it as text")
return None
def parse_binary(data: bytes, out: Out) -> Credentials | None:
"""
Last resort without an anchor: look for two plausible 32 bit values
side by side, on aligned offsets only. Save files are not random, so
a single hit is trustworthy; several hits are not, and we say so
instead of picking one.
"""
hits: list[tuple[int, int, int]] = []
for off in range(0, len(data) - 8, 4):
a, b = struct.unpack_from("<II", data, off)
if a in PLAUSIBLE and b in PLAUSIBLE and a != b:
hits.append((off, a, b))
for off, a, b in hits[:12]:
out.step(f"binary candidate at 0x{off:x}: {a}, {b}")
if len(hits) == 1:
off, a, b = hits[0]
return Credentials(a, b, f"adjacent 32 bit values at offset 0x{off:x}")
if len(hits) > 1:
out.warn(f"{len(hits)} possible pairs; too ambiguous to choose one.")
return None
def parse(data: bytes, gamer_id: int | None, out: Out) -> Credentials:
if gamer_id is not None:
found = find_anchored(data, gamer_id, out)
if found:
return found
out.fail(
f"gamer ID {gamer_id} does not appear in the save file",
"Check the number on your game profile, or drop --gamer-id to",
"let the script search on its own.",
)
for strategy in (parse_save_tail, parse_text, parse_binary):
found = strategy(data, out)
if found:
return found
out.fail(
"the save file was read but the values could not be identified",
f"file size: {len(data)} bytes",
"",
"Fastest fix: your gamer ID is shown on your profile in the game.",
"Pass it in and the token is found next to it:",
" python3 hb_token.py --gamer-id 100000001",
"",
"If that also fails, the save format has changed. Run",
" python3 hb_token.py --describe",
"and report that description; it contains no token.",
)
# ───────────────────────────────────────────────────────────── diagnosis
def describe(data: bytes, out: Out) -> None:
"""
Prints the shape of the save file so an unrecognised format can be
reported without sending the file itself, which holds a live token.
"""
printable = sum(1 for b in data[:4096] if 32 <= b < 127 or b in (9, 10, 13))
ratio = printable / min(len(data), 4096) if data else 0
print()
print("save file description")
print(f" size {len(data)} bytes")
print(f" first bytes {data[:16].hex(' ')}")
print(f" looks like {'text' if ratio > 0.85 else 'binary'} "
f"({ratio:.0%} printable)")
for marker, what in ((b"ES3", "Easy Save 3"), (b"{", "JSON"), (b"PK", "zip")):
if data[:8].lstrip().startswith(marker):
print(f" format hint {what}")
break
keys = sorted(set(re.findall(rb'"([A-Za-z_][A-Za-z0-9_]{2,24})"\s*[:=]', data[:65536])))
if keys:
names = ", ".join(k.decode("ascii", "ignore") for k in keys[:25])
print(f" field names {names}")
print()
print(" Paste this description when reporting an unrecognised format.")
print(" It contains no token and no personal data.")
print()
# ────────────────────────────────────────────────────────────────── main
def main() -> None:
ap = argparse.ArgumentParser(
prog="hb_token.py",
description="Read your Hi Beatz gamer ID and session token over USB.",
)
ap.add_argument("--serial", help="device serial, when several are attached")
ap.add_argument("--adb", help="path to adb, if it is not on your PATH")
ap.add_argument("--file", help="parse an existing save file instead of the phone")
ap.add_argument("--describe", action="store_true",
help="describe the save file without writing anything anywhere")
ap.add_argument("--gamer-id", type=int, metavar="N",
help="your ID from the game profile; makes the search exact")
ap.add_argument("--json", action="store_true", help="print JSON instead of text")
ap.add_argument("--verbose", action="store_true", help="show every attempt")
args = ap.parse_args()
out = Out(
colour=sys.stdout.isatty() and not args.json,
verbose=args.verbose,
quiet=args.json,
)
out.head("Hi Beatz - token reader")
# ── get the save file ────────────────────────────────────────────
folder_id: int | None = None
if args.file:
try:
with open(args.file, "rb") as fh:
data = fh.read()
except OSError as exc:
out.fail(f"could not read {args.file}: {exc}")
out.info("source", args.file)
else:
adb_path = locate_adb(args.adb, out)
adb = Adb(adb_path, args.serial, out)
serial = pick_device(adb, out)
adb.serial = serial
out.info("device", serial)
data, from_folder = read_save(adb, out)
# The folder is named after the account, so it doubles as a check
# and as a fallback if the file itself will not give up the ID.
if from_folder and args.gamer_id is None:
args.gamer_id = None # let the parser try on its own first
folder_id = int(from_folder)
else:
folder_id = None
if args.describe:
describe(data, out)
# ── find the values ──────────────────────────────────────────────
creds = parse(data, args.gamer_id or folder_id, out)
if args.json:
print(json.dumps({
"gamer_id": creds.gamer_id,
"token": creds.token,
"found_by": creds.how,
}, indent=2))
return
out.info("gamer_id", str(creds.gamer_id))
out.info("token", str(creds.token))
if args.verbose:
out.info("found by", creds.how)
body = json.dumps({"gamer_id": creds.gamer_id, "token": creds.token})
print()
print("# paste this to create your key:")
print(f"curl -X POST {API_BASE}/v1/auth \\")
print(" -H 'Content-Type: application/json' \\")
print(f" -d '{body}'")
print()
print("# the token expires the next time you log in on your phone.")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(130)
Endpoint reference
All 61 endpoints, one line each. Open a row for the description, its parameters, the command that calls it and a real answer recorded against the live API.
Getting started
No key required 3One call turns a game token into an API key. The key never expires on its own; posting the same gamer ID again refreshes the game token behind it and hands back the key you already have.
POST
/v1/auth
Trade a game token for an API key
No key
Exchange a gamer ID and game token for an API key. Both values come from the login helper further up this page. Call it again with a fresh token whenever the game server stops accepting the old one; the API key itself stays the same.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
gamer_id | int | required | Your numeric player ID |
token | int | required | The game token from the login helper |
curl -X POST "https://api.beatz-nodefox.de/v1/auth" \ -H "Content-Type: application/json" \ -d '{"gamer_id": 100000001, "token": 700000001}'
First call
{
"api_key": "3f7c1a90b4e25d68af03c1e97b5d24aa",
"gamer_id": 100000001,
"usage": "Send this key as the X-Api-Key header on every following request.",
"note": "Existing API key returned, game token updated."
}
Called again with a new token
{
"api_key": "3f7c1a90b4e25d68af03c1e97b5d24aa",
"gamer_id": 100000001,
"usage": "Send this key as the X-Api-Key header on every following request.",
"note": "Existing API key returned, game token updated."
}
Empty body
{
"error": {
"code": "bad_request",
"message": "gamer_id and token required, as a positive number. Both values sit together in GlobalData.bytes on the device."
}
}
GET
/v1
Name, version and where to authenticate
No key
Name, version, where the documentation lives and how to authenticate. The endpoints themselves are not listed here: that list is this page, and keeping a second copy of it inside the API only meant two things to update.
curl "https://api.beatz-nodefox.de/v1"
Version root
{
"name": "Hi Beatz API",
"version": "1.0",
"docs": "https://beatz-nodefox.de",
"auth": "POST /v1/auth with gamer_id and token, then use the X-Api-Key header"
}
GET
/v1/status
Maintenance flag and notice, no key needed
No key
Whether the game server is in maintenance and what notice it is showing. This is the only game backed endpoint that needs no key, so it works as a health check before you authenticate.
curl "https://api.beatz-nodefox.de/v1/status"
Live status
{
"maintenance": false,
"notice": null,
"checked_at": "2026-09-06T02:16:29Z"
}
Catalog
No key required 11The song catalog, its charts and the vocabulary the rest of the API uses. All of it is read from the game's own config files, so these answers are the same for everybody and need no key.
GET
/v1/meta
Catalog summary and version
No key
Catalog summary in one object: how many songs exist, how many are visible in the client, the BPM range, how many carry a release date and how many sit in the Hi-Fi Vault. catalog_version is the game's own package version for its content and changes with every update, which makes this a cheap way to notice one. game_version is something else: the client version this API identifies itself as when it talks to the game server, and it does not move with the content.
curl "https://api.beatz-nodefox.de/v1/meta"
Summary
{
"catalog_version": "Android22-20260928-1535",
"songs_total": 168,
"songs_visible": 155,
"songs_audit": 15,
"duplicates": 10,
"styles": 5,
"bpm_min": 66,
"bpm_max": 220,
"songs_dated": 53,
"newest_release": "2026-11-23T04:59:59Z",
"songs_in_vault": 72,
"charts": {
"songs": 168,
"density": {
"min": 1.43,
"max": 8.61
}
},
"note": "Metadata and cover art only. Audio files are not served.",
"game_version": "0.2.4"
}
GET
/v1/songs
Song list with filters, sorting and paging
No key
The song list. Without limit it returns the whole catalog in one answer, which is small enough to cache locally. Filters combine, so you can ask for slow EDM tracks of the 2020s in a single call.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
q | string | — | Substring of the title or the artist, case insensitive |
style | int | — | Music style ID, see /v1/styles |
bpm_min | int | — | Lowest BPM to include |
bpm_max | int | — | Highest BPM to include |
visible | bool | — | Only songs the client actually shows |
tag | int | — | Ranked tag ID, see /v1/tags |
vault | bool | — | Only songs obtainable from the Hi-Fi Vault |
released | bool | — | true for songs that carry a release date, false for those without one |
sort | string | id | id, name, artist, bpm, density, duration or released |
limit | int | — | Page size. Omit it to get everything |
offset | int | 0 | How many songs to skip |
curl "https://api.beatz-nodefox.de/v1/songs?limit=3"
Three songs
{
"sort": "id",
"total": 168,
"count": 3,
"offset": 0,
"limit": 3,
"songs": [
{
"id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#8BECF5",
"gradient": "#0B9ED7"
},
"cover": "/v1/songs/10001/cover",
"audit_song_id": 90002,
"duplicate_of": null,
"chart": {
"duration_ms": 95714,
"levels": 4,
"peak_density": 3.69
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10002,
"name": "Gangnam Style",
"artist": "PSY",
"bpm": 132,
"style": {
"id": 0,
"name": "Pop",
"color": "#FF63A1"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#57C682",
"gradient": "#16712D"
},
"cover": "/v1/songs/10002/cover",
"audit_song_id": 90001,
"duplicate_of": null,
"chart": {
"duration_ms": 101818,
"levels": 4,
"peak_density": 5.15
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10003,
"name": "Wake Me Up",
"artist": "Avicii ft. Aloe Blacc",
"bpm": 121,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#9AE469",
"gradient": "#2B9401"
},
"cover": "/v1/songs/10003/cover",
"audit_song_id": 90014,
"duplicate_of": null,
"chart": {
"duration_ms": 120000,
"levels": 4,
"peak_density": 4.42
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
}
]
}
Search by artist
{
"sort": "id",
"total": 11,
"count": 11,
"offset": 0,
"limit": null,
"songs": [
{
"id": 10007,
"name": "Leave the Door Open",
"artist": "Bruno Mars, Anderson .Paak, Silk Sonic",
"bpm": 74,
"style": {
"id": 3,
"name": "R&B",
"color": "#2CD3FF"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#E1AB73",
"gradient": "#6B2A17"
},
"cover": "/v1/songs/10007/cover",
"audit_song_id": 90010,
"duplicate_of": null,
"chart": {
"duration_ms": 134595,
"levels": 4,
"peak_density": 3.41
},
"released_at": null,
"tags": [
{
"tag_id": 50006,
"name": "2020s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10012,
"name": "Talking to the Moon",
"artist": "Bruno Mars",
"bpm": 73,
"style": {
"id": 3,
"name": "R&B",
"color": "#2CD3FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#F6E271",
"gradient": "#D09006"
},
"cover": "/v1/songs/10012/cover",
"audit_song_id": 90005,
"duplicate_of": null,
"chart": {
"duration_ms": 127397,
"levels": 4,
"peak_density": 3.23
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
},
"... 9 more entries of the same shape"
]
}
Style, BPM range and visibility
{
"sort": "id",
"total": 15,
"count": 15,
"offset": 0,
"limit": null,
"songs": [
{
"id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#8BECF5",
"gradient": "#0B9ED7"
},
"cover": "/v1/songs/10001/cover",
"audit_song_id": 90002,
"duplicate_of": null,
"chart": {
"duration_ms": 95714,
"levels": 4,
"peak_density": 3.69
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10003,
"name": "Wake Me Up",
"artist": "Avicii ft. Aloe Blacc",
"bpm": 121,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#9AE469",
"gradient": "#2B9401"
},
"cover": "/v1/songs/10003/cover",
"audit_song_id": 90014,
"duplicate_of": null,
"chart": {
"duration_ms": 120000,
"levels": 4,
"peak_density": 4.42
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
},
"... 13 more entries of the same shape"
]
}
Sorted by note density
{
"sort": "density",
"total": 168,
"count": 2,
"offset": 0,
"limit": 2,
"songs": [
{
"id": 10091,
"name": "GDFR",
"artist": "Flo Rida ft. Sage The Gemini and Lookas",
"bpm": 146,
"style": {
"id": 2,
"name": "Hip Hop",
"color": "#6EB1FF"
},
"visible": true,
"unlock_level": 15,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#B0D2FF",
"gradient": "#034EAF"
},
"cover": "/v1/songs/10091/cover",
"audit_song_id": 90008,
"duplicate_of": null,
"chart": {
"duration_ms": 133562,
"levels": 4,
"peak_density": 8.61
},
"released_at": "2026-03-26T00:00:00Z",
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 1,
"hifi_vault": true,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
},
{
"id": 10093,
"name": "Bangarang",
"artist": "SKRILLEX feat. Sirah",
"bpm": 220,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#C415D8",
"gradient": "#4F4FFF"
},
"cover": "/v1/songs/10093/cover",
"audit_song_id": 90010,
"duplicate_of": null,
"chart": {
"duration_ms": 98182,
"levels": 4,
"peak_density": 7.49
},
"released_at": null,
"tags": [
{
"tag_id": 40004,
"name": "Premium",
"family": "edition",
"buff": [15, 10, 5]
},
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 2,
"hifi_vault": true,
"hifi_main": true
},
"starter": {
"novice": false,
"register": false
}
}
]
}
Sorted by release date
{
"sort": "released",
"total": 168,
"count": 2,
"offset": 0,
"limit": 2,
"songs": [
{
"id": 10153,
"name": "Wolves",
"artist": "Selena Gomez, Marshmello",
"bpm": 126,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 10,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#2D48C6",
"gradient": "#000033"
},
"cover": "/v1/songs/10153/cover",
"audit_song_id": 90002,
"duplicate_of": null,
"chart": {
"duration_ms": 117143,
"levels": 4,
"peak_density": 4.41
},
"released_at": "2026-11-23T04:59:59Z",
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 1,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
},
{
"id": 10072,
"name": "Entertain Me",
"artist": "Ylona Garcia",
"bpm": 103,
"style": {
"id": 0,
"name": "Pop",
"color": "#FF63A1"
},
"visible": true,
"unlock_level": 15,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#C45EDD",
"gradient": "#710891"
},
"cover": "/v1/songs/10072/cover",
"audit_song_id": 90013,
"duplicate_of": null,
"chart": {
"duration_ms": 134854,
"levels": 4,
"peak_density": 3.75
},
"released_at": "2026-10-22T05:00:00Z",
"tags": [
{
"tag_id": 50006,
"name": "2020s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 1,
"hifi_vault": true,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
}
]
}
Only the 2020s tag
{
"sort": "id",
"total": 49,
"count": 2,
"offset": 0,
"limit": 2,
"songs": [
{
"id": 10006,
"name": "Beautiful Things",
"artist": "Benson Boone",
"bpm": 70,
"style": {
"id": 1,
"name": "Rock",
"color": "#FF3537"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#E1743D",
"gradient": "#AC1601"
},
"cover": "/v1/songs/10006/cover",
"audit_song_id": 90012,
"duplicate_of": null,
"chart": {
"duration_ms": 116857,
"levels": 4,
"peak_density": 3.42
},
"released_at": null,
"tags": [
{
"tag_id": 50006,
"name": "2020s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10007,
"name": "Leave the Door Open",
"artist": "Bruno Mars, Anderson .Paak, Silk Sonic",
"bpm": 74,
"style": {
"id": 3,
"name": "R&B",
"color": "#2CD3FF"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#E1AB73",
"gradient": "#6B2A17"
},
"cover": "/v1/songs/10007/cover",
"audit_song_id": 90010,
"duplicate_of": null,
"chart": {
"duration_ms": 134595,
"levels": 4,
"peak_density": 3.41
},
"released_at": null,
"tags": [
{
"tag_id": 50006,
"name": "2020s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
}
]
}
Only Hi-Fi Vault songs
{
"sort": "id",
"total": 72,
"count": 2,
"offset": 0,
"limit": 2,
"songs": [
{
"id": 10071,
"name": "That's What I Like (Full Version)",
"artist": "Bruno Mars",
"bpm": 134,
"style": {
"id": 3,
"name": "R&B",
"color": "#2CD3FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#963CE5",
"gradient": "#2D0C70"
},
"cover": "/v1/songs/10071/cover",
"audit_song_id": 90014,
"duplicate_of": null,
"chart": {
"duration_ms": 204403,
"levels": 4,
"peak_density": 4.55
},
"released_at": null,
"tags": [
{
"tag_id": 40003,
"name": "Full Ver.",
"family": "edition",
"buff": [15, 10, 5]
},
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 2,
"hifi_vault": true,
"hifi_main": true
},
"starter": {
"novice": false,
"register": false
}
},
{
"id": 10072,
"name": "Entertain Me",
"artist": "Ylona Garcia",
"bpm": 103,
"style": {
"id": 0,
"name": "Pop",
"color": "#FF63A1"
},
"visible": true,
"unlock_level": 15,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#C45EDD",
"gradient": "#710891"
},
"cover": "/v1/songs/10072/cover",
"audit_song_id": 90013,
"duplicate_of": null,
"chart": {
"duration_ms": 134854,
"levels": 4,
"peak_density": 3.75
},
"released_at": "2026-10-22T05:00:00Z",
"tags": [
{
"tag_id": 50006,
"name": "2020s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 1,
"hifi_vault": true,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
}
]
}
Songs without a release date
{
"sort": "id",
"total": 115,
"count": 2,
"offset": 0,
"limit": 2,
"songs": [
{
"id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#8BECF5",
"gradient": "#0B9ED7"
},
"cover": "/v1/songs/10001/cover",
"audit_song_id": 90002,
"duplicate_of": null,
"chart": {
"duration_ms": 95714,
"levels": 4,
"peak_density": 3.69
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
},
{
"id": 10002,
"name": "Gangnam Style",
"artist": "PSY",
"bpm": 132,
"style": {
"id": 0,
"name": "Pop",
"color": "#FF63A1"
},
"visible": true,
"unlock_level": 0,
"is_base": true,
"is_audit": false,
"colors": {
"primary": "#57C682",
"gradient": "#16712D"
},
"cover": "/v1/songs/10002/cover",
"audit_song_id": 90001,
"duplicate_of": null,
"chart": {
"duration_ms": 101818,
"levels": 4,
"peak_density": 5.15
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": true
}
}
]
}
Second page
{
"sort": "id",
"total": 168,
"count": 2,
"offset": 10,
"limit": 2,
"songs": [
{
"id": 10011,
"name": "Rush E",
"artist": "E",
"bpm": 100,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"visible": false,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#F1625E",
"gradient": "#BD1010"
},
"cover": "/v1/songs/10011/cover",
"audit_song_id": 90006,
"duplicate_of": null,
"chart": {
"duration_ms": 153224,
"levels": 4,
"peak_density": 7.22
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 2,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
},
{
"id": 10012,
"name": "Talking to the Moon",
"artist": "Bruno Mars",
"bpm": 73,
"style": {
"id": 3,
"name": "R&B",
"color": "#2CD3FF"
},
"visible": true,
"unlock_level": 0,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#F6E271",
"gradient": "#D09006"
},
"cover": "/v1/songs/10012/cover",
"audit_song_id": 90005,
"duplicate_of": null,
"chart": {
"duration_ms": 127397,
"levels": 4,
"peak_density": 3.23
},
"released_at": null,
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 0,
"hifi_vault": false,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
}
]
}
Unknown sort key
{
"error": {
"code": "bad_request",
"message": "Unknown sort 'nonsense'. Available: id, name, artist, bpm, density, duration, released."
}
}
GET
/v1/songs/{id}
One song in full
No key
One song with everything the catalog knows about it: artist, BPM, style and its two colours, the difficulty levels it ships, its ranked tags, its release date and which box or shop can hand it out.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | int | required | Song ID, four or five digits |
curl "https://api.beatz-nodefox.de/v1/songs/10137"
One song
{
"catalog_version": "Android22-20260928-1535",
"song": {
"id": 10137,
"name": "The Show Goes On",
"artist": "Lupe Fiasco",
"bpm": 143,
"style": {
"id": 2,
"name": "Hip Hop",
"color": "#6EB1FF"
},
"visible": true,
"unlock_level": 15,
"is_base": false,
"is_audit": false,
"colors": {
"primary": "#FF7070",
"gradient": "#D11215"
},
"cover": "/v1/songs/10137/cover",
"audit_song_id": 90009,
"duplicate_of": null,
"chart": {
"duration_ms": 110350,
"levels": 4,
"peak_density": 5.44
},
"released_at": "2026-09-10T05:00:00Z",
"tags": [
{
"tag_id": 50005,
"name": "2010s",
"family": "decade",
"buff": [15, 10, 5]
}
],
"obtainable": {
"draw_type": 1,
"hifi_vault": true,
"hifi_main": false
},
"starter": {
"novice": false,
"register": false
}
}
}
Unknown song
{
"error": {
"code": "not_found",
"message": "No song with ID 99999 in the catalog."
}
}
GET
/v1/songs/{id}/chart
Note statistics per difficulty
No key
Note chart statistics per difficulty: how many taps, holds and slides the chart has, its longest possible combo, notes per second and how the notes spread across the lanes. The note positions themselves are not served.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | int | required | Song ID |
curl "https://api.beatz-nodefox.de/v1/songs/10001/chart"
Chart statistics
{
"song_id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"catalog_version": "0.2.4",
"levels": [
{
"level": {
"id": 1,
"name": "Standard",
"perfect_plus_ms": 70,
"perfect_ms": 140,
"great_ms": 190
},
"notes": 153,
"taps": 245,
"combo": 357,
"duration_ms": 95714,
"first_note_ms": 1905,
"taps_per_second": 2.56,
"lanes": {
"1": 78,
"2": 44,
"3": 57,
"4": 66
}
},
{
"level": {
"id": 2,
"name": "Gold",
"perfect_plus_ms": 60,
"perfect_ms": 120,
"great_ms": 160
},
"notes": 183,
"taps": 343,
"combo": 495,
"duration_ms": 95714,
"first_note_ms": 1905,
"taps_per_second": 3.58,
"lanes": {
"1": 74,
"2": 98,
"3": 101,
"4": 70
}
},
"... 2 more entries of the same shape"
],
"note": "A tap is one hit. A note can carry several taps at once, which is how holds and simultaneous hits are stored."
}
Unknown song
{
"error": {
"code": "not_found",
"message": "No song with ID 99999 in the catalog."
}
}
GET
/v1/songs/{id}/notes
The chart itself, note by note
No key
The chart itself, not its statistics: every note with its lane and its millisecond. Four kinds, worked out by counting all 207,543 notes in the game rather than from a document that does not exist. tap is one hit on one lane. swipe is one hit to the left or the right, which the NotePrefab table names through its arrow prefabs. hold runs from time_ms to end_ms on the same lane. slide is a path across lanes and lists every corner of it. Lanes run 1 to 4.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | int | required | Song ID |
level | int | all | One difficulty, 1 to 4. Worth passing: a full song runs to a few hundred notes |
curl "https://api.beatz-nodefox.de/v1/songs/10001/notes?level=1"
Titanium, Standard
{
"song_id": 10001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"cover": "/v1/songs/10001/cover",
"catalog_version": "Android22-20260928-1535",
"lanes": 4,
"levels": {
"1": {
"notes": 153,
"duration_ms": 95714,
"first_note_ms": 1905,
"by_type": {
"tap": 86,
"swipe": 12,
"hold": 43,
"slide": 12
},
"lanes": {
"1": 49,
"2": 27,
"3": 37,
"4": 40
},
"chart": [
{
"type": "tap",
"lane": 1,
"time_ms": 1905
},
{
"type": "tap",
"lane": 3,
"time_ms": 2381
},
{
"type": "hold",
"lane": 1,
"time_ms": 18571,
"end_ms": 20000
},
{
"type": "swipe",
"lane": 3,
"time_ms": 20714,
"direction": "right",
"direction_id": 4
},
{
"type": "swipe",
"lane": 2,
"time_ms": 21190,
"direction": "left",
"direction_id": 3
},
{
"type": "slide",
"lane": 4,
"time_ms": 23571,
"end_ms": 24762,
"points": [
[4, 23571],
[3, 23571],
[3, 24762]
]
},
{
"type": "hold",
"lane": 1,
"time_ms": 26190,
"end_ms": 27143
},
{
"type": "slide",
"lane": 1,
"time_ms": 38810,
"end_ms": 40000,
"points": [
[1, 38810],
[2, 38810],
[2, 40000]
]
},
"... 145 more entries of the same shape"
]
}
},
"note": "time_ms and end_ms are milliseconds from the start of the track. lane runs 1 to 4. A swipe carries direction left or right, and direction_id is the game's own number for it."
}
GET
/v1/sounds
The 177 interface sounds
No key
The 177 interface sounds the game ships, with the path each one is served at. Button clicks, the chime for a Great hit, the box opening on the prize wheel. These are the studio's own short effects. No music: the song recordings are not part of this API and are not in the game's bundles either, they are fetched at play time.
curl "https://api.beatz-nodefox.de/v1/sounds"
Sound index
{
"total": 177,
"sounds": [
{
"key": "0",
"name": "0",
"format": "ogg",
"bytes": 6921,
"audio": "/v1/sounds/0"
},
{
"key": "1",
"name": "1",
"format": "ogg",
"bytes": 8739,
"audio": "/v1/sounds/1"
},
"... 175 more entries of the same shape"
],
"note": "The game's own interface sounds, Ogg Vorbis. Button clicks, hit chimes, menu effects. No music: the song recordings are not part of this API."
}
GET
/v1/styles
The five music styles
No key
The five music styles with their two brand colours and how many songs each one holds. The IDs are what /v1/songs?style= expects.
curl "https://api.beatz-nodefox.de/v1/styles"
All styles
{
"catalog_version": "0.2.4",
"styles": [
{
"id": 0,
"name": "Pop",
"color": "#FF63A1",
"songs": 50
},
{
"id": 1,
"name": "Rock",
"color": "#FF3537",
"songs": 30
},
"... 3 more entries of the same shape"
]
}
GET
/v1/levels
The four editions and their thresholds
No key
The four song editions: Standard, Gold and Diamond, plus a fourth row the config carries but the client never shows. Each one with its three timing windows in milliseconds, the score it caps at, the star and medal thresholds a run is measured against, the note speed and how many copies of a song the next edition costs. What turns a hit into points is in /v1/scoring.
curl "https://api.beatz-nodefox.de/v1/levels"
Editions
{
"levels": [
{
"id": 1,
"name": "Standard",
"perfect_plus_ms": 70,
"perfect_ms": 140,
"great_ms": 190,
"max_score": 50000,
"stage_score": 500,
"max_combo_step": 3,
"star_scores": [10000, 17500, 35000, 45000],
"medal_scores": [47500, 48500, 49500],
"note_speed": [1.5, 2],
"copies_to_unlock": 1
},
{
"id": 2,
"name": "Gold",
"perfect_plus_ms": 60,
"perfect_ms": 120,
"great_ms": 160,
"max_score": 75000,
"stage_score": 1000,
"max_combo_step": 4,
"star_scores": [15000, 37500, 60000, 67500],
"medal_scores": [71250, 72750, 74250],
"note_speed": [2, 2.3],
"copies_to_unlock": 2
},
{
"id": 3,
"name": "Diamond",
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130,
"max_score": 100000,
"stage_score": 1500,
"max_combo_step": 5,
"star_scores": [20000, 50000, 80000, 90000],
"medal_scores": [95000, 97000, 99000],
"note_speed": [2.5, 2.5],
"copies_to_unlock": 4
},
{
"id": 4,
"name": "Diamond",
"perfect_plus_ms": 40,
"perfect_ms": 70,
"great_ms": 100,
"max_score": 125000,
"stage_score": 2000,
"max_combo_step": 5,
"star_scores": [20000, 50000, 80000, 90000],
"medal_scores": [118750, 121250, 123750],
"note_speed": [2.8, 2.8],
"copies_to_unlock": 4
}
],
"note": "perfect_plus_ms, perfect_ms and great_ms are the timing windows in milliseconds. star_scores and medal_scores are the thresholds a run is measured against; how they turn into points is in /v1/scoring."
}
GET
/v1/tags
All ranked tags in five families
No key
Every tag a ranked match can ask for, grouped into five families: style, tempo band, artist, edition and decade. Each tag carries the score buff it grants for first, second and third place.
curl "https://api.beatz-nodefox.de/v1/tags"
Tag families
{
"total": 133,
"families": {
"artist": [
{
"tag_id": 30001,
"name": "David Guetta",
"family": "artist",
"buff": [15, 10, 5]
},
{
"tag_id": 30002,
"name": "PSY",
"family": "artist",
"buff": [15, 10, 5]
},
"... 115 more entries of the same shape"
],
"decade": [
{
"tag_id": 50001,
"name": "Pre-80s",
"family": "decade",
"buff": [15, 10, 5]
},
{
"tag_id": 50002,
"name": "1980s",
"family": "decade",
"buff": [15, 10, 5]
},
"... 4 more entries of the same shape"
],
"edition": [
{
"tag_id": 40003,
"name": "Full Ver.",
"family": "edition",
"buff": [15, 10, 5]
},
{
"tag_id": 40004,
"name": "Premium",
"family": "edition",
"buff": [15, 10, 5]
}
],
"style": [
{
"tag_id": 10001,
"name": "Rock",
"family": "style",
"buff": [15, 10, 5]
},
{
"tag_id": 10002,
"name": "R&B",
"family": "style",
"buff": [15, 10, 5]
},
"... 3 more entries of the same shape"
],
"tempo": [
{
"tag_id": 20001,
"name": "Slow",
"family": "tempo",
"buff": [15, 10, 5],
"bpm_min": 0,
"bpm_max": 76
},
{
"tag_id": 20002,
"name": "Mid",
"family": "tempo",
"buff": [15, 10, 5],
"bpm_min": 76,
"bpm_max": 120
},
"... 1 more entries of the same shape"
]
},
"note": "The tags a ranked match can ask for. buff is passed through from the game config; what separates its three numbers is not stated there."
}
GET
/v1/regions
The 186 country codes and their flags
No key
The 186 country codes the game knows, with a flag for each. These are the only values /v1/leaderboard accepts as region; anything else now answers 400 instead of an empty board. name is the ISO 3166 English short name and is not part of the game's data, name_zh is.
curl "https://api.beatz-nodefox.de/v1/regions"
Country codes
{
"total": 186,
"regions": [
{
"code": "AD",
"name": "Andorra",
"name_zh": "安道尔",
"flag": "/v1/flags/ad"
},
{
"code": "AE",
"name": "United Arab Emirates",
"name_zh": "阿联酋",
"flag": "/v1/flags/ae"
},
{
"code": "AF",
"name": "Afghanistan",
"name_zh": "阿富汗",
"flag": "/v1/flags/af"
},
"... 183 more entries of the same shape"
],
"note": "The country codes the game itself knows, and the only ones /v1/leaderboard accepts as region. name is the ISO 3166 English short name and is not part of the game's data; name_zh is. OT is the game's bucket for everything else."
}
GET
/v1/tips
The game's own loading screen tips
No key
The 20 loading screen tips, word for word as the game shows them. They double as the game's own short explanation of its mechanics: the maximum score per edition, how many tickets a song pays, what unlocks a Gold song.
curl "https://api.beatz-nodefox.de/v1/tips"
Loading tips
{
"total": 20,
"tips": [
{
"id": 1,
"text": "Turn up the volume and feel the beat!"
},
{
"id": 2,
"text": "Hit perfect+ in the perfect zone for a higher score!"
},
{
"id": 3,
"text": "More perfect+ hits = higher score!"
},
{
"id": 4,
"text": "Earn up to 8 tickets per song!"
},
{
"id": 5,
"text": "Keep your combo going for more points!"
},
"... 15 more entries of the same shape"
],
"note": "The loading screen texts, word for word as the game shows them."
}
Game rules
No key required 11Season structure, reward tracks, unlock levels, prices and draw chances. All of this is read from the game config, so it needs no key either.
GET
/v1/scoring
Judgements, combo and what a run pays
No key
How a played song turns into a number. The five judgements with what each is worth, the five combo steps, what a broken combo costs, the seven thresholds a finished song pays out on, and what a revive costs. formula states how the parts multiply, read off the game's own code rather than guessed: every note pays base_score × judgement × combo, and base_score is derived per chart so that a flawless run lands exactly on the edition's maximum.
curl "https://api.beatz-nodefox.de/v1/scoring"
The scoring model
{
"judgements": [
{
"id": 0,
"name": "Perfect+",
"default_window_ms": 50,
"score_rate": 5,
"combo": "continues"
},
{
"id": 1,
"name": "Perfect",
"default_window_ms": 100,
"score_rate": 4,
"combo": "continues"
},
{
"id": 2,
"name": "Great",
"default_window_ms": 130,
"score_rate": 2,
"combo": "breaks"
},
{
"id": 3,
"name": "Early",
"default_window_ms": null,
"score_rate": 0,
"combo": "breaks"
},
{
"id": 4,
"name": "Miss",
"default_window_ms": null,
"score_rate": 0,
"combo": "unchanged"
}
],
"combo": {
"steps": [
{
"step": 1,
"name": "x 1",
"multiplier": 1,
"notes": 10,
"color": "#FFFFFF"
},
{
"step": 2,
"name": "x 2",
"multiplier": 2,
"notes": 15,
"color": "#10EC00"
},
{
"step": 3,
"name": "x 3",
"multiplier": 3,
"notes": 25,
"color": "#FFB200"
},
{
"step": 4,
"name": "x 4",
"multiplier": 4,
"notes": 50,
"color": "#9D00FF"
},
{
"step": 5,
"name": "x 5",
"multiplier": 5,
"notes": 50,
"color": "#FF0095"
}
],
"break": [
{
"breaks_in_a_row": 1,
"decrease_num": -1
},
{
"breaks_in_a_row": 2,
"decrease_num": 30
},
"... 4 more entries of the same shape"
],
"note": "A combo step holds for its own number of notes and then moves up. break lists the config's own columns for a broken combo, interruptNum and decreaseNum; the config does not state the unit of decreaseNum, so it is passed through unchanged."
},
"editions": [
{
"id": 1,
"name": "Standard",
"max_score": 50000,
"stage_score": 500,
"max_combo_step": 3,
"star_scores": [10000, 17500, 35000, 45000],
"medal_scores": [47500, 48500, 49500],
"note_speed": [1.5, 2],
"perfect_plus_ms": 70,
"perfect_ms": 140,
"great_ms": 190,
"copies_to_unlock": 1
},
{
"id": 2,
"name": "Gold",
"max_score": 75000,
"stage_score": 1000,
"max_combo_step": 4,
"star_scores": [15000, 37500, 60000, 67500],
"medal_scores": [71250, 72750, 74250],
"note_speed": [2, 2.3],
"perfect_plus_ms": 60,
"perfect_ms": 120,
"great_ms": 160,
"copies_to_unlock": 2
},
{
"id": 3,
"name": "Diamond",
"max_score": 100000,
"stage_score": 1500,
"max_combo_step": 5,
"star_scores": [20000, 50000, 80000, 90000],
"medal_scores": [95000, 97000, 99000],
"note_speed": [2.5, 2.5],
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130,
"copies_to_unlock": 4
},
{
"id": 4,
"name": "Diamond",
"max_score": 125000,
"stage_score": 2000,
"max_combo_step": 5,
"star_scores": [20000, 50000, 80000, 90000],
"medal_scores": [118750, 121250, 123750],
"note_speed": [2.8, 2.8],
"perfect_plus_ms": 40,
"perfect_ms": 70,
"great_ms": 100,
"copies_to_unlock": 4
}
],
"settlement": {
"thresholds": [
{
"id": 1,
"kind": "star",
"score_per_edition": [10000, 15000, 20000],
"battle_pass_coin": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 1
}
]
},
{
"id": 2,
"kind": "star",
"score_per_edition": [17500, 37500, 50000],
"battle_pass_coin": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 1
}
]
},
"... 5 more entries of the same shape"
],
"note": "Each threshold pays once per song. score_per_edition holds one score per edition, in the order of editions above. The first four are the star thresholds, the last three the medal thresholds."
},
"revive": {
"diamond_cost": [5, 8, 10],
"ad_revives": 3,
"ad_reset_hours": 5,
"offered_when": {
"remaining_notes_at_most": 30,
"score_at_least": 1000,
"combo_at_least": 50
}
},
"formula": {
"per_note": "base_score * judgement.score_rate * combo.multiplier",
"base_score": "(edition.max_score - edition.stage_score * stages) / (weighted note count * Perfect+ rate + hold rate)",
"clamp": "The running score never exceeds edition.max_score.",
"stars": "How many entries of star_scores the final score passes, 0 to 4.",
"medals": "How many entries of medal_scores the final score passes, 0 to 3. This is the S, SS and SSS rating, and the number /v1/user/records reports as grade.",
"note": "base_score is derived per chart, not configured: it is set so that a flawless run reaches the edition's maximum exactly. Read off AddScoreRate, CountKeyNoteScore, GetStarbyScore and GetMedalbyScore in Assembly-CSharp."
},
"song_rank_grade": {
"rows": [
{
"id": 1,
"grade": 1,
"score": 99
},
{
"id": 2,
"grade": 10,
"score": 90
},
{
"id": 3,
"grade": 30,
"score": 80
},
{
"id": 4,
"grade": 60,
"score": 70
},
{
"id": 5,
"grade": 100,
"score": 0
}
],
"note": "The config table SongRankGrade, unchanged. No client code reads it, so what its two columns mean is not stated here. The rating a played song gets is the medal count below, which is what /v1/user/records reports as grade."
}
}
GET
/v1/ranked/seasons
Season dates and rules
No key
The competitive seasons with their start and end dates, the tier you are guaranteed to keep, how long idle rewards accumulate and where the season counts as ending.
curl "https://api.beatz-nodefox.de/v1/ranked/seasons"
Seasons
{
"seasons": [
{
"id": 1,
"name": "Season 1",
"start": "2026-07-28T05:00:00Z",
"end": "2026-10-29T04:30:00Z",
"start_tier": {
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100
},
"guaranteed_tier": {
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100
},
"end_tier": {
"id": 1044,
"name": "Legend",
"group": "Legend",
"division": null,
"colors": {
"primary": "#FFCD42",
"gradient": "#991300"
},
"badge": "/v1/tiers/legend",
"point_conversion_percent": 100
},
"idle_reward_hours": 72,
"ending_days": 30,
"record_break_point": 10,
"active": true
},
{
"id": 2,
"name": "Season 2",
"start": "2026-10-29T05:00:00Z",
"end": "2027-01-29T04:30:00Z",
"start_tier": {
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100
},
"guaranteed_tier": {
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100
},
"end_tier": {
"id": 1044,
"name": "Legend",
"group": "Legend",
"division": null,
"colors": {
"primary": "#FFCD42",
"gradient": "#991300"
},
"badge": "/v1/tiers/legend",
"point_conversion_percent": 100
},
"idle_reward_hours": 72,
"ending_days": 30,
"record_break_point": 10,
"active": false
},
"... 1 more entries of the same shape"
],
"current": 1
}
GET
/v1/ranked/tiers
All 35 leagues and their rewards
No key
All 35 leagues from Rookie1 up to Legend: the ranked score each one needs, its badge and colours, the share of points it converts and what it pays out on promotion, per idle window and at the end of the season.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
season | int | current | Season ID, see /v1/ranked/seasons |
curl "https://api.beatz-nodefox.de/v1/ranked/tiers"
Current season
{
"season": 1,
"seasons": [1, 2, 3],
"total": 35,
"groups": [
"Rookie",
"Novice",
"Elite",
"Pro",
"Star",
"Expert",
"Master",
"Virtuoso",
"Legend"
],
"tiers": [
{
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100,
"ranked_score": 0,
"score_gap": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
],
"idle_rewards": [],
"season_rewards": []
},
{
"id": 1011,
"name": "Rookie2",
"group": "Rookie",
"division": 2,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100,
"ranked_score": 1,
"score_gap": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
],
"idle_rewards": [],
"season_rewards": []
},
"... 33 more entries of the same shape"
]
}
Season 1
{
"season": 1,
"seasons": [1, 2, 3],
"total": 35,
"groups": [
"Rookie",
"Novice",
"Elite",
"Pro",
"Star",
"Expert",
"Master",
"Virtuoso",
"Legend"
],
"tiers": [
{
"id": 1010,
"name": "Rookie1",
"group": "Rookie",
"division": 1,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100,
"ranked_score": 0,
"score_gap": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
],
"idle_rewards": [],
"season_rewards": []
},
{
"id": 1011,
"name": "Rookie2",
"group": "Rookie",
"division": 2,
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"badge": "/v1/tiers/rookie",
"point_conversion_percent": 100,
"ranked_score": 1,
"score_gap": 1,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
],
"idle_rewards": [],
"season_rewards": []
},
"... 33 more entries of the same shape"
]
}
Unknown season
{
"error": {
"code": "bad_request",
"message": "No season 99. Available: 1, 2, 3."
}
}
GET
/v1/ranked/matchmaking
The twelve round rule sets
No key
The twelve rule sets a ranked round can run under: how long the round lasts, how many places the board has, how many of them promote, what a placing is worth and which tags the round may draw.
curl "https://api.beatz-nodefox.de/v1/ranked/matchmaking"
Rule sets
{
"total": 12,
"rules": [
{
"round_minutes": 5,
"ending_minutes": 0,
"songs": 1,
"song_level": 1,
"reroll_limit": 999,
"board_size": 15,
"dummy_size": 11,
"promote_above": 5,
"demote_below": 16,
"points": {
"win": 100,
"draw": 0,
"lose": 0
},
"fixed_tags": [],
"random_tags": [],
"random_tag_count": 0,
"fatigue": {
"enabled": false,
"gate": 999,
"penalty_base": 10,
"penalty_mod": 5
},
"debuff_base_score": 5,
"max_debuff": 5,
"matchmaking_id": 1
},
{
"round_minutes": 5,
"ending_minutes": 0,
"songs": 1,
"song_level": 1,
"reroll_limit": 999,
"board_size": 15,
"dummy_size": 11,
"promote_above": 5,
"demote_below": 16,
"points": {
"win": 100,
"draw": 0,
"lose": 0
},
"fixed_tags": [],
"random_tags": [
{
"tag_id": 10001,
"name": "Rock",
"family": "style",
"buff": [15, 10, 5]
},
{
"tag_id": 10002,
"name": "R&B",
"family": "style",
"buff": [15, 10, 5]
},
"... 3 more entries of the same shape"
],
"random_tag_count": 1,
"fatigue": {
"enabled": false,
"gate": 999,
"penalty_base": 10,
"penalty_mod": 5
},
"debuff_base_score": 5,
"max_debuff": 5,
"matchmaking_id": 2
},
"... 10 more entries of the same shape"
]
}
GET
/v1/progression
The 60 player levels
No key
The 60 player levels with the total score each one asks for and the reward for reaching it.
curl "https://api.beatz-nodefox.de/v1/progression"
Player levels
{
"total": 60,
"levels": [
{
"level": 1,
"total_score": 0,
"task_id": null,
"rewards": []
},
{
"level": 2,
"total_score": 55000,
"task_id": null,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
]
},
"... 58 more entries of the same shape"
],
"note": "Levels are earned through total score, not through the exp counter."
}
GET
/v1/battlepass
Battle pass track of one period
No key
The battle pass reward track for one period, free and premium side by side, with the coins each level costs.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
period | int | current | Battle pass period, 1 upwards |
curl "https://api.beatz-nodefox.de/v1/battlepass"
Current period
{
"periods": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"period": 10,
"peak": {
"coin_per_stage": 60,
"free": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 1
}
],
"premium": [
{
"cfg_id": 110100004,
"kind": "item",
"key": "vault_ticket",
"name": "Vault Tickets",
"icon": "/v1/items/Icon_GoldPrize",
"count": 1
}
]
},
"levels": [
{
"level": 1,
"coin": 0,
"bonus": 0,
"song": {
"song_id": 10128,
"name": "Boulevard Of Broken Dreams",
"artist": "Green Day",
"cover": "/v1/songs/10128/cover"
},
"free": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
],
"premium": [
{
"cfg_id": 110100004,
"kind": "item",
"key": "vault_ticket",
"name": "Vault Tickets",
"icon": "/v1/items/Icon_GoldPrize",
"count": 2
}
]
},
{
"level": 2,
"coin": 20,
"bonus": 0,
"song": {
"song_id": 10128,
"name": "Boulevard Of Broken Dreams",
"artist": "Green Day",
"cover": "/v1/songs/10128/cover"
},
"free": [
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 5
}
],
"premium": [
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 15
}
]
},
"... 48 more entries of the same shape"
]
}
Period 1
{
"periods": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"period": 1,
"peak": {
"coin_per_stage": 20,
"free": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
],
"premium": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
]
},
"levels": [
{
"level": 1,
"coin": 0,
"bonus": 0,
"song": {
"song_id": 10021,
"name": "7 rings",
"artist": "Ariana Grande",
"cover": "/v1/songs/10021/cover"
},
"free": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
],
"premium": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 5
}
]
},
{
"level": 2,
"coin": 20,
"bonus": 0,
"song": {
"song_id": 10021,
"name": "7 rings",
"artist": "Ariana Grande",
"cover": "/v1/songs/10021/cover"
},
"free": [
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 5
}
],
"premium": [
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 15
}
]
},
"... 48 more entries of the same shape"
]
}
Unknown period
{
"error": {
"code": "bad_request",
"message": "No battle pass period 99. Available: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10."
}
}
GET
/v1/journey
The nine journey boards
No key
The nine boards of the journey mode with their badges, colours, the songs each board draws from and the rewards along the way.
curl "https://api.beatz-nodefox.de/v1/journey"
Boards
{
"total": 9,
"boards": [
{
"board": 1,
"name": "Rookie",
"badge": "/v1/tiers/rookie",
"colors": {
"primary": "#D1B382",
"gradient": "#594035"
},
"players": 20,
"promoted": 5,
"cycle_minutes": 0,
"max_song_score": 50000,
"entry_score": 0,
"rewards": []
},
{
"board": 2,
"name": "Novice",
"badge": "/v1/tiers/novice",
"colors": {
"primary": "#CBF065",
"gradient": "#074007"
},
"players": 20,
"promoted": 5,
"cycle_minutes": 0,
"max_song_score": 50000,
"entry_score": 0,
"rewards": [
{
"cfg_id": 120110022,
"kind": "song",
"key": null,
"name": "APT",
"icon": "/v1/songs/10022/cover",
"song_id": 10022,
"artist": "ROSE, Bruno Mars",
"count": 1
}
]
},
"... 7 more entries of the same shape"
]
}
GET
/v1/modules
Which feature unlocks when
No key
Which feature of the game unlocks at which player level. The same list the client uses to grey out menu entries.
curl "https://api.beatz-nodefox.de/v1/modules"
Unlock levels
{
"total": 25,
"modules": [
{
"module": 1,
"key": "battle_pass",
"level": 9
},
{
"module": 2,
"key": "friend",
"level": 9999
},
"... 23 more entries of the same shape"
],
"note": "Which feature of the game unlocks at which player level."
}
GET
/v1/economy
Rates, costs, caps and shop catalogue
No key
Exchange rates between the currencies, upgrade costs, weekly caps, the daily advertisement limits and the full shop catalogue with prices.
curl "https://api.beatz-nodefox.de/v1/economy"
Economy
{
"constants": {
"ad_diamond_count": 5,
"ad_ten_draw_times": 1,
"ad_trial_song_times": 3,
"battle_coin_per_song": 3,
"battle_coin_week_limit": 500,
"exchange_battle_coin_diamond": 5,
"exchange_lottery_coin_diamond": 5,
"exchange_vault_ticket_diamond": 5,
"fragment_normal_count": 60,
"fragment_premiere_count": 60,
"fragment_wish_count": 120,
"hifi_convert_count": 2,
"hifi_main_convert_count": 4,
"hifi_main_upgrade_diamond": 596,
"newNoviceTaskFinalReward": [
{
"cfg_id": 100100040,
"kind": "decoration",
"key": "avatar",
"name": "avatar",
"icon": "/v1/avatars/40",
"count": 1
}
],
"novice_task_end_days": 7,
"song_convert_goods_count": 30,
"song_upgrade_diamond": 298,
"wish_song_min_count": 6
},
"shop": [
{
"shop_id": 1301,
"commodities": [
{
"commodity_id": 130100001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 50
}
],
"diamonds": null,
"price": 0.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem1"
},
{
"commodity_id": 130100002,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 275
}
],
"diamonds": null,
"price": 4.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem2"
},
"... 4 more entries of the same shape"
]
},
{
"shop_id": 1302,
"commodities": [
{
"commodity_id": 130210001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 15
}
],
"diamonds": null,
"price": 9.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem6"
}
]
},
"... 5 more entries of the same shape"
],
"note": "Config data, read only. The constant keys are the game's own. price has no currency: which one applies depends on the store and the region."
}
GET
/v1/events
Event and gift store templates
No key
The event templates the 2026-09-15 game update introduced. Each gift store with its title, its offers, the purchase limit per offer and the discount the client prints on the card. Which of them is running right now, and how much of a limit is left, is in /v1/user/events.
curl "https://api.beatz-nodefox.de/v1/events"
Event templates
{
"events": {
"10001": {
"config_id": 10001,
"template": "gift_store",
"template_config_id": 1,
"widget_position": [1],
"widget_timer": true,
"banner": false,
"gift_store": {
"config_id": 1,
"title": "Hi-Fi Super Pack",
"subtitle": "Onc Chance Super Discounts!",
"shown_items": 3,
"items": [
{
"bundle_item_id": 1,
"enabled": true,
"commodity_id": 130600001,
"limit": 9,
"limit_refresh": 0,
"discount_percent": 10,
"title": "We strongly recommend wearing headphones for the best gaming experience",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600001,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 20
}
],
"diamonds": null,
"price": 0.29,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 2,
"enabled": true,
"commodity_id": 130600004,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 20,
"title": "Confirm Your Purchase",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600004,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 160
},
{
"cfg_id": 110100003,
"kind": "item",
"key": "hi_fi_coin",
"name": "Hi-Fi Coin",
"icon": "/v1/items/Icon_HiFiCoin",
"count": 5
}
],
"diamonds": null,
"price": 9.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 3,
"enabled": true,
"commodity_id": 130600005,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 30,
"title": "Hit 120 Beat Notes{0}",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600005,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 310
},
{
"cfg_id": 110100003,
"kind": "item",
"key": "hi_fi_coin",
"name": "Hi-Fi Coin",
"icon": "/v1/items/Icon_HiFiCoin",
"count": 8
}
],
"diamonds": null,
"price": 19.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
}
]
}
},
"10002": {
"config_id": 10002,
"template": "gift_store",
"template_config_id": 2,
"widget_position": [2],
"widget_timer": true,
"banner": false,
"gift_store": {
"config_id": 2,
"title": "Hi-Fi Booster Rush",
"subtitle": "Boosters on sale for a limited time—Stock up on boosters and speed up your vault progress!",
"shown_items": 7,
"items": [
{
"bundle_item_id": 4,
"enabled": true,
"commodity_id": 140400001,
"limit": 1,
"limit_refresh": 1,
"discount_percent": 0,
"title": "Free Gift",
"commodity": {
"shop_id": 1404,
"commodity_id": 140400001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 5
}
],
"diamonds": null,
"price": null,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 5,
"enabled": true,
"commodity_id": 130600001,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 85,
"title": "Starter Pack",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600001,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 20
}
],
"diamonds": null,
"price": 0.29,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
"... 5 more entries of the same shape"
]
}
}
},
"gift_stores": {
"1": {
"config_id": 1,
"title": "Hi-Fi Super Pack",
"subtitle": "Onc Chance Super Discounts!",
"shown_items": 3,
"items": [
{
"bundle_item_id": 1,
"enabled": true,
"commodity_id": 130600001,
"limit": 9,
"limit_refresh": 0,
"discount_percent": 10,
"title": "We strongly recommend wearing headphones for the best gaming experience",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600001,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 20
}
],
"diamonds": null,
"price": 0.29,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 2,
"enabled": true,
"commodity_id": 130600004,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 20,
"title": "Confirm Your Purchase",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600004,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 160
},
{
"cfg_id": 110100003,
"kind": "item",
"key": "hi_fi_coin",
"name": "Hi-Fi Coin",
"icon": "/v1/items/Icon_HiFiCoin",
"count": 5
}
],
"diamonds": null,
"price": 9.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 3,
"enabled": true,
"commodity_id": 130600005,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 30,
"title": "Hit 120 Beat Notes{0}",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600005,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 310
},
{
"cfg_id": 110100003,
"kind": "item",
"key": "hi_fi_coin",
"name": "Hi-Fi Coin",
"icon": "/v1/items/Icon_HiFiCoin",
"count": 8
}
],
"diamonds": null,
"price": 19.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
}
]
},
"2": {
"config_id": 2,
"title": "Hi-Fi Booster Rush",
"subtitle": "Boosters on sale for a limited time—Stock up on boosters and speed up your vault progress!",
"shown_items": 7,
"items": [
{
"bundle_item_id": 4,
"enabled": true,
"commodity_id": 140400001,
"limit": 1,
"limit_refresh": 1,
"discount_percent": 0,
"title": "Free Gift",
"commodity": {
"shop_id": 1404,
"commodity_id": 140400001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 5
}
],
"diamonds": null,
"price": null,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
{
"bundle_item_id": 5,
"enabled": true,
"commodity_id": 130600001,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 85,
"title": "Starter Pack",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600001,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 20
}
],
"diamonds": null,
"price": 0.29,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
}
},
"... 5 more entries of the same shape"
]
}
},
"note": "The event templates from the game config. Which one is running right now is in /v1/user/events."
}
GET
/v1/odds
Draw chances and pity counters
No key
Draw chances of the prize wheel, both vaults and the premiere box, with the pity counters that force a rare result. Two numbers are reported for the wheel: the raw weight out of ten thousand and the percentage the client displays. They do not describe the same distribution, so both are given as the game states them, uninterpreted.
curl "https://api.beatz-nodefox.de/v1/odds"
All draw tables
{
"wheel": {
"grades": [
{
"grade": 1,
"name": null,
"color": "rainbow",
"weight": 1,
"weight_total": 10000,
"displayed_probability_percent": 0.01
},
{
"grade": 2,
"name": "MYTHIC",
"color": "gold",
"weight": 50,
"weight_total": 10000,
"displayed_probability_percent": 1.5
},
"... 3 more entries of the same shape"
],
"prizes": [
{
"grade": 1,
"slot": 0,
"weight": 100,
"weight_total": 100,
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 100
},
{
"grade": 2,
"slot": 5,
"weight": 67,
"weight_total": 100,
"cfg_id": 110200001,
"kind": "item",
"key": "normal_box_l1",
"name": "Song Box",
"icon": "/v1/items/Icon_Box",
"count": 1
},
"... 14 more entries of the same shape"
],
"guarantee": [
{
"after_spins": 50,
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 20
},
{
"after_spins": 100,
"cfg_id": 110300002,
"kind": "item",
"key": "wish_box_fragment",
"name": "Wish Fragments",
"icon": "/v1/items/Icon_WishBoxChip",
"count": 20
},
"... 2 more entries of the same shape"
]
},
"vault": {
"grades": [
{
"grade": 2,
"name": "MYTHIC",
"quality": 1,
"displayed_probability_percent": 1.5,
"examples": [
{
"cfg_id": 110200006,
"kind": "item",
"key": "premiere_box",
"name": "Premiere Box",
"icon": "/v1/items/Icon_PremiereBox",
"count": 1
},
{
"cfg_id": 110200001,
"kind": "item",
"key": "normal_box_l1",
"name": "Song Box",
"icon": "/v1/items/Icon_Box",
"count": 1
}
]
},
{
"grade": 3,
"name": "SPECIAL",
"quality": 2,
"displayed_probability_percent": 5,
"examples": [
{
"cfg_id": 110300003,
"kind": "item",
"key": "premiere_box_fragment",
"name": "Premiere Fragments",
"icon": "/v1/items/Icon_HiFiChip",
"count": 12
},
{
"cfg_id": 110300003,
"kind": "item",
"key": "premiere_box_fragment",
"name": "Premiere Fragments",
"icon": "/v1/items/Icon_HiFiChip",
"count": 10
},
"... 2 more entries of the same shape"
]
},
"... 2 more entries of the same shape"
],
"prizes": [
{
"grade": 2,
"cfg_id": 110200001,
"kind": "item",
"key": "normal_box_l1",
"name": "Song Box",
"icon": "/v1/items/Icon_Box",
"count": 1
},
{
"grade": 2,
"cfg_id": 110200006,
"kind": "item",
"key": "premiere_box",
"name": "Premiere Box",
"icon": "/v1/items/Icon_PremiereBox",
"count": 1
},
"... 10 more entries of the same shape"
],
"displayed_total_percent": 100
},
"shop_vault": {
"grades": [
{
"grade": 2,
"name": "MYTHIC",
"quality": 1,
"displayed_probability_percent": 1,
"examples": [
{
"cfg_id": 110200006,
"kind": "item",
"key": "premiere_box",
"name": "Premiere Box",
"icon": "/v1/items/Icon_PremiereBox",
"count": 1
},
{
"cfg_id": 110200001,
"kind": "item",
"key": "normal_box_l1",
"name": "Song Box",
"icon": "/v1/items/Icon_Box",
"count": 1
}
]
},
{
"grade": 3,
"name": "SPECIAL",
"quality": 2,
"displayed_probability_percent": 3,
"examples": [
{
"cfg_id": 110300003,
"kind": "item",
"key": "premiere_box_fragment",
"name": "Premiere Fragments",
"icon": "/v1/items/Icon_HiFiChip",
"count": 12
},
{
"cfg_id": 110300003,
"kind": "item",
"key": "premiere_box_fragment",
"name": "Premiere Fragments",
"icon": "/v1/items/Icon_HiFiChip",
"count": 10
},
"... 2 more entries of the same shape"
]
},
"... 2 more entries of the same shape"
],
"displayed_total_percent": 101
},
"premiere_box": {
"1": [
{
"song_id": 10080,
"name": "Uptown Funk",
"artist": "Mark Ronson ft.Bruno Mars",
"cover": "/v1/songs/10080/cover"
},
{
"song_id": 10081,
"name": "DAKITI",
"artist": "Bad Bunny, Jhayco",
"cover": "/v1/songs/10081/cover"
},
"... 4 more entries of the same shape"
],
"2": [
{
"song_id": 10086,
"name": "Something Just Like This",
"artist": "The Chainsmokers, Coldplay",
"cover": "/v1/songs/10086/cover"
},
{
"song_id": 10087,
"name": "Chandelier",
"artist": "Sia",
"cover": "/v1/songs/10087/cover"
},
"... 4 more entries of the same shape"
],
"3": [
{
"song_id": 10092,
"name": "IDGAF",
"artist": "Dua Lipa",
"cover": "/v1/songs/10092/cover"
},
{
"song_id": 10093,
"name": "Bangarang",
"artist": "SKRILLEX feat. Sirah",
"cover": "/v1/songs/10093/cover"
},
"... 4 more entries of the same shape"
],
"4": [
{
"song_id": 10073,
"name": "INFERNO",
"artist": "Sub Urban / Bella Poarch",
"cover": "/v1/songs/10073/cover"
},
{
"song_id": 10075,
"name": "Stereo Hearts",
"artist": "Gym Class Heroes feat. Adam Levine",
"cover": "/v1/songs/10075/cover"
},
"... 4 more entries of the same shape"
],
"5": [
{
"song_id": 10104,
"name": "Finesse(Remix)",
"artist": "Bruno Mars feat. Cardi B",
"cover": "/v1/songs/10104/cover"
},
{
"song_id": 10105,
"name": "Way down We Go",
"artist": "KALEO",
"cover": "/v1/songs/10105/cover"
},
"... 4 more entries of the same shape"
],
"6": [
{
"song_id": 10110,
"name": "2002",
"artist": "Anne-Marie",
"cover": "/v1/songs/10110/cover"
},
{
"song_id": 10111,
"name": "Black And Yellow",
"artist": "Wiz Khalifa",
"cover": "/v1/songs/10111/cover"
},
"... 4 more entries of the same shape"
],
"7": [
{
"song_id": 10117,
"name": "Ride",
"artist": "twenty one pilots",
"cover": "/v1/songs/10117/cover"
},
{
"song_id": 10118,
"name": "The Box",
"artist": "Roddy Ricch",
"cover": "/v1/songs/10118/cover"
},
"... 4 more entries of the same shape"
],
"8": [
{
"song_id": 10071,
"name": "That's What I Like (Full Version)",
"artist": "Bruno Mars",
"cover": "/v1/songs/10071/cover"
},
{
"song_id": 10123,
"name": "Life Goes On",
"artist": "Oliver Tree",
"cover": "/v1/songs/10123/cover"
},
"... 4 more entries of the same shape"
],
"9": [
{
"song_id": 10129,
"name": "The Spectre (Full Version)",
"artist": "Alan Walker",
"cover": "/v1/songs/10129/cover"
},
{
"song_id": 10130,
"name": "Djadja",
"artist": "Aya Nakamura",
"cover": "/v1/songs/10130/cover"
},
"... 4 more entries of the same shape"
],
"10": [
{
"song_id": 10135,
"name": "24K Magic (Full Version)",
"artist": "Bruno Mars",
"cover": "/v1/songs/10135/cover"
},
{
"song_id": 10136,
"name": "Don't Start Now",
"artist": "Dua Lipa",
"cover": "/v1/songs/10136/cover"
},
"... 4 more entries of the same shape"
]
},
"note": "Read only. Nothing can be drawn or bought through this API. weight and displayed_probability_percent come from the game config unchanged; for the wheel the two do not agree."
}
Images and audio
No key required 7Every image route answers WebP with a one year cache lifetime, the sound route answers Ogg Vorbis, and none of them needs a key. Music is not served: the song recordings are licensed, and they are not in the game's bundles to begin with.
GET
/v1/songs/{id}/cover
Cover art, 512 by 512 WebP
No key
Cover art, 512 by 512. A song whose artwork the game loads separately answers 404 no_cover rather than a placeholder.
curl "https://api.beatz-nodefox.de/v1/songs/10001/cover"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/songs/10001/cover | 200 | image/webp | 25,084 bytes |
/v1/songs/99999/cover | 404 | application/json | 112 bytes |
GET
/v1/avatars/{id}
Profile picture by avatar ID
No key
Profile picture by avatar ID, 256 by 256. The ID comes from /v1/user/avatar or from a player profile.
curl "https://api.beatz-nodefox.de/v1/avatars/1"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/avatars/1 | 200 | image/webp | 4,534 bytes |
GET
/v1/banners/{id}
Profile banner by decoration ID
No key
Profile banner, named by its decoration ID.
curl "https://api.beatz-nodefox.de/v1/banners/100300001"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/banners/100300001 | 200 | image/webp | 25,608 bytes |
GET
/v1/tiers/{group}
League badge by group name
No key
League badge by group name: rookie, novice, elite, pro, star, expert, master, virtuoso or legend.
curl "https://api.beatz-nodefox.de/v1/tiers/legend"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/tiers/legend | 200 | image/webp | 70,544 bytes |
/v1/tiers/nosuchleague | 404 | application/json | 110 bytes |
GET
/v1/flags/{code}
Country flag by ISO code
No key
Country flag by ISO code, lower case.
curl "https://api.beatz-nodefox.de/v1/flags/de"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/flags/de | 200 | image/webp | 294 bytes |
GET
/v1/sounds/{name}
One interface sound as Ogg
No key
One interface sound as Ogg Vorbis. The names come from /v1/sounds.
curl "https://api.beatz-nodefox.de/v1/sounds/ButtonClick01"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/sounds/ButtonClick01 | 200 | audio/ogg | 8,277 bytes |
/v1/sounds/NoSuchSound | 404 | application/json | 113 bytes |
GET
/v1/items/{name}
Currency, item or decoration icon
No key
Currency, item or decoration icon. Every reward object in this API carries its own icon path, so you rarely have to build one by hand.
curl "https://api.beatz-nodefox.de/v1/items/Icon_Diamond"
| Request | Status | Type | Size |
|---|---|---|---|
/v1/items/Icon_Diamond | 200 | image/webp | 7,754 bytes |
Your account
X-Api-Key 21These read your own player data through the game server and need the X-Api-Key header. The game answers most of them in two bundles rather than one request each, and the API keeps both for the length of a request, so asking for several in a row costs the game server no more than asking for one. All of them are read only.
GET
/v1/user
Your profile and the links to everything below
X-Api-Key
Your profile: name, unique code, level, region, how many songs you own and which styles you picked. links points at the sub-resources below.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user"
Profile
{
"gamer_id": 100000001,
"username": "Nightfall",
"unique_code": "HB7K2Q",
"level": 47,
"exp": 9148630,
"region": {
"code": "DE",
"name": "Germany",
"flag": "/v1/flags/de"
},
"gender": 3,
"songs_owned": 110,
"music_styles": [4, 2],
"links": {
"avatar": "/v1/user/avatar",
"banner": "/v1/user/banner",
"score": "/v1/user/score",
"currencies": "/v1/user/currencies",
"level": "/v1/user/level",
"songs": "/v1/user/songs",
"wishlist": "/v1/user/wishlist"
}
}
Without a key
{
"error": {
"code": "unauthorized",
"message": "Missing X-Api-Key header."
}
}
With an unknown key
{
"error": {
"code": "unauthorized",
"message": "Unknown API key."
}
}
Unknown sub-resource
{
"error": {
"code": "not_found",
"message": "Unknown sub resource: /v1/user/nonsense. Available: avatar, banner, score, currencies, level, songs, wishlist, records, ranked, tasks, mail, board, battlepass, savings, wheel, rewards, activities, online, novice, monthcard."
}
}
GET
/v1/user/avatar
Your avatar ID and image
X-Api-Key
Your avatar ID and the image path that serves it.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/avatar"
Avatar
{
"gamer_id": 100000001,
"avatar_id": 100100017,
"image": "/v1/avatars/100100017"
}
GET
/v1/user/banner
Your banner and its accent colour
X-Api-Key
Your profile banner, its decoration type and the accent colour the client draws on top of it.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/banner"
Banner
{
"gamer_id": 100000001,
"banner_id": 100300006,
"type": "banner",
"image": "/v1/banners/100300006",
"color": "#073A5C"
}
GET
/v1/user/score
Total score and global rank
X-Api-Key
Your total score and where it puts you on the global board.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/score"
Score
{
"gamer_id": 100000001,
"total_score": 9148630,
"rank": {
"order": 27,
"show_type": 0
}
}
GET
/v1/user/currencies
Wallet and owned decorations
X-Api-Key
Every currency and item you hold, plus the avatars, emojis and banners you own.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/currencies"
Wallet
{
"gamer_id": 100000001,
"currencies": [
{
"cfg_id": 110600001,
"key": "lucky_box_l1",
"name": "Lucky Box",
"icon": "/v1/items/Surprise_Box",
"count": 0
},
{
"cfg_id": 110300003,
"key": "premiere_box_fragment",
"name": "Premiere Fragments",
"icon": "/v1/items/Icon_HiFiChip",
"count": 3
},
"... 11 more entries of the same shape"
],
"decorations": [
{
"cfg_id": 0,
"count": 0,
"type": null,
"image": null,
"color": null
},
{
"cfg_id": 0,
"count": 0,
"type": null,
"image": null,
"color": null
}
]
}
GET
/v1/user/level
Level, experience and unlocks
X-Api-Key
Level, experience, the score the next level asks for, when you reached the current one and which features that unlocked.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/level"
Level
{
"gamer_id": 100000001,
"level": 47,
"exp": 9148630,
"total_score": 9148630,
"reached_at": {
"level": 47,
"total_score": 9000000,
"task_id": null,
"rewards": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 10
},
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 10
}
]
},
"next_level": {
"level": 48,
"total_score": 9300000,
"task_id": null,
"rewards": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 10
},
{
"cfg_id": 110300002,
"kind": "item",
"key": "wish_box_fragment",
"name": "Wish Fragments",
"icon": "/v1/items/Icon_WishBoxChip",
"count": 10
}
],
"score_missing": 151370
},
"received_level": 47,
"task": null,
"unlocked_modules": [
{
"module": 13,
"key": "box_repeat_song",
"level": 6
},
{
"module": 1,
"key": "battle_pass",
"level": 9
},
"... 22 more entries of the same shape"
]
}
GET
/v1/user/songs
The songs you own
X-Api-Key
The songs you own, resolved against the catalog. unknown counts songs the game has handed out that this catalog version does not know yet.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/songs"
Owned songs
{
"gamer_id": 100000001,
"total": 110,
"unknown": 1,
"catalog_version": "0.2.4",
"songs": [
{
"song_id": 10001,
"raw_id": 120110001,
"name": "Titanium",
"artist": "David Guetta ft. Sia",
"bpm": 126,
"style": {
"id": 4,
"name": "EDM",
"color": "#B145FF"
},
"cover": "/v1/songs/10001/cover",
"level": {
"id": 3,
"name": "Diamond",
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130
},
"count": 4,
"known": true
},
{
"song_id": 10002,
"raw_id": 120110002,
"name": "Gangnam Style",
"artist": "PSY",
"bpm": 132,
"style": {
"id": 0,
"name": "Pop",
"color": "#FF63A1"
},
"cover": "/v1/songs/10002/cover",
"level": {
"id": 3,
"name": "Diamond",
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130
},
"count": 4,
"known": true
},
"... 108 more entries of the same shape"
],
"displayed": []
}
GET
/v1/user/wishlist
Marked vault songs
X-Api-Key
The song IDs you marked in the Hi-Fi Vault.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/wishlist"
Wishlist
{
"gamer_id": 100000001,
"total": 15,
"song_ids": [120110009, 120110012]
}
GET
/v1/user/records
Best result per song and difficulty
X-Api-Key
Your best result per song and difficulty: score, accuracy, combo and grade, with the song resolved from the catalog.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/records"
Records
{
"gamer_id": 100000001,
"total": 264,
"catalog_version": "0.2.4",
"records": [
{
"song_id": 10044,
"name": "Shape of You",
"artist": "Ed Sheeran",
"cover": "/v1/songs/10044/cover",
"score": 100000,
"grade": 3,
"times_played": 6,
"progress": 10000,
"enhance_level": 0,
"unlocked_at": "2026-07-10T06:55:39Z",
"last_played": "2026-09-05T14:16:14Z"
},
{
"song_id": 10076,
"name": "Heavy",
"artist": "Linkin Park feat. Kiiara",
"cover": "/v1/songs/10076/cover",
"score": 100000,
"grade": 3,
"times_played": 5,
"progress": 10000,
"enhance_level": 0,
"unlocked_at": "2026-06-15T23:44:34Z",
"last_played": "2026-09-05T14:02:08Z"
},
"... 262 more entries of the same shape"
]
}
GET
/v1/user/tasks
The level task you are on
X-Api-Key
The level task you are currently on, or null with a note when you have finished the chain.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/tasks"
All tasks finished
{
"gamer_id": 100000001,
"finished": true,
"task": null
}
GET
/v1/user/mail
Inbox with attachments
X-Api-Key
Your inbox with the full message body, whether it was read and whether its attachment was collected.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/mail"
Inbox
{
"gamer_id": 100000001,
"total": 1,
"unread": 0,
"mail": [
{
"mail_id": "100000001000000004",
"title": "Compensation for Hi-Fi Vault Display Issue",
"body": "Dear Hi Beatz Players,\n\nIn the Hi-Fi Vault rotation that becomes available at 5:00 AM local time on July 9, certain songs may not appear correctly in the All Songs list.\n\nWe have identified this issue and are working to fix it as soon as possible.\n\nWe sincerely apologize for the inconvenience this may cause. As compensation, we will send Vault Tickets ×10 to all players.\n\nThank you for your understanding and support.\n\nBest regards,\nHi Beatz Team",
"type": 1,
"read": true,
"collected": true,
"sent_at": "2026-07-09T07:58:06Z",
"expires_at": null,
"attachments": [
{
"cfg_id": 110100004,
"key": "vault_ticket",
"name": "Vault Tickets",
"icon": "/v1/items/Icon_GoldPrize",
"count": 10
}
]
}
]
}
GET
/v1/user/battlepass
Your pass, coins and its task list
X-Api-Key
Your battle pass: period, tier, level, coins earned this week and the task list that pays them.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/battlepass"
Battle pass
{
"gamer_id": 100000001,
"battle_pass": {
"period": 10,
"tier": {
"id": 1,
"name": "premium"
},
"level": 22,
"coins": {
"total": 610,
"week": 500
},
"ends_at": "2026-10-11T20:59:59Z",
"week_refresh_at": "2026-09-07T03:00:00Z",
"day_refresh_at": "2026-09-06T03:00:00Z",
"rewards": {
"free": [
{
"cfg_id": 451,
"key": null,
"name": null,
"icon": null
},
{
"cfg_id": 452,
"key": null,
"name": null,
"icon": null
},
"... 20 more entries of the same shape"
],
"premium": [
{
"cfg_id": 451,
"key": null,
"name": null,
"icon": null
},
{
"cfg_id": 452,
"key": null,
"name": null,
"icon": null
},
"... 20 more entries of the same shape"
]
},
"tasks": {
"week": [
{
"task_id": 41,
"state": {
"id": 2,
"name": "collected"
},
"progress": 100,
"name": "Spin 100 Times in Total",
"target": 100000100,
"rewards": [
{
"cfg_id": 150100001,
"kind": "item",
"key": "battle_pass_coin",
"name": "Season Coins",
"icon": "/v1/items/Icon_lotteryCoin",
"count": 50
}
]
},
{
"task_id": 42,
"state": {
"id": 2,
"name": "collected"
},
"progress": 3,
"name": "Obtain 3 Song Boxes",
"target": 100000101,
"rewards": [
{
"cfg_id": 150100001,
"kind": "item",
"key": "battle_pass_coin",
"name": "Season Coins",
"icon": "/v1/items/Icon_lotteryCoin",
"count": 50
}
]
},
"... 4 more entries of the same shape"
],
"day": [
{
"task_id": 48,
"state": {
"id": 2,
"name": "collected"
},
"progress": 1,
"name": "Achieve S or Higher on 1 Song",
"target": 100000106,
"rewards": [
{
"cfg_id": 150100001,
"kind": "item",
"key": "battle_pass_coin",
"name": "Season Coins",
"icon": "/v1/items/Icon_lotteryCoin",
"count": 10
}
]
},
{
"task_id": 49,
"state": {
"id": 2,
"name": "collected"
},
"progress": 1,
"name": "Obtain 1 Song Box",
"target": 100000106,
"rewards": [
{
"cfg_id": 150100001,
"kind": "item",
"key": "battle_pass_coin",
"name": "Season Coins",
"icon": "/v1/items/Icon_lotteryCoin",
"count": 15
}
]
},
"... 2 more entries of the same shape"
]
},
"peak": null,
"next_level": {
"level": 23,
"coin": 650,
"bonus": 0,
"song": {
"song_id": 10128,
"name": "Boulevard Of Broken Dreams",
"artist": "Green Day",
"cover": "/v1/songs/10128/cover"
},
"free": [
{
"cfg_id": 110300002,
"kind": "item",
"key": "wish_box_fragment",
"name": "Wish Fragments",
"icon": "/v1/items/Icon_WishBoxChip",
"count": 5
}
],
"premium": [
{
"cfg_id": 110300002,
"kind": "item",
"key": "wish_box_fragment",
"name": "Wish Fragments",
"icon": "/v1/items/Icon_WishBoxChip",
"count": 15
}
],
"coin_missing": 40
},
"track": "/v1/battlepass?period=10"
}
}
GET
/v1/user/savings
The savings jar
X-Api-Key
The savings jar: how much has accumulated, its cap, how many collections are left and when it refreshes.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/savings"
Savings
{
"gamer_id": 100000001,
"savings": {
"count": 0,
"count_limit": 15,
"delta": 0,
"state": {
"id": 2,
"name": "collected"
},
"collections": {
"remaining": 0,
"limit": 4
},
"refreshes_at": "2026-09-06T03:00:00Z",
"reward_expires_at": null
}
}
GET
/v1/user/wheel
Wheel counters, pity list and vault
X-Api-Key
Prize wheel counters and the Hi-Fi Vault: spins so far, how many draws until a guaranteed gold or purple result, and the songs the current vault rotation offers. guarantee lists the four pity milestones at 50, 100, 200 and 400 spins and marks the ones already handed out. It is null per entry when the game server declines that one extra request; the rest of the answer is unaffected.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/wheel"
Wheel and vault
{
"gamer_id": 100000001,
"wheel": {
"guaranteed": {
"gold": 37,
"purple": 2
},
"spins": 424,
"refreshes_at": "2026-09-07T03:00:00Z"
},
"vault": {
"period_id": 10,
"safety": {
"gold_times": 81,
"buff": 1,
"stashed": 5,
"draw_count": 1155,
"ends_at": "2026-09-09T20:00:00Z"
},
"songs": [
{
"song_id": 10140,
"name": "Get A Job",
"artist": "Games We Play",
"cover": "/v1/songs/10140/cover",
"type": {
"id": 0,
"name": "normal"
}
},
{
"song_id": 10139,
"name": "Modern Love",
"artist": "David Bowie",
"cover": "/v1/songs/10139/cover",
"type": {
"id": 0,
"name": "normal"
}
},
"... 4 more entries of the same shape"
],
"showcase": [
{
"cfg_id": 100100019,
"key": null,
"name": null,
"icon": null
},
{
"cfg_id": 100300007,
"key": null,
"name": null,
"icon": null
}
]
}
}
The guarantee block
{
"guarantee": [
{
"id": 1,
"after_spins": 50,
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 20,
"collected": true
},
{
"id": 2,
"after_spins": 100,
"cfg_id": 110300002,
"kind": "item",
"key": "wish_box_fragment",
"name": "Wish Fragments",
"icon": "/v1/items/Icon_WishBoxChip",
"count": 20,
"collected": true
},
{
"id": 3,
"after_spins": 200,
"cfg_id": 110200001,
"kind": "item",
"key": "normal_box_l1",
"name": "Song Box",
"icon": "/v1/items/Icon_Box",
"count": 1,
"collected": false
},
{
"id": 4,
"after_spins": 400,
"cfg_id": 110200002,
"kind": "item",
"key": "wish_box",
"name": "Wish Song Box",
"icon": "/v1/items/Icon_WishBox",
"count": 1,
"collected": false
}
]
}
GET
/v1/user/rewards
Daily allowances left
X-Api-Key
The daily allowances: revives left, free ten-draws, the diamond claim and how many trial songs you have left today.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/rewards"
Daily allowances
{
"gamer_id": 100000001,
"rewards": {
"revives_remaining": 0,
"ten_draws_remaining": 0,
"diamond_state": {
"id": 2,
"name": "collected"
},
"trial_song": {
"remaining": 3,
"total": 3
},
"refreshes_at": "2026-09-06T03:00:00Z"
}
}
GET
/v1/user/activities
Daily and weekend activities
X-Api-Key
The daily and weekend activity list with progress, the coins each one pays into the battle pass and the rewards attached.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/activities"
Activities
{
"gamer_id": 100000001,
"coin": 270,
"collected": [1, 2],
"activities": [
{
"cfg_id": 1,
"state": {
"id": 2,
"name": "collected"
},
"weekend": false,
"progress": 1,
"name": "Log in daily",
"target": 100000106,
"type": 1,
"activity_coin": 15,
"battle_pass_coin": 15,
"icon": "/v1/items/Icon_Prize",
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
]
},
{
"cfg_id": 2,
"state": {
"id": 2,
"name": "collected"
},
"weekend": false,
"progress": 1,
"name": "Play any song once",
"target": 100000106,
"type": 5,
"activity_coin": 15,
"battle_pass_coin": 15,
"icon": "/v1/items/Icon_Prize",
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
]
},
"... 4 more entries of the same shape"
],
"refreshes": {
"day": "2026-09-06T03:00:00Z",
"week": "2026-09-07T03:00:00Z"
}
}
GET
/v1/user/online
Play-time reward track
X-Api-Key
The play-time reward track: when the session started and which of the fifteen time-based rewards are still open.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/online"
Online rewards
{
"gamer_id": 100000001,
"online": {
"started_at": "2026-09-05T06:59:20Z",
"refreshes_at": "2026-09-06T03:00:00Z",
"awards": [
{
"cfg_id": 1,
"state": 2,
"order": 1,
"minutes": 1,
"ad": false,
"rewards": [
{
"cfg_id": 110300001,
"kind": "item",
"key": "normal_box_fragment",
"name": "Song Fragments",
"icon": "/v1/items/Icon_BoxChip",
"count": 5
}
]
},
{
"cfg_id": 2,
"state": 2,
"order": 2,
"minutes": 2,
"ad": false,
"rewards": [
{
"cfg_id": 110100002,
"kind": "item",
"key": "lottery_coin",
"name": "Lucky Ticket",
"icon": "/v1/items/Icon_Prize",
"count": 2
}
]
},
"... 13 more entries of the same shape"
]
}
}
GET
/v1/user/novice
Newcomer calendar and tasks
X-Api-Key
The newcomer login calendar and the first weeks of tasks. Ends for good once you finish it, which is what the example shows.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/novice"
Finished
{
"gamer_id": 100000001,
"login": {
"finished": true,
"today": null,
"refreshes_at": null,
"days": []
},
"tasks": {
"state": {
"id": 2,
"name": "ended"
},
"today": null,
"week": null,
"refreshes_at": null,
"ends_at": null,
"weeks": []
}
}
GET
/v1/user/monthcard
Monthly card state
X-Api-Key
Whether a monthly card is active, which level it is, when it ends and when it next pays out.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/monthcard"
Monthly card
{
"gamer_id": 100000001,
"month_card": {
"active": true,
"type": {
"id": 1,
"name": "level_1"
},
"ends_at": "2026-09-08T23:57:51Z",
"next_award_at": "2026-09-06T03:00:00Z",
"newly_purchased": false
}
}
GET
/v1/user/events
Events running for you
X-Api-Key
The events running for you right now, with each gift store's offers and how many of each you have left. The game server reports numbers only; the titles and the offers come from the config. This costs no extra request: the event block travels in the same bundle as the tasks and the month card.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/events"
Events and a gift store
{
"gamer_id": 100000001,
"server_time": "2026-09-27T20:00:00Z",
"events": [
{
"activity_id": 10002,
"template": {
"id": 1,
"name": "gift_store"
},
"config_id": 2,
"starts_at": "2026-09-22T03:00:00Z",
"ends_at": "2026-09-29T02:59:59Z",
"widget_position": [2],
"gift_store": {
"config_id": 2,
"title": "Hi-Fi Booster Rush",
"subtitle": "Boosters on sale for a limited time—Stock up on boosters and speed up your vault progress!",
"completed": false,
"items": [
{
"bundle_item_id": 4,
"enabled": true,
"commodity_id": 140400001,
"limit": 1,
"limit_refresh": 1,
"discount_percent": 0,
"title": "Free Gift",
"commodity": {
"shop_id": 1404,
"commodity_id": 140400001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 5
}
],
"diamonds": null,
"price": null,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
},
"bought": 0,
"left": 1,
"next_refresh_at": null
},
{
"bundle_item_id": 5,
"enabled": true,
"commodity_id": 130600001,
"limit": 1,
"limit_refresh": 0,
"discount_percent": 85,
"title": "Starter Pack",
"commodity": {
"shop_id": 1306,
"commodity_id": 130600001,
"goods": [
{
"cfg_id": 150200001,
"kind": "item",
"key": "limited_vault_ticket",
"name": "Vault Tickets (Timed)",
"icon": null,
"count": 20
}
],
"diamonds": null,
"price": 0.29,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Prize1"
},
"bought": 0,
"left": 1,
"next_refresh_at": null
},
"... 5 more entries of the same shape"
]
}
}
]
}
GET
/v1/shop
This week's shop slots
X-Api-Key
The shop slots of the current week and what each one sells, priced in diamonds or in money, together with the one-time offers.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/shop"
Shop
{
"gamer_id": 100000001,
"total": 7,
"slots": [
{
"shop_id": 1301,
"time_limit": false,
"one_time": false,
"starts_at": null,
"ends_at": null,
"commodities": [
{
"commodity_id": 130100001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 50
}
],
"diamonds": null,
"price": 0.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem1"
},
{
"commodity_id": 130100002,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 275
}
],
"diamonds": null,
"price": 4.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem2"
},
"... 4 more entries of the same shape"
]
},
{
"shop_id": 1302,
"time_limit": false,
"one_time": true,
"starts_at": null,
"ends_at": null,
"commodities": [
{
"commodity_id": 130210001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 15
}
],
"diamonds": null,
"price": 9.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem6"
}
]
},
"... 5 more entries of the same shape"
],
"one_time_commodities": [
{
"shop_id": 1302,
"commodity_id": 130210001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 15
}
],
"diamonds": null,
"price": 9.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem6"
},
{
"shop_id": 1304,
"commodity_id": 130400001,
"goods": [
{
"cfg_id": 110100001,
"kind": "item",
"key": "diamond",
"name": "Diamond",
"icon": "/v1/items/Icon_Diamond01",
"count": 7500
}
],
"diamonds": null,
"price": 0.99,
"product_type": 0,
"original_count": 0,
"icon": "/v1/items/Gem6"
}
],
"one_time_products": []
}
Competitive
X-Api-Key 6Ranked seasons, boards and the journey mode, all read only.
GET
/v1/leaderboard
Ranked board with names resolved
X-Api-Key
A ranked board with the player names resolved. Your own placing comes back in self even when it falls outside the returned page.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
rank | string | required | global, region, season_global or season_region |
region | string | your own | Country code, for the two region boards. Must be one of /v1/regions |
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/leaderboard?rank=global"
Global
{
"leaderboard": "global",
"region": null,
"total": 200,
"self": {
"gamer_id": 100000001,
"rank": 480,
"score": 9148630,
"show_type": 0
},
"entries": [
{
"rank": 1,
"gamer_id": 100000108,
"username": "Player12201",
"avatar_id": 100100019,
"avatar": "/v1/avatars/100100019",
"level": 59,
"region": {
"code": "CN",
"name": "China",
"flag": "/v1/flags/cn"
},
"score": 12700000
},
{
"rank": 2,
"gamer_id": 100000109,
"username": "Player12202",
"avatar_id": 100100005,
"avatar": "/v1/avatars/100100005",
"level": 59,
"region": {
"code": "CO",
"name": "Colombia",
"flag": "/v1/flags/co"
},
"score": 12700000
},
"... 198 more entries of the same shape"
]
}
One region
{
"leaderboard": "region",
"region": "DE",
"total": 200,
"self": {
"gamer_id": 100000001,
"rank": 27,
"score": 9148630,
"show_type": 0
},
"entries": [
{
"rank": 1,
"gamer_id": 100000124,
"username": "Player122494",
"avatar_id": 100100017,
"avatar": "/v1/avatars/100100017",
"level": 54,
"region": {
"code": "DE",
"name": "Germany",
"flag": "/v1/flags/de"
},
"score": 11295137
},
{
"rank": 2,
"gamer_id": 100000150,
"username": "Player122495",
"avatar_id": 100100041,
"avatar": "/v1/avatars/100100041",
"level": 53,
"region": {
"code": "DE",
"name": "Germany",
"flag": "/v1/flags/de"
},
"score": 10897715
},
"... 198 more entries of the same shape"
]
}
Season board
{
"leaderboard": "season_global",
"region": null,
"total": 200,
"self": {
"gamer_id": 100000001,
"rank": 132,
"score": 80,
"show_type": 0
},
"entries": [
{
"rank": 1,
"gamer_id": 100000109,
"username": "Player122496",
"avatar_id": 100100005,
"avatar": "/v1/avatars/100100005",
"level": 59,
"region": {
"code": "CO",
"name": "Colombia",
"flag": "/v1/flags/co"
},
"score": 637
},
{
"rank": 2,
"gamer_id": 100000140,
"username": "Player122497",
"avatar_id": 100100014,
"avatar": "/v1/avatars/100100014",
"level": 53,
"region": {
"code": "BR",
"name": "Brazil",
"flag": "/v1/flags/br"
},
"score": 556
},
"... 198 more entries of the same shape"
]
}
Unknown board
{
"error": {
"code": "api_error",
"message": "Unknown leaderboard 'nonsense'. Available: global, region, season_global, season_region."
}
}
GET
/v1/user/ranked
Your league, score and match history
X-Api-Key
Your league, ranked score, match points, the season state and your recent matches. Every match carries the link to its own detail.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/ranked"
Ranked standing
{
"gamer_id": 100000001,
"season_state": {
"id": 2,
"name": "running"
},
"season_id": 1,
"tier": {
"id": 1032,
"name": "Expert4",
"group": "Expert",
"division": 4,
"colors": {
"primary": "#5CC9F8",
"gradient": "#0D195C"
},
"badge": "/v1/tiers/expert",
"point_conversion_percent": 100
},
"ranked_score": 80,
"match_points": 33,
"week_refresh": "2026-09-07T09:00:00Z",
"matches": [
{
"match_id": 1694459311134658515,
"detail": "/v1/user/ranked/matches/1694459311134658515",
"rank_order": 10,
"played_at": "2026-09-06T02:04:14Z",
"score_after": 80,
"score_before": 80,
"total_score": 99914
},
{
"match_id": 1694459310295797206,
"detail": "/v1/user/ranked/matches/1694459310295797206",
"rank_order": 3,
"played_at": "2026-09-06T01:46:18Z",
"score_after": 80,
"score_before": 78,
"total_score": 75000
},
"... 28 more entries of the same shape"
]
}
GET
/v1/user/ranked/match
The match running right now
X-Api-Key
The match running right now with its opponents and their current scores, or null when nothing is running.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/ranked/match"
Current match
{
"gamer_id": 100000001,
"match": {
"match_id": 1694459311788969159,
"state": 1,
"start": "2026-09-06T02:04:29Z",
"end": "2026-09-06T02:19:29Z",
"song_tags": [
{
"tag_id": 10003,
"name": "Pop",
"family": "style",
"buff": [15, 10, 5]
},
{
"tag_id": 40004,
"name": "Premium",
"family": "edition",
"buff": [15, 10, 5]
}
],
"rechoose_left": 1
},
"opponents": []
}
GET
/v1/user/ranked/matches/{id}
One finished match in full
X-Api-Key
One finished match: every placing with score and reward, the tags the round ran under and the matchmaking rule set it used.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
id | int | required | Match ID from /v1/user/ranked |
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/ranked/matches/1694459311134658515"
Finished match
{
"gamer_id": 100000001,
"match": {
"match_id": 1694459311134658515,
"played_at": "2026-09-06T02:04:14Z",
"tier": {
"id": 1032,
"name": "Expert4",
"group": "Expert",
"division": 4,
"colors": {
"primary": "#5CC9F8",
"gradient": "#0D195C"
},
"badge": "/v1/tiers/expert",
"point_conversion_percent": 100
},
"tier_before": {
"id": 1032,
"name": "Expert4",
"group": "Expert",
"division": 4,
"colors": {
"primary": "#5CC9F8",
"gradient": "#0D195C"
},
"badge": "/v1/tiers/expert",
"point_conversion_percent": 100
},
"ranked_score": 80,
"match_points": 33,
"winning_rank": 5,
"matchmaking": {
"round_minutes": 15,
"ending_minutes": 1,
"songs": 1,
"song_level": 3,
"reroll_limit": 1,
"board_size": 15,
"dummy_size": 2,
"promote_above": 5,
"demote_below": 11,
"points": {
"win": 100,
"draw": 0,
"lose": -100
},
"fixed_tags": [],
"random_tags": [
{
"tag_id": 10001,
"name": "Rock",
"family": "style",
"buff": [15, 10, 5]
},
{
"tag_id": 10002,
"name": "R&B",
"family": "style",
"buff": [15, 10, 5]
},
"... 12 more entries of the same shape"
],
"random_tag_count": 2,
"fatigue": {
"enabled": true,
"gate": 1,
"penalty_base": 10,
"penalty_mod": 5
},
"debuff_base_score": 5,
"max_debuff": 5,
"matchmaking_id": 11
},
"song_tags": [
{
"tag_id": 40004,
"name": "Premium",
"family": "edition",
"buff": [15, 10, 5]
},
{
"tag_id": 10004,
"name": "EDM",
"family": "style",
"buff": [15, 10, 5]
}
]
},
"placings": [
{
"rank": 1,
"gamer_id": 100000176,
"username": "Player122500",
"avatar_id": 100100041,
"avatar": "/v1/avatars/100100041",
"banner_id": 100300011,
"total_score": 100000,
"tolerance": 2000,
"tolerance_percent": 20
},
{
"rank": 2,
"gamer_id": 100000569,
"username": "Player122501",
"avatar_id": 100100038,
"avatar": "/v1/avatars/100100038",
"banner_id": 100300004,
"total_score": 100000,
"tolerance": 1000,
"tolerance_percent": 10
},
"... 13 more entries of the same shape"
]
}
Unknown match
{
"gamer_id": 100000001,
"match": null,
"note": "No match 1 on record for this account."
}
GET
/v1/user/board
Journey progress
X-Api-Key
Progress in the journey mode with each board's name, badge and rewards. Between seasons the game server has nothing to hand out, which the API reports as a note rather than as an error.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/user/board"
No season running
{
"gamer_id": 100000001,
"boards": null,
"note": "No board season running at the moment.",
"upstream": {
"code": 500,
"message": "Game server returned error 500"
}
}
GET
/v1/challenge
The running song challenge
X-Api-Key
The running song challenge, your result in it and the period's task list. Like the boards, it answers a note when no challenge is live.
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/challenge"
No challenge running
{
"gamer_id": 100000001,
"challenge": null,
"note": "No song challenge running at the moment.",
"upstream": {
"code": 500,
"message": "Game server returned error 500"
}
}
Other players
X-Api-Key 2Public profiles of other players, exactly as the game shows them in a search.
GET
/v1/players/{gamer_id}
Public profile of another player
X-Api-Key
A public profile: name, level, region, avatar and banner, how many songs the player owns, their results per difficulty and their most played songs.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
gamer_id | int | required | Numeric player ID |
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/players/100000108"
Public profile
{
"gamer_id": 100000108,
"username": "Player122499",
"unique_code": "HB7K2Q",
"level": 59,
"region": {
"code": "CN",
"name": "China",
"flag": "/v1/flags/cn"
},
"gender": 3,
"avatar_id": 100100019,
"avatar": "/v1/avatars/100100019",
"banner_id": 100300007,
"songs_owned": 0,
"by_level": [
{
"level": {
"id": 1,
"name": "Standard",
"perfect_plus_ms": 70,
"perfect_ms": 140,
"great_ms": 190
},
"avg_score": 50000,
"max_score": 50000,
"songs": 127
},
{
"level": {
"id": 2,
"name": "Gold",
"perfect_plus_ms": 60,
"perfect_ms": 120,
"great_ms": 160
},
"avg_score": 75000,
"max_score": 75000,
"songs": 127
},
"... 1 more entries of the same shape"
],
"most_played": [
{
"song_id": 10093,
"name": "Bangarang",
"level": {
"id": 3,
"name": "Diamond",
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130
},
"times": 625
},
{
"song_id": 10003,
"name": "Wake Me Up",
"level": {
"id": 3,
"name": "Diamond",
"perfect_plus_ms": 50,
"perfect_ms": 100,
"great_ms": 130
},
"times": 243
},
"... 3 more entries of the same shape"
]
}
GET
/v1/players/search
Find players by name or code
X-Api-Key
Search players by name or by unique code.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
q | string | required | Name fragment or unique code |
curl -H "X-Api-Key: $KEY" \ "https://api.beatz-nodefox.de/v1/players/search?q=a"
Search
{
"query": "a",
"total": 20,
"players": [
{
"gamer_id": 100000607,
"username": "Player122498",
"unique_code": "HB7K2Q",
"level": 0,
"region": {
"code": "US",
"name": "United States",
"flag": "/v1/flags/us"
},
"avatar_id": 100100001,
"avatar": "/v1/avatars/100100001",
"profile": "/v1/players/154804973"
},
{
"gamer_id": 100000608,
"username": "Player122498",
"unique_code": "HB7K2Q",
"level": 0,
"region": {
"code": "US",
"name": "United States",
"flag": "/v1/flags/us"
},
"avatar_id": 100100034,
"avatar": "/v1/avatars/100100034",
"profile": "/v1/players/130635978"
},
"... 18 more entries of the same shape"
]
}
Without a query
{
"error": {
"code": "api_error",
"message": "Query parameter q is required."
}
}
The catalog examples are rendered from the current game build, Android22-20260928-1535. The account examples were recorded against the live API earlier; the account's own identifiers and all player names were replaced while recording. Long lists are cut short in every example.
Errors
Every failure answers the same shape, so one handler covers all of them.
{
"error": {
"code": "unauthorized",
"message": "Missing X-Api-Key header."
}
}