Back to admin panel

Jukebox Radio API

REST API for controlling spots programmatically. Use it to add music, skip tracks, adjust volume, and play announcements from any external app.

Introduction

The Jukebox Radio API lets you build integrations that control your radio spots. Common use cases:

Base URL

https://jukebox.warp-9.app/api/v1

All paths below are relative to the base URL. All request bodies and responses are JSON.

Authentication

Every request must carry your API key in the X-Api-Key header. Keys are generated in the admin panel under API Keys and are scoped to a single tenant — they can only touch that tenant's spots.

# Example with curl
curl https://jukebox.warp-9.app/api/v1/spots \
  -H "X-Api-Key: jbr_your_key_here"

You can also use a Bearer token if that's easier for your HTTP client:

Authorization: Bearer jbr_your_key_here

Keep keys secret. Anyone holding a key can add songs, skip tracks, and change volume on your spots. Rotate a key any time from the admin panel — the old key is immediately invalidated.

Errors

Failed requests return a JSON body with an error field and one of these HTTP status codes:

CodeMeaning
401Missing or invalid API key
400Bad request — check the request body
403Spot is suspended, or belongs to a different tenant
404Spot or queue item not found
413Announcement audio exceeds the 15 MB limit
503Radio engine not ready yet — retry in a moment
# Error response shape
{ "error": "Invalid or revoked API key" }

List spots

GET /spots All spots for this tenant

Returns every spot belonging to the authenticated tenant.

Response
{
  "spots": [
    {
      "id":       12,
      "name":     "Main Hall",
      "location": "Ground floor",
      "joinKey":  "ABCD12",
      "status":   "active"
    }
  ]
}

Get spot state

GET /spots/:spotId/state Current playback state

Returns the live playback state: what's playing now, the full queue, and the current server timestamp.

Response
{
  "now":    1718000000000,          // server epoch ms — use to compute elapsed time
  "current": {
    "id":        "4321",
    "videoId":   "dQw4w9WgXcQ",
    "title":     "Rick Astley - Never Gonna Give You Up",
    "duration":  213,               // seconds
    "startedAt": 1717999900000,    // epoch ms when this track started
    "position":  100.4              // current position in seconds
  },
  "queue": [
    { "id": "4321", "videoId": "dQw4w9WgXcQ", "title": "...", "duration": 213 }
  ]
}

current is null when nothing is playing (empty queue).

Add a song

POST /spots/:spotId/add Append a YouTube video to the queue

Adds a YouTube video to the end of the spot's queue. If nothing is playing, playback starts immediately.

Request body
FieldTypeDescription
url*stringFull YouTube URL — youtu.be or youtube.com/watch?v= or playlist URL
videoIdstring11-char YouTube video ID (alternative to url)

Provide either url or videoId — not both.

Example
curl -X POST https://jukebox.warp-9.app/api/v1/spots/12/add \
  -H "X-Api-Key: jbr_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://youtu.be/dQw4w9WgXcQ"}'
Response
{
  "ok": true,
  "item": {
    "id":       "4322",
    "videoId": "dQw4w9WgXcQ",
    "title":   "Rick Astley - Never Gonna Give You Up",
    "duration": 213
  }
}

Metadata is fetched from YouTube live — this call may take ~1–2 s on the first request for a new video. Audio is downloaded in the background so there's no playback delay.

Skip current song

POST /spots/:spotId/skip Advance to the next track

Skips the currently playing track and advances to the next one in the queue. The queue loops, so after the last track it wraps back to the first.

Example
curl -X POST https://jukebox.warp-9.app/api/v1/spots/12/skip \
  -H "X-Api-Key: jbr_your_key_here"
Response
{ "ok": true }

Set volume

POST /spots/:spotId/volume Set volume for all live listeners

Remotely sets the playback volume on every browser currently tuned into this spot. Volume changes take effect instantly over WebSocket.

Request body
FieldTypeDescription
volume*numberVolume level — 0 (mute) to 1 (full). Decimals are fine, e.g. 0.5.
Example — set to 50%
curl -X POST https://jukebox.warp-9.app/api/v1/spots/12/volume \
  -H "X-Api-Key: jbr_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"volume": 0.5}'
Response
{ "ok": true }

This affects only browsers that are currently connected. Browsers that join after the call receive the default volume (controlled by the listener). Use the admin panel's remote volume knobs for per-device control.

Remove queue item

DELETE /spots/:spotId/queue/:itemId Remove a track from the queue

Removes a specific item from the queue by its id (obtained from GET /state). If the item is currently playing, playback advances to the next track.

Example
curl -X DELETE https://jukebox.warp-9.app/api/v1/spots/12/queue/4321 \
  -H "X-Api-Key: jbr_your_key_here"
Response
{ "ok": true }

Play an announcement

POST /spots/:spotId/announce Play an audio clip to all listeners

Pushes a pre-recorded audio clip to every browser currently tuned into the spot. Listeners hear a short chime, the music ducks automatically, the clip plays, and the music fades back up — the same treatment as a live mic announcement. Use it for store messages, paging, scheduled reminders, or text-to-speech you generate elsewhere.

Unlike the other endpoints, the body is the raw audio file (not JSON). Common formats work — mp3, m4a, wav, ogg, aac. The server transcodes the clip, plays it once, then discards it (nothing is stored permanently).

Headers
HeaderDescription
X-Api-Key*Your API key
Content-TypeThe clip's MIME type, e.g. audio/mpeg (or application/octet-stream)
X-LabelOptional short caption shown to listeners (URL-encoded, max 80 chars)
Example — send an MP3
curl -X POST https://jukebox.warp-9.app/api/v1/spots/12/announce \
  -H "X-Api-Key: jbr_your_key_here" \
  -H "Content-Type: audio/mpeg" \
  -H "X-Label: Closing in 10 minutes" \
  --data-binary @announcement.mp3
Response
{
  "ok": true,
  "duration": 7.4   // length of the clip in seconds
}

The clip reaches only browsers that are currently tuned in to the spot. It does not interrupt or change the music queue — playback resumes at the same spot once the announcement finishes.

Max upload size is 15 MB. For text announcements, generate speech with any TTS service and POST the resulting audio file.

Managing API keys

Keys are created and revoked in the admin panel:

  1. Go to Admin panel → API Keys (or click the API Keys button on a tenant row if you're a superadmin).
  2. Give the key a name (e.g. "Lobby kiosk") and click Generate key.
  3. The full key is shown exactly once — copy it now. The admin panel stores only the first 12 characters for identification.
  4. To revoke access immediately, click Revoke on the key. The next request from that key will get a 401.

Lost a key? There's no way to recover it — delete it and generate a new one.

Key format

Keys follow the format jbr_ + 32 random hex characters (36 characters total). Only the SHA-256 hash of the key is stored — the plaintext is never persisted.

Quick-start example (JavaScript)

const BASE = 'https://jukebox.warp-9.app/api/v1';
const KEY  = 'jbr_your_key_here';
const SPOT = 12; // get from GET /spots

async function addSong(url) {
  const res = await fetch(`${BASE}/spots/${SPOT}/add`, {
    method: 'POST',
    headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ url }),
  });
  return res.json();
}

async function setVolume(vol) {
  await fetch(`${BASE}/spots/${SPOT}/volume`, {
    method: 'POST',
    headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ volume: vol }),
  });
}

// examples
await addSong('https://youtu.be/dQw4w9WgXcQ');
await setVolume(0.7);