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:
- A kiosk tablet that lets guests queue songs
- A home-automation routine that skips songs after midnight
- A Slack bot that lets staff change the office music
- A scheduling system that loads a morning playlist automatically
- A paging system that plays voice announcements over the music
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:
| Code | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 400 | Bad request — check the request body |
| 403 | Spot is suspended, or belongs to a different tenant |
| 404 | Spot or queue item not found |
| 413 | Announcement audio exceeds the 15 MB limit |
| 503 | Radio engine not ready yet — retry in a moment |
# Error response shape { "error": "Invalid or revoked API key" }
List spots
Returns every spot belonging to the authenticated tenant.
{ "spots": [ { "id": 12, "name": "Main Hall", "location": "Ground floor", "joinKey": "ABCD12", "status": "active" } ] }
Get spot state
Returns the live playback state: what's playing now, the full queue, and the current server timestamp.
{ "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
Adds a YouTube video to the end of the spot's queue. If nothing is playing, playback starts immediately.
| Field | Type | Description |
|---|---|---|
| url* | string | Full YouTube URL — youtu.be or youtube.com/watch?v= or playlist URL |
| videoId | string | 11-char YouTube video ID (alternative to url) |
Provide either url or videoId — not both.
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"}'
{ "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
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.
curl -X POST https://jukebox.warp-9.app/api/v1/spots/12/skip \ -H "X-Api-Key: jbr_your_key_here"
{ "ok": true }
Set volume
Remotely sets the playback volume on every browser currently tuned into this spot. Volume changes take effect instantly over WebSocket.
| Field | Type | Description |
|---|---|---|
| volume* | number | Volume level — 0 (mute) to 1 (full). Decimals are fine, e.g. 0.5. |
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}'
{ "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
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.
curl -X DELETE https://jukebox.warp-9.app/api/v1/spots/12/queue/4321 \ -H "X-Api-Key: jbr_your_key_here"
{ "ok": true }
Play an announcement
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).
| Header | Description |
|---|---|
| X-Api-Key* | Your API key |
| Content-Type | The clip's MIME type, e.g. audio/mpeg (or application/octet-stream) |
| X-Label | Optional short caption shown to listeners (URL-encoded, max 80 chars) |
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
{ "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:
- Go to Admin panel → API Keys (or click the API Keys button on a tenant row if you're a superadmin).
- Give the key a name (e.g. "Lobby kiosk") and click Generate key.
- The full key is shown exactly once — copy it now. The admin panel stores only the first 12 characters for identification.
- 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);