1. Overview
The MUSIXQUARE Developer API reads room state, controls playback, 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,queue:read,queue:write, andmedia:upload. Direct file additions require bothmedia:uploadandqueue:write, because completion also appends a playlist item. - 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.
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, and playlist revision without private R2 identifiers or URLs.
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, 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);
Playback commands require an awake room with a compatible active coordinator. Selecting a large file can be accepted before every device has finished downloading and decoding it; read playback state before issuing an immediately dependent command.
6. Manage the Playlist
- POST/rooms/{roomCode}/queue/items
- Add one YouTube item using a canonical video ID and display metadata.
- DELETE/rooms/{roomCode}/queue/items
- Clear the entire playlist atomically with no request body. This stops current playback, clears the current selection, and returns one updated queue with no items.
- 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.
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',
}),
});
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
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(),
}),
});
Collection DELETE /queue/items clears every item; item
DELETE /queue/items/{queueItemId} removes exactly one. Both 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, except that clearing the queue stops any current playback.
7. 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.
8. 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.
9. 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
}
}
- 400 / 401 / 403
- Invalid input, invalid key, missing scope, or a forbidden browser-origin request.
- 404 / 409
- Unavailable resource, stale queue state, quota conflict, or a command that cannot run in the current room state.
- 400
- The request shape is invalid, including an unsupported or oversized upload declaration.
- 429 / 503
- Rate limited, temporarily disabled, or an unavailable backend. Retry only when the response says it is safe.
10. 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 contact@musixquare.com.