Run your own guest page and call SpotiQueue from your backend. Everything the built-in guest queue and bigscreen do is available: search, queue, undo, voting, now playing with lyrics, album art, and hosted guest sign-in.
The API is included with Pro. Each account can create up to ten secret keys, each
with its own queue permissions, managed in Account → Developer API
(https://dash.spotiqueue.com/account?p=developer-api).
https://spotiqueue.com/api/v1application/json/api/v1 is stable. Breaking changes ship as /api/v2.Server-side only. The key is a bearer secret. Put it on your backend and have your frontend talk to your backend. Never ship the key to a browser or a mobile app bundle.
Send the key on every request:
Authorization: Bearer sq_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Pick the queue with the slug in the path:
POST https://spotiqueue.com/api/v1/my-party/queue/search
A request is accepted when all four hold:
If a key has no queues ticked, it cannot reach any queue. This is deliberate: a leaked key cannot touch a queue you did not explicitly enable for that key.
Every per-guest call carries two headers. The fingerprint id names the device; the device token proves your backend registered that device with this API key.
X-Guest-Fingerprint: 9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f
X-Guest-Device-Token: a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
Get both from POST /api/v1/{slug}/fingerprint/generate (first visit) or from
POST /api/platform/public-api/guest-auth/exchange after hosted OAuth. Store them
against that visitor in your own session and replay them on later guest-scoped calls.
The fingerprint must be 16 to 64 hex characters. The device token is 64 hex characters
(32 random bytes). Treat the token like a password: never expose it in the browser or
in client-side JavaScript.
A device token is bound to the API key that minted it. Another key on the same account cannot act as that guest, even if it knows the fingerprint id.
Cooldowns, undo eligibility, "my songs", and vote state are all keyed to the fingerprint id, so reusing one id for every visitor makes them share one cooldown budget.
All paths below are relative to https://spotiqueue.com/api/v1/{slug}.
| Method | Path | Guest header | Purpose |
|---|---|---|---|
| GET | /config/public | no | Queue flags: search UI, voting, prequeue, lyrics delay, immersive mode, ads, custom theme |
| POST | /fingerprint/generate | optional | Create or update a guest device; returns fingerprint_id and guest_device_token when minted |
| POST | /fingerprint/validate | yes | Check a device is still allowed (blocked, cooldown, username) |
| GET | /queue/current | no | Up next, with vote tallies and now-playing context |
| POST | /queue/search | yes | Body { "query": "…" }; returns up to 10 tracks |
| POST | /queue/add | yes | Body { "track_id": "…" } or { "track_url": "…" } |
| POST | /queue/undo | yes | Body { "queue_item_id": "…" } (omit for the guest's most recent) |
| POST | /queue/vote | yes | Body { "queue_item_id": "…", "direction": 1 } (-1 to downvote) |
| GET | /queue/votes | yes | Tallies plus this guest's own votes |
| POST | /prequeue/submit | yes | Body { "track_id": "…" } when the host reviews requests first |
| GET | /now-playing | no | Current track with embedded synced lyrics when available |
| GET | /album-art?url=… | no | Same-origin image proxy for Spotify CDN art (needed for colour sampling) |
| GET | /google/status | yes | Guest Google link state |
| GET | /github/status | yes | Guest GitHub link state |
| GET | /discord/status | yes | Guest Discord link state |
Not on the API: instance admin, owner Spotify OAuth, platform routes, and the provider
/login and /unlink routes. Anything else under /api/v1 returns
404 endpoint_not_available.
Read queue settings so your UI knows which controls to render:
curl https://spotiqueue.com/api/v1/my-party/config/public \
-H "Authorization: Bearer $SPOTIQUEUE_API_KEY"
{
"queueing_enabled": true,
"search_ui_enabled": true,
"url_input_enabled": true,
"voting_enabled": true,
"voting_downvote_enabled": true,
"prequeue_enabled": false,
"aura_enabled": false,
"lyrics_delay_seconds": 0,
"instance_slug": "my-party",
"instance_name": "Friday Rooftop",
"theme": {
"enabled": true,
"colors": {
"light": {
"background": "#f6f8f7",
"foreground": "#0c120f",
"card": "#ffffff",
"primary": "#1db954",
"border": "#d8dfdb",
"muted": "#5e6e66",
"accent": "#e2e9e6"
},
"dark": {
"background": "#0a100d",
"foreground": "#f4f6f5",
"card": "#0f1512",
"primary": "#1db954",
"border": "#242e29",
"muted": "#96a69e",
"accent": "#1f2924"
}
}
}
}
theme.colors is null unless the host has an active custom theme (Pro only). When present,
each mode (light, dark) holds hex colour values and either mode may be absent; apply the
matching mode to your own UI or ignore the field. Treat any field here as additive: ignore
fields you do not know.
Register a new visitor:
curl -X POST https://spotiqueue.com/api/v1/my-party/fingerprint/generate \
-H "Authorization: Bearer $SPOTIQUEUE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"Ada"}'
{
"ready": true,
"fingerprint_id": "9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f",
"guest_device_token": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"username": "Ada",
"requires_username": false,
"requires_google_auth": false,
"requires_github_auth": false,
"requires_discord_auth": false,
"guest_authenticated": true
}
To update an existing visitor (for example to set their name later), replay their
X-Guest-Fingerprint and X-Guest-Device-Token on the same call. guest_device_token
is only returned again when a new token is minted (first register or OAuth exchange).
Search and queue:
curl -X POST https://spotiqueue.com/api/v1/my-party/queue/search \
-H "Authorization: Bearer $SPOTIQUEUE_API_KEY" \
-H "X-Guest-Fingerprint: 9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f" \
-H "X-Guest-Device-Token: $GUEST_DEVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"blue monday"}'
curl -X POST https://spotiqueue.com/api/v1/my-party/queue/add \
-H "Authorization: Bearer $SPOTIQUEUE_API_KEY" \
-H "X-Guest-Fingerprint: 9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f" \
-H "X-Guest-Device-Token: $GUEST_DEVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"track_id":"3AhXZa8sUQht0UEdBJgpGc"}'
queue/add returns { "success": true, "queue_item_id": "…", "track": { … } }. Keep the
queue_item_id: it is what queue/undo and queue/vote take.
POST /queue/vote returns { "userVote": 1 | -1 | null, "votes": <net for this item>, "queue_item_id": "…", "votesByQueueItem": { "<queue_item_id>": <net>, … } }.
The votesByQueueItem map lists net scores for every queued item on the instance so your UI can refresh all counters from one response.
Guests cannot vote on a row they queued themselves (400 with { "error": "You cannot vote on your own song." }).
GET /now-playing returns the current track and, when we have them, synced lyrics:
{
"track": {
"id": "3AhXZa8sUQht0UEdBJgpGc",
"name": "Blue Monday",
"artists": "New Order",
"album_art": "https://i.scdn.co/image/…",
"duration_ms": 448000,
"progress_ms": 61200,
"is_playing": true,
"lyrics": { "synced": true, "lines": [{ "time": 12.4, "text": "…" }] }
}
}
Apply lyrics_delay_seconds from /config/public as an offset when highlighting the
current line. Load album art through /album-art?url=… if you sample its colours;
Spotify's CDN does not send CORS headers.
When the host requires guest Google, GitHub, or Discord sign-in, that sign-in must happen on SpotiQueue: each provider allows one fixed callback URL, and the guest's proof lives in cookies on our origin. Your page hands the guest over and gets them back with a code.
guest-auth/start with the queue slug and your return URL, and gets
back an auth_url on spotiqueue.com.auth_url.return_to?code=…&state=….guest-auth/exchange with that code and the same API key that
called guest-auth/start. The response includes fingerprint_id, guest_device_token,
and linked provider names.Before you start: save your return URL in Account → Developer API → Guest sign-in
redirect URLs (HTTPS, or http://localhost / http://127.0.0.1 for local dev; up to 15).
return_to must match a saved URL on origin and path;
query strings are ignored when matching.
POST https://spotiqueue.com/api/platform/public-api/guest-auth/start
Authorization: Bearer sq_live_…
{
"instance_slug": "my-party",
"return_to": "https://your-site.example/party/callback",
"state": "your-own-session-token"
}
{
"auth_url": "https://spotiqueue.com/my-party/guest-auth?session=…",
"expires_in": 900
}
Send the guest to auth_url. The page collects a username and any required provider
sign-in, then redirects to return_to?code=…&state=…. state is echoed back exactly as
you sent it; use it to tie the callback to your own session.
POST https://spotiqueue.com/api/platform/public-api/guest-auth/exchange
Authorization: Bearer sq_live_…
{ "code": "…" }
{
"instance_slug": "my-party",
"fingerprint_id": "9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f",
"guest_device_token": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"guest": {
"google": { "username": "[email protected]", "avatar": "https://…" },
"github": null,
"discord": null
}
}
Codes are single use and expire after 5 minutes. Store fingerprint_id and
guest_device_token against your session and send both headers on guest-scoped calls.
Read /config/public and /fingerprint/generate first. If a response has
requires_google_auth, requires_github_auth, or requires_discord_auth set, or a write
returns 401, run the hosted flow. If none are set, fingerprint/generate alone is
enough.
Budgets are per account, not per IP, so all your guests share them. Every response carries
standard RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers.
| Category | Limit | Endpoints |
|---|---|---|
| Reads | 600 / min | config/public, queue/current, queue/votes, now-playing, album-art, provider status |
| Search | 120 / min | queue/search |
| Writes | 180 / min | queue/add, queue/vote, queue/undo, prequeue/submit, fingerprint/* |
| Guest auth | 30 / min | guest-auth/start, guest-auth/exchange |
Over budget returns 429 with { "error": "…", "code": "rate_limited" }.
Practical guidance for a party-sized crowd:
queue/current and now-playing from your server on a shared timer (about
every 2 to 3 seconds each) and fan the result out to your visitors. Do not let each
browser poll SpotiQueue through your proxy.queue/add can return
429 with cooldown_remaining because of the host's cooldown settings, which is a
different condition from hitting the API budget.Errors are JSON: { "error": "human readable", "code": "machine_readable" }. code is
absent on some validation errors inherited from the guest routes.
| Status | Code | Meaning |
|---|---|---|
| 401 | api_key_required | No Authorization: Bearer header |
| 401 | api_key_invalid | Unknown or revoked key |
| 403 | pro_required | The account is not on Pro |
| 403 | account_disabled | The account is suspended |
| 403 | instance_not_allowed | Slug is not owned by this account, or not ticked for this key |
| 404 | instance_not_found | No active queue for that slug |
| 404 | endpoint_not_available | Path is not part of the API |
| 429 | rate_limited | API budget exhausted |
| 400 | code_invalid | Guest auth code is expired, already used, or belongs to another account |
| 400 | redirect_url_not_allowed | return_to is not one of your saved redirect URLs |
| 400 | redirect_url_missing | No redirect URLs saved yet |
| 403 | handoff_key_mismatch | guest-auth/exchange used a different API key than guest-auth/start |
| 401 | guest_device_token_required | Missing device token or device not registered via this key |
| 401 | guest_device_token_invalid | Wrong X-Guest-Device-Token for this fingerprint |
| 403 | guest_device_key_mismatch | Device was minted for another API key |
Guest-facing conditions come back with the same shape the built-in UI sees:
| Status | Body | Meaning |
|---|---|---|
| 400 | Could not fingerprint your device. | Missing or malformed X-Guest-Fingerprint |
| 400 | guest_fingerprint_required / guest_fingerprint_invalid | Public API guest header problems |
| 401 | requires_google_auth / requires_github_auth / requires_discord_auth | Run hosted sign-in |
| 403 | This device is blocked from queueing songs. | Host blocked this device |
| 403 | Explicit songs are not allowed. / This song is not allowed. | Host content rules |
| 409 | This song is already in the queue. | Duplicate request |
| 429 | cooldown_remaining present | Host cooldown, not an API limit |
| 503 | Queueing is currently disabled. | Host paused requests |
Surface these to your visitors rather than retrying: most are decisions the host made.
/api/v1 is stable. Additive changes (new fields, new endpoints) ship without a version
bump, so ignore unknown fields rather than failing on them.
Anything that could break an existing integration is announced ahead of the change:
"apiBreaking": true and publicApiDocUrl.Removing or changing the meaning of an existing field means a new version, /api/v2, with
/api/v1 kept alive during the migration window.
/config/public to shape your UI./fingerprint/generate per visitor (or run hosted auth). Store fingerprint_id and
guest_device_token, then replay both as X-Guest-Fingerprint and X-Guest-Device-Token.Stuck on an integration? [email protected].
We use essential cookies to keep you signed in, run guest queues, and complete OAuth sign-in. We do not use advertising or third-party analytics cookies. Details are in our Privacy Policy.