1. Overview
The MUSIXQUARE Developer API reads room state, controls playback and room-wide audio effects, manages the persistent playlist, and uploads audio to a PRO room. Each key is bound to one room and only receives its assigned scopes.
All versioned endpoints use this base URL:
https://api.musixquare.com/v1
This is a server-to-server API. Calls made directly from browser pages are rejected. Keep the API key in a server environment variable, never in client-side JavaScript, an app bundle, a URL, or a public repository.
Machine-readable reference: OpenAPI 3.1 specification.
2. Authentication
Send the room-bound key as a Bearer credential on every versioned request.
Authorization: Bearer mxqr_live_<key-id>.<secret>
- Scopes
-
Keys can receive
room:read,playback:read,playback:control,effects:read,effects:control,queue:read,queue:write, andmedia:upload. Direct file additions require bothmedia:uploadandqueue:write, because completion also appends a playlist item. Effect state reads requireeffects:read; effect commands requireeffects:control. - Key safety
- A key is shown only when issued. Store it as a secret, redact it from logs, and request revocation if it may have been exposed. Never send a key belonging to one room with another room code.
- Authority and attribution
- Treat every Developer API key as a highest-privilege room credential within its assigned scopes. API actions are room-authoritative automation, not actions whose human requester MUSIXQUARE has verified. A bot or integration must separately identify the requesting user, the integration executing the command, and the user’s exact intent. Require explicit confirmation for destructive or broadly scoped actions, and never treat another user’s message or shared conversation context as the current user’s intent.
3. Quick Start
Node.js 18 or later includes the fetch API used below.
const baseUrl = 'https://api.musixquare.com/v1';
const roomCode = process.env.MXQR_ROOM_CODE;
const apiKey = process.env.MXQR_API_KEY;
if (!roomCode || !apiKey) throw new Error('MXQR_ROOM_CODE and MXQR_API_KEY are required');
const response = await fetch(`${baseUrl}/rooms/${roomCode}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
4. Read Room State
- GET/rooms/{roomCode}
- Room status, runtime, participant count, control availability, revisions, and storage quota.
- GET/rooms/{roomCode}/playback
- Canonical playback state, selected queue item, position, and observation time.
- GET/rooms/{roomCode}/queue
-
Persistent playlist, current item, playlist revision, and each item's required
addedByprovenance without private R2 identifiers, API key IDs, or URLs. - GET/rooms/{roomCode}/effects
-
Canonical full room-wide effect state, its revision, and last update time. This state remains available
while the PRO room is sleeping. Send the required
X-MXQR-Effects-Version: 2header. The response uses schema version 2 and includesvirtualTreblewith the other four effects. - GET/rooms/{roomCode}/queue-mode
-
Canonical repeat and shuffle settings. Requires
playback:readand remains available while the PRO room is sleeping.
addedBy is always present: participant for an item added directly in the app,
current_api_key for an item added with the credential making this request, or
another_api_key for an item added with a different Developer API credential. Raw API key IDs
are never exposed.
Read responses include an ETag. Send it later as If-None-Match; an unchanged
representation returns 304 Not Modified without a JSON body.
5. Control Playback
- POST/rooms/{roomCode}/commands
-
Submit
play,pause,seek,next, orplay_item. - GET/rooms/{roomCode}/commands/{commandId}
- Poll a command until it becomes
applied,rejected, orexpired.
Every mutation requires a unique Idempotency-Key header. Reusing the same key with the same body
never repeats the operation and returns the latest sanitized state; reusing it with a different body is
rejected.
const response = await fetch(`${baseUrl}/rooms/${roomCode}/commands`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({ type: 'seek', positionSeconds: 42.5 }),
});
const command = await response.json();
console.log(command.commandId, command.status);
next behaves like the app's Next button. When the selected top-level item is a YouTube
playlist aggregate, it advances to the next video inside that aggregate first. After the aggregate's last
video, it advances to the next top-level item using the room's current repeat and shuffle settings.
play_item is different: it force-selects the exact top-level item identified by
queueItemId instead of traversing from the current item's internal video.
Playback and effect commands are authorized and applied by the PRO room server. They remain available while the room runtime is sleeping; its timeline stays frozen until participants return and consume the canonical state. Selecting a large file can be committed before every connected or returning device has finished downloading and decoding it, so read playback state before issuing an immediately dependent command.
6. Control Audio Effects
Submit set_effects through the same command endpoint and poll the returned command ID through
/commands/{commandId}. The request is a partial update: omitted effects and omitted reverb
fields keep their current values. All supplied fields are validated and applied as one command. The
effects object and every supplied nested object must contain at least one field.
const response = await fetch(`${baseUrl}/rooms/${roomCode}/commands`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
type: 'set_effects',
effects: {
reverb: {
mixPercent: 40,
decaySeconds: 1,
preDelaySeconds: 0.02,
lowCutPercent: 0,
highCutPercent: 0,
},
equalizer: { bandsDb: [5, 3, 0, -2, -3] },
virtualBass: { strengthPercent: 60 },
virtualSurround: { widthPercent: 120 },
virtualTreble: { enabled: true },
},
}),
});
const command = await response.json();
console.log(command.commandId, command.status);
- Reverb
-
mixPercent0–100,decaySeconds0.1–30,preDelaySeconds0–1, andlowCutPercent/highCutPercent0–100. The app's Studio preset is{ mixPercent: 40, decaySeconds: 1, preDelaySeconds: 0.02, lowCutPercent: 0, highCutPercent: 0 }. Arena is{ mixPercent: 50, decaySeconds: 5, preDelaySeconds: 0.12, lowCutPercent: 0, highCutPercent: 40 }. Turn reverb off with{ mixPercent: 0 }. - Equalizer
-
bandsDbmust contain exactly five values from -12 through 12 dB, ordered at 60, 230, 910, 3,600, and 14,000 Hz. Bright is[0, -2, 0, 4, 6]; Warm is[5, 3, 0, -2, -3]; flat/off is[0, 0, 0, 0, 0]. - Virtual bass
-
strengthPercentaccepts 0–100. The app's on value is60; off is0. - Virtual surround
-
widthPercentaccepts 0–200 and controls stereo width, not channel routing. The app's on value is120; neutral/off is100. - Virtual treble
-
enabledis a boolean. Usetrueto turn the room-wide exciter on andfalseto turn it off. Every effects read uses version 2 and includes this field.
Effect state is persisted across PRO room sleep/wake cycles and client reconnects, and joining devices
receive the current state. set_effects is applied directly by the room server even while the
runtime is sleeping; there is no browser coordinator. Read /effects after the command becomes
applied to observe the canonical result.
The in-room BOT reads this same canonical effect state and can turn virtual treble on or off only when the user makes an explicit immediate request.
These are room-wide Web Audio effects. They do not control device-local volume, channel roles, subwoofer cutoff, or manual sync. YouTube playback is unaffected. During system-audio sharing, the sender hears the original source while receivers hear the configured effects.
7. Manage the Playlist
- POST/rooms/{roomCode}/queue/items
-
Add one independent YouTube video, or include
playlistIdto add one YouTube playlist aggregate whosevideoIdis its entry video. - POST/rooms/{roomCode}/queue/items/batch
-
Atomically add 1–100 YouTube items in order. If one item is invalid or the complete batch does
not fit, none are added. The effective limit is whichever comes first: 100 items or a 128 KiB
complete JSON request. Each distinct
playlistIdcreates exactly one top-level playlist: if the same ID appears again, the first occurrence supplies its position and display metadata while manifest-less rows contribute theirvideoIdin request order, or identical full manifests are preserved once. Mixed or conflicting manifests are rejected. Duplicate IDs are preserved. Entries withoutplaylistIdremain independent videos, including repeated IDs. - DELETE/rooms/{roomCode}/queue/items
- Clear the entire playlist atomically with no request body. Warning: this removes tracks added by people and by every API credential. It stops current playback, clears the current selection, and returns one updated queue with no items.
- DELETE/rooms/{roomCode}/queue/items/owned
- Atomically remove only items added by the API credential making this request, with no request body. An empty match is a successful no-op. Playback stops and the selection clears only when the current item belongs to this credential; released media references become eligible for later R2 cleanup.
- DELETE/rooms/{roomCode}/queue/items/{queueItemId}
- Remove only the identified queue item with no request body. Removing media also releases its playlist reference for later cleanup.
- PUT/rooms/{roomCode}/queue/order
-
Replace the order with
queueItemIds, an exact permutation of the current queue, and thebasePlaylistRevisionreturned by the latest queue read. - PUT/rooms/{roomCode}/queue-mode
-
Replace repeat and shuffle settings with an explicit desired state. Requires
playback:control, a uniqueIdempotency-Key, and the revision returned by the latest queue-mode read.
await fetch(`${baseUrl}/rooms/${roomCode}/queue/items`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
videoId: '<youtube-video-id>',
name: 'Example video',
title: 'Example title',
artist: 'Example artist',
}),
});
// Add one YouTube playlist aggregate with its complete ordered manifest.
await fetch(`${baseUrl}/rooms/${roomCode}/queue/items`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
videoId: '<playlist-entry-video-id>',
videoIds: ['<playlist-entry-video-id>', '<next-video-id>'],
playlistId: '<youtube-playlist-id>',
name: 'Example playlist',
title: 'Example playlist',
}),
});
const batchResponse = await fetch(
`${baseUrl}/rooms/${roomCode}/queue/items/batch`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
items: videos.map(({ videoId, title, artist }) => ({
videoId,
name: title,
title,
artist,
})),
}),
},
);
if (!batchResponse.ok) throw new Error(await batchResponse.text());
const queueAfterBatch = await batchResponse.json();
Batch canonicalization follows request order. For every non-empty playlistId, the first item
supplies the aggregate's position, entry videoId, and display metadata; later items with that
same ID are absorbed and their metadata is ignored. Manifest-less rows contribute their lone
videoId in request order; repeated full videoIds manifests must be identical and are
preserved once. A manifest accepts 1–5,000 IDs, preserves duplicates, and must contain the item's entry
videoId. Mixed manifest forms are rejected. Every supplied item is still validated atomically,
but the number of
appended top-level items can therefore be smaller than items.length.
const clearResponse = await fetch(`${baseUrl}/rooms/${roomCode}/queue/items`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
if (!clearResponse.ok) throw new Error(await clearResponse.text());
const clearedQueue = await clearResponse.json();
console.log(clearedQueue.currentQueueItemId); // null
console.log(clearedQueue.items.length); // 0
// Safe bot cleanup: human tracks and tracks from other API keys remain.
const cleanupResponse = await fetch(`${baseUrl}/rooms/${roomCode}/queue/items/owned`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
if (!cleanupResponse.ok) throw new Error(await cleanupResponse.text());
const queueAfterCleanup = await cleanupResponse.json();
console.log(queueAfterCleanup.items);
const queue = await fetch(`${baseUrl}/rooms/${roomCode}/queue`, {
headers: { Authorization: `Bearer ${apiKey}` },
}).then((response) => response.json());
await fetch(`${baseUrl}/rooms/${roomCode}/queue/order`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
basePlaylistRevision: queue.playlistRevision,
queueItemIds: queue.items.map((item) => item.queueItemId).reverse(),
}),
});
const queueMode = await fetch(`${baseUrl}/rooms/${roomCode}/queue-mode`, {
headers: { Authorization: `Bearer ${apiKey}` },
}).then((response) => response.json());
const queueModeResponse = await fetch(`${baseUrl}/rooms/${roomCode}/queue-mode`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
baseRevision: queueMode.revision,
repeatMode: 'all',
shuffleEnabled: true,
}),
});
if (queueModeResponse.status === 409) {
// Read the latest state, decide again, then retry with its revision and a new key.
throw new Error('Queue mode changed concurrently');
}
const updatedQueueMode = await queueModeResponse.json();
Collection DELETE /queue/items clears every person's and every integration's item;
DELETE /queue/items/owned removes only items owned by the current API credential; item
DELETE /queue/items/{queueItemId} removes exactly one. All three require a unique
Idempotency-Key for each new intent. Replaying a key returns the current sanitized queue without
applying that mutation again. Reordering rejects a stale playlist revision; fetch the queue again instead
of retrying an old order blindly. Queue writes remain available while the room is sleeping and do not start
playback automatically. A full clear always stops current playback; an owned cleanup stops it only when it
removes the selected item.
Queue mode is an explicit setter, not a toggle: every update supplies
baseRevision, repeatMode (off, all, or
one), and shuffleEnabled. On
409 QUEUE_MODE_REVISION_CONFLICT, read /queue-mode again, reconsider the desired
state, and retry with the fresh revision and a new idempotency key. Use the same key only when replaying the
exact same request after an uncertain transport result.
Queue-mode reads and writes work while the PRO room is sleeping, and the stored state applies on its next wake. The exact shuffle order is server-owned and intentionally hidden: it is preserved while shuffle stays enabled, generated when shuffle changes from off to on, and cleared when shuffle is disabled.
8. Upload Audio
Uploads use a three-step flow. The audio body goes directly to a short-lived signed R2 URL and never passes through the Developer API Worker.
A reservation requires name, byteLength, and mime. Optional metadata is
title, artist, thumbnail, and a lowercase 64-character
sha256 hint.
- POST/rooms/{roomCode}/media/uploads
-
Reserve capacity and receive an opaque asset ID, signed PUT URL, exact required headers,
uploadExpiresAtMs, and the latercompletionExpiresAtMsgrace deadline. - PUT{upload.url}
- Send the exact declared number of bytes directly to R2 using every returned header.
- POST/rooms/{roomCode}/media/uploads/{assetId}/complete
- Verify the object, finalize it, and append exactly one audio item to the playlist.
const reservationResponse = await fetch(
`${baseUrl}/rooms/${roomCode}/media/uploads`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
name: 'music.flac',
byteLength: audioBuffer.byteLength,
mime: 'audio/flac',
title: 'Example title',
artist: 'Example artist',
}),
},
);
const reservation = await reservationResponse.json();
await fetch(reservation.upload.url, {
method: reservation.upload.method,
headers: reservation.upload.headers,
body: audioBuffer,
redirect: 'manual',
});
await fetch(
`${baseUrl}/rooms/${roomCode}/media/uploads/${reservation.assetId}/complete`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Idempotency-Key': crypto.randomUUID(),
},
},
);
Treat the signed upload URL as a temporary credential. Do not log it, follow redirects, alter its query string, omit returned headers, or send a different content length. Completing an upload appends the item but never starts playback automatically.
Audio files deliberately do not use a binary-in-JSON batch endpoint. Each file keeps its own reservation, direct R2 upload, completion, checksum, quota check, and idempotency key. Multiple files can use the same three-step helper with at most two in flight per API key:
const pending = [...audioFiles];
const workers = Array.from(
{ length: Math.min(2, pending.length) },
async () => {
while (pending.length > 0) {
const file = pending.shift();
await reservePutAndComplete(file); // the three-step flow above
}
},
);
await Promise.all(workers);
Multi-file audio upload is intentionally not atomic: a file that completes remains in the playlist if a later file fails. Record each returned asset and queue item so your integration can retry or remove it explicitly.
9. Limits and Supported Media
- Storage
- Up to 200 MiB per asset and 1 GiB of persistent media per PRO room.
- Upload issuance
- Up to two active reservations per key and ten new reservations per hour.
- Audio candidates
- MP3, WAV, FLAC, M4A, AAC, OGG/OGA, Opus, WebM audio, AIF/AIFF, and CAF are accepted as candidates. Actual decoding support still depends on each playback device and browser.
- Request rate
-
Read and mutation limits are applied per key and room. A
429response includesRetry-After; wait for that duration before retrying.
10. Responses and Errors
JSON responses include X-Request-Id. Keep that value when reporting a failure. Error responses
use a stable code, human-readable message, request ID, and retryable flag.
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again later.",
"requestId": "req_<request-id>",
"retryable": true
}
}
Immediate non-2xx responses use the uppercase codes below. A command that was accepted with
202 is different: poll it and inspect its lowercase resultCode. Never treat a
lowercase command result as an HTTP error code. Retry automatically only when retryable is
true, and honor Retry-After when it is present.
Immediate HTTP error codes
API_DISABLED· 503 · retryable: true- The Developer API is disabled. Retry with backoff or contact the room operator if it persists.
API_NOT_CONFIGURED· 503 · retryable: true- The API deployment is temporarily incomplete or unavailable. Retry with backoff.
ASSET_CAPACITY_EXCEEDED· 409 · retryable: false- The room cannot register another stored media asset. Remove unused file-backed items before trying a new upload.
BACKEND_UNAVAILABLE· 503 · retryable: true- A required MUSIXQUARE backend did not respond. Retry with backoff and retain the request ID if the failure continues.
BROWSER_ORIGIN_FORBIDDEN· 403 · retryable: false- The request came directly from a browser origin. Move the API call and key to a trusted server.
COMMAND_CAPACITY_EXCEEDED· 409 · retryable: false- Too many playback commands are still active. Let existing commands settle, read current state, then submit a new intent with a new idempotency key.
COORDINATOR_INCOMPATIBLE· 409 · retryable: false- Legacy v1 compatibility value. Current PRO rooms are server-authoritative and do not use a browser coordinator. If an older or transitioning backend returns this code, read current state and update the integration before retrying.
FORBIDDEN· 403 · retryable: false- The key lacks a required scope. Use a key issued with the required permission; repeating the same request will not help.
IDEMPOTENCY_CONFLICT· 409 · retryable: false- The idempotency key was already used with a different request. Reconcile current state before creating a new key for a new intent.
IDEMPOTENCY_KEY_REQUIRED· 400 · retryable: false- A mutation is missing a valid
Idempotency-Key. Add a unique key that satisfies the documented header format. INTERNAL_RESPONSE_INVALID· 503 · retryable: true- A backend returned a response that failed the public contract check. Retry with backoff and retain the request ID.
INVALID_REQUEST· 400 · retryable: false- The request body, header, value, declared upload, or size is invalid. Correct it against the OpenAPI schema before resubmitting.
NOT_FOUND· 404 · retryable: false- The route, room, queue item, command, asset, or upload is unavailable to this key. Re-read the relevant collection and identifiers.
NO_MEDIA· 409 · retryable: false- No playable item is currently selected. Choose a queue item in the app or submit
play_itemwith a validqueueItemId. PLAYLIST_CAPACITY_EXCEEDED· 409 · retryable: false- The persistent playlist is full. Remove queue items before adding more.
PLAYLIST_REVISION_CONFLICT· 409 · retryable: false- The playlist changed after the supplied base revision. Read the queue again, recompute the mutation, and use a new idempotency key.
QUEUE_MODE_REVISION_CONFLICT· 409 · retryable: false- Repeat or shuffle state changed after the supplied base revision. Read
/queue-modeagain before sending a new explicit update. RATE_LIMITED· 429 · retryable: true- The key, room, or ingress limit was exceeded. Wait for the response's
Retry-Afterduration before retrying. RESERVATION_CAPACITY_EXCEEDED· 409 · retryable: false- This key has too many unfinished upload reservations. Complete outstanding uploads or wait for abandoned reservations to expire.
ROOM_QUOTA_EXCEEDED· 409 · retryable: false- The upload would exceed the PRO room storage quota. Remove stored file items before reserving another upload.
ROOM_SLEEPING· 409 · retryable: false- Legacy v1 compatibility value. The current server-authoritative path accepts canonical playback and effect commands while the room runtime is sleeping. If this code is observed, read room state and treat the responding backend as incompatible or in transition.
ROOM_STATE_CAPACITY_EXCEEDED· 409 · retryable: false- The bounded persistent room record cannot accept this change. Reduce playlist or stored state before submitting a new mutation.
UNAUTHORIZED· 401 · retryable: false- The Bearer key is missing, malformed, expired, revoked, or unknown. Obtain a valid room-bound key.
UPLOAD_INCOMPLETE· 409 · retryable: true- The signed direct upload is not visible as complete yet. Honor
Retry-After, then retry completion for the same reservation. UPLOAD_MISMATCH· 409 · retryable: false- The uploaded object does not match its reservation. Verify size, digest, and upload target, then create a fresh reservation if needed.
Accepted command result codes
A successful POST /commands returns 202 and a command ID. Poll that command until it
is terminal. Before submitting another intent after any non-success result, read canonical playback and
queue state first. Result names containing coordinator are retained only for v1 wire
compatibility; current PRO rooms do not elect a browser coordinator.
applied- The room server committed the requested intent to canonical state. Connected or returning devices may still be loading the selected media.
already_applied- The canonical room state already matched the requested intent; treat this as success.
busy- The room server was handling an incompatible transition. Read current state before deciding whether a new command is still needed.
no_media- The selected media was no longer available when the room server applied the accepted command.
stale_queue- The current queue item changed between command acceptance and execution. Read the queue and playback state again.
unsupported_mode- The requested operation is not supported by the current media mode.
expired- The accepted command did not reach a terminal server result before its deadline.
execution_failed- The room server received the command but could not complete the media operation.
coordinator_changed- Legacy v1 compatibility value for a superseded browser-coordinator command. Current server-authoritative rooms do not emit it for ordinary command handling.
coordinator_incompatible- Legacy v1 compatibility value for an unsupported browser-coordinator control version. Current server-authoritative rooms do not require that browser role.
coordinator_unavailable- Legacy v1 compatibility value for loss of a browser coordinator. Current server-authoritative rooms continue without one.
11. Compatibility and Contact
The URL major version is v1. New optional response fields may be added compatibly; clients should
ignore fields they do not recognize. Removing a field or changing its meaning requires a new major version.
For API key issuance, revocation, or integration questions, contact [email protected].