Hi Beatz API
Public REST API · v1

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.

Base URL https://api.beatz-nodefox.de
168songs in the catalog
61endpoints
151cover images
247country flags

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"
    }
  ]
}
Only metadata and artwork. The game's audio files are licensed label music and are never served by this API.

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.

0 · Pop 1 · Rock 2 · Hip Hop 3 · R&B 4 · EDM

Timing windows per difficulty

How many milliseconds early or late a tap may be and still count as Perfect. Available at /v1/levels.

LevelNamePerfectGreat
1Standard±140 ms±190 ms
2Gold±120 ms±160 ms
3Diamond±100 ms±130 ms
4Diamond±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.

FieldMeaning
cfg_idThe game's own number, e.g. 110100002
keyStable machine name, e.g. lottery_coin. Safe to switch on in code
nameWhat the game calls it on screen, e.g. Lucky Ticket
iconURL 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:

kindExampleWhat you get
item110100001A currency, box or fragment. key, name and the item icon
song120110022A song. The title, the artist, its cover and song_id for the catalog
decoration100300009An 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.

GradeWeightAs a shareShown in the app
1 · rainbow1 / 100000.01 %0.01 %
2 · MYTHIC50 / 100000.5 %1.5 %
3 · SPECIAL300 / 100003 %5 %
4 · RARE3100 / 1000031 %30 %
5 · COMMON6549 / 1000065.49 %63.49 %
Both columns come straight out of the game config. The API reports them as 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:

FieldWhereWhy it is left alone
buff/v1/tagsThree numbers per tag, [15, 10, 5]. The config never says what separates them
original_count/v1/shopRuns 20 to 100 while the diamond price runs 5 to 2500, so it is neither a former price nor an amount
dummy_sizematchmaking rulesFalls 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.

SongStyleBPMTaps / second
GDFRHip Hop1468.61
BangarangEDM2207.49
Know Your EnemyRock1306.83
SpitfireEDM1576.57
WhistleHip Hop1046.56
Statistics, not the chart itself. The note timings stay out of this API. They are what an automated player would need, and the same API serves the leaderboards it would be competing on.

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.

  1. 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.

  2. 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.

  3. 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.py into that folder next to adb. 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.

  4. Read the two values

    The script reads the game's save file from Android/data, where adb is allowed to look without root, and pulls gamer_id and token out 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
  5. 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'
If your phone blocks the folder. The save file always sits in the same place, because that path comes from the game and not from Android. What differs between phones is whether adb may read /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.
The script writes nothing. The save file is read into memory, the two values are picked out, and it is dropped when the script exits. Nothing is stored on your computer and nothing is changed on your phone.
The game token rotates on every login. Logging in on your phone invalidates the old one, so run /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.

Download hb_token.py Python 3.8 or newer · 585 lines · MIT
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.

61 endpoints

Getting started

No key required 3

One 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.

ParameterTypeDefaultMeaning
gamer_idintrequiredYour numeric player ID
tokenintrequiredThe 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 call200/v1/auth
{
  "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 token200/v1/auth
{
  "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 body400/v1/auth
{
  "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 root200/v1
{
  "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 status200/v1/status
{
  "maintenance": false,
  "notice": null,
  "checked_at": "2026-09-06T02:16:29Z"
}

Catalog

No key required 11

The 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"
Summary200/v1/meta
{
  "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.

ParameterTypeDefaultMeaning
qstring—Substring of the title or the artist, case insensitive
styleint—Music style ID, see /v1/styles
bpm_minint—Lowest BPM to include
bpm_maxint—Highest BPM to include
visiblebool—Only songs the client actually shows
tagint—Ranked tag ID, see /v1/tags
vaultbool—Only songs obtainable from the Hi-Fi Vault
releasedbool—true for songs that carry a release date, false for those without one
sortstringidid, name, artist, bpm, density, duration or released
limitint—Page size. Omit it to get everything
offsetint0How many songs to skip
curl "https://api.beatz-nodefox.de/v1/songs?limit=3"
Three songs200/v1/songs?limit=3
{
  "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 artist200/v1/songs?q=bruno+mars
{
  "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 visibility200/v1/songs?style=4&bpm_min=120&bpm_max=140&visible=true
{
  "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 density200/v1/songs?sort=density&limit=2
{
  "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 date200/v1/songs?sort=released&limit=2
{
  "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 tag200/v1/songs?tag=50006&limit=2
{
  "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 songs200/v1/songs?vault=true&limit=2
{
  "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 date200/v1/songs?released=false&limit=2
{
  "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 page200/v1/songs?limit=2&offset=10
{
  "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 key400/v1/songs?sort=nonsense
{
  "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.

ParameterTypeDefaultMeaning
idintrequiredSong ID, four or five digits
curl "https://api.beatz-nodefox.de/v1/songs/10137"
One song200/v1/songs/10137
{
  "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 song404/v1/songs/99999
{
  "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.

ParameterTypeDefaultMeaning
idintrequiredSong ID
curl "https://api.beatz-nodefox.de/v1/songs/10001/chart"
Chart statistics200/v1/songs/10001/chart
{
  "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 song404/v1/songs/99999/chart
{
  "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.

ParameterTypeDefaultMeaning
idintrequiredSong ID
levelintallOne 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, Standard200/v1/songs/10001/notes?level=1
{
  "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 index200/v1/sounds
{
  "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 styles200/v1/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"
Editions200/v1/levels
{
  "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 families200/v1/tags
{
  "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 codes200/v1/regions
{
  "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 tips200/v1/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 11

Season 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 model200/v1/scoring
{
  "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"
Seasons200/v1/ranked/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.

ParameterTypeDefaultMeaning
seasonintcurrentSeason ID, see /v1/ranked/seasons
curl "https://api.beatz-nodefox.de/v1/ranked/tiers"
Current season200/v1/ranked/tiers
{
  "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 1200/v1/ranked/tiers?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 season400/v1/ranked/tiers?season=99
{
  "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 sets200/v1/ranked/matchmaking
{
  "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 levels200/v1/progression
{
  "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.

ParameterTypeDefaultMeaning
periodintcurrentBattle pass period, 1 upwards
curl "https://api.beatz-nodefox.de/v1/battlepass"
Current period200/v1/battlepass
{
  "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 1200/v1/battlepass?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 period400/v1/battlepass?period=99
{
  "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"
Boards200/v1/journey
{
  "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 levels200/v1/modules
{
  "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"
Economy200/v1/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 templates200/v1/events
{
  "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 tables200/v1/odds
{
  "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 7

Every 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"
RequestStatusTypeSize
/v1/songs/10001/cover200image/webp25,084 bytes
/v1/songs/99999/cover404application/json112 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"
RequestStatusTypeSize
/v1/avatars/1200image/webp4,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"
RequestStatusTypeSize
/v1/banners/100300001200image/webp25,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"
RequestStatusTypeSize
/v1/tiers/legend200image/webp70,544 bytes
/v1/tiers/nosuchleague404application/json110 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"
RequestStatusTypeSize
/v1/flags/de200image/webp294 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"
RequestStatusTypeSize
/v1/sounds/ButtonClick01200audio/ogg8,277 bytes
/v1/sounds/NoSuchSound404application/json113 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"
RequestStatusTypeSize
/v1/items/Icon_Diamond200image/webp7,754 bytes

Your account

X-Api-Key 21

These 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"
Profile200/v1/user
{
  "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 key401/v1/user
{
  "error": {
    "code": "unauthorized",
    "message": "Missing X-Api-Key header."
  }
}
With an unknown key401/v1/user
{
  "error": {
    "code": "unauthorized",
    "message": "Unknown API key."
  }
}
Unknown sub-resource404/v1/user/nonsense
{
  "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"
Avatar200/v1/user/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"
Banner200/v1/user/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"
Score200/v1/user/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"
Wallet200/v1/user/currencies
{
  "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"
Level200/v1/user/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 songs200/v1/user/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"
Wishlist200/v1/user/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"
Records200/v1/user/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 finished200/v1/user/tasks
{
  "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"
Inbox200/v1/user/mail
{
  "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 pass200/v1/user/battlepass
{
  "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"
Savings200/v1/user/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 vault200/v1/user/wheel
{
  "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 block200/v1/user/wheel
{
  "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 allowances200/v1/user/rewards
{
  "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"
Activities200/v1/user/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 rewards200/v1/user/online
{
  "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"
Finished200/v1/user/novice
{
  "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 card200/v1/user/monthcard
{
  "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 store200/v1/user/events
{
  "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"
Shop200/v1/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 6

Ranked 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.

ParameterTypeDefaultMeaning
rankstringrequiredglobal, region, season_global or season_region
regionstringyour ownCountry 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"
Global200/v1/leaderboard?rank=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 region200/v1/leaderboard?rank=region&region=DE
{
  "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 board200/v1/leaderboard?rank=season_global
{
  "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 board400/v1/leaderboard?rank=nonsense
{
  "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 standing200/v1/user/ranked
{
  "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 match200/v1/user/ranked/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.

ParameterTypeDefaultMeaning
idintrequiredMatch ID from /v1/user/ranked
curl -H "X-Api-Key: $KEY" \
     "https://api.beatz-nodefox.de/v1/user/ranked/matches/1694459311134658515"
Finished match200/v1/user/ranked/matches/1694459311134658515
{
  "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 match200/v1/user/ranked/matches/1
{
  "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 running200/v1/user/board
{
  "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 running200/v1/challenge
{
  "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 2

Public 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.

ParameterTypeDefaultMeaning
gamer_idintrequiredNumeric player ID
curl -H "X-Api-Key: $KEY" \
     "https://api.beatz-nodefox.de/v1/players/100000108"
Public profile200/v1/players/100000108
{
  "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"
  ]
}

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."
  }
}
400bad_requestA required value is missing or out of range
400bad_jsonThe request body is not valid JSON
401unauthorizedKey missing, malformed or unknown, or the game token expired
403api_errorThis endpoint is disabled on this installation
404not_foundNo such route, song, player or image
404no_coverThe song exists but the game loads its artwork separately
502upstream_errorThe game server answered with an error
504upstream_errorThe game server could not be reached
Read only. This API never writes to your account. Every endpoint that would change something — starting matches, collecting rewards, buying, deleting the account — is switched off at the server.