App

PRIVATE BETA

Developer API

Connect a server, bot, or integration to one MUSIXQUARE PRO room.

API version: v1 · Updated July 17, 2026

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, and media:upload. Direct file additions require both media:upload and queue: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, or play_item.
GET/rooms/{roomCode}/commands/{commandId}
Poll a command until it becomes applied, rejected, or expired.

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 the basePlaylistRevision returned 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 later completionExpiresAtMs grace 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 429 response includes Retry-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.