Sending a catalogue
Pushing a catalogue
Present the sync token as a bearer token:
POST https://api.lets.dj/providers/sync
Authorization: Bearer <sync token>
Content-Type: application/json
{
"baseUrl": "https://0123456789abcdef.letsdj.io:8443",
"mediaToken": "<secret you generate>",
"capabilities": {
"canTransposeKey": false,
"canSeek": true,
"allowsSharing": true,
"allowsCommercialUse": false
},
"tracks": [
{
"externalId": "5ec27f0af401a3ab",
"title": "Diễm Xưa",
"artist": "Trịnh Công Sơn",
"durationSeconds": 254,
"thumbnailUrl": "https://...",
"playable": true
}
],
"final": true
}
baseUrl is required and must be https. The Stage is an HTTPS page and will refuse to load media over plain HTTP, so this is rejected at sync time rather than failing silently on the screen later. Only its origin is used to build media addresses, as described in Serving media, and the certificate behind it has to be one a browser already trusts.
mediaToken is required, and is generated by you, not us. You are the one who has to enforce it, so you should be the one who owns it. Use something unguessable that is safe in a query string; random hexadecimal or base64url of at least 128 bits will do. Rotating it on restart is fine, with the consequence described under Serving media.
capabilities says what you are and what you will do. See Capabilities. It is replaced as a whole by every sync, so send all of it every time. A flag you leave out goes back to its default, and defaults are not all false.
limits and consoleUrl are described under Answering on demand. They are replaced as a whole too: a sync that omits consoleUrl removes the link to your console.
tracks is required and may be empty. Send at most 500 tracks per request; more is a 400.
final is optional, and its default is true. See Replacing, and adding.
Tracks
externalIdis required. It is yours, any non-empty string, and unique within your library. It names the track’s media as well: it is the last part of the media address, percent-encoded.titleis required.artist,durationSeconds(seconds, any finite number), andthumbnailUrlare optional.playableis optional. Only a literalfalsesays anything. See Formats.
externalId must be stable across re-scans. A queued song resolves through it, so an id derived from mutable state will break songs mid-party. The bridge derives its ids from the size of the file and the bytes at each end of it, not from its path, so that moving or renaming a file does not orphan the corrections somebody has made to it.
A track with no externalId or no title cannot be found or played, and is skipped without failing the batch. The response says how many were kept, so compare it with how many you sent.
Replacing, and adding
A sync does one of two things, and final is what chooses.
Replacing the catalogue. Send it in batches of at most 500, with final: false on every batch except the last. The final batch removes every track that none of the batches in that replacement mentioned. A correction the owner made survives the removal, and returns if the track does. A track that is waiting to be fetched is never removed this way: see Answering on demand.
Adding or updating. Send final: false, and nothing is removed. This is how a fetched track is reported, and how a provider that knows exactly what changed can say only that.
Because final defaults to true, a sync that leaves it out is a replacement. A single track sent that way removes the rest of the catalogue. An empty batch with final: true removes all of it. So when a scan fails, for instance because a drive is unplugged or a share is asleep, do not send what the failed scan found. Leave the last catalogue in place and say so to the person, which is what the bridge does.
The answer
{ "ok": true, "accepted": 312, "trackCount": 812 }
accepted is how many tracks of this request were kept. trackCount is how many tracks the library holds after it. The library becomes ready the first time a sync lands.
- 400 with an
errorsaying what:body must be JSON,baseUrl is required,baseUrl must be https,consoleUrl must be https,tracks must be an array,at most 500 tracks per batch, ormediaToken is required. - 401 and 403 as described under Conventions.
- 503 means the service could not store a secret just now. It is ours, and worth retrying later.
Artwork
Optional, and layered the same way a correction is: whatever we hold always wins, but nothing here ever touches the file on your side.
Two ways to supply it:
A URL, in the sync payload: thumbnailUrl on each track, shown above. Anything the browser on a guest’s phone can fetch works. This is the wrong choice if the URL only resolves on your own network or expires after a few hours; use the endpoint below instead.
Bytes, pushed to a dedicated endpoint. This is the route a provider on a private network has to take, since it cannot hand out a URL anyone else can reach:
POST https://api.lets.dj/providers/art?externalId=<externalId>
Authorization: Bearer <sync token>
Content-Type: image/jpeg
<raw image bytes>
externalId must match a track already reported through /providers/sync, so push the catalogue first and the art after. A track the service has not heard of is a 404 with unknown track. Content-Type must start with image/, and the body must not be empty. Send JPEG, PNG, or WebP: anything a browser cannot draw will be stored and shown as a broken picture.
The limit is 1 MB, which is 1,048,576 bytes. We do not decode or resize what you send, so an oversized image is a flat 400 rather than a silent resample. Send something already sized for a thumbnail. A 1280×720 JPEG at 70 KB, the kind a phone or a DVD rip already produces, clears the limit by a wide margin.
Pushing art for a track again replaces the previous image. Art survives later syncs of the same track, and is deleted with the track when a replacement removes it.
Art pushed this way outranks a thumbnailUrl for the same track, because bytes we already hold beat a link that might go stale or 404 later. Either loses to a correction made in the app: your catalogue is a source, not the last word.
What the owner can change
The owner can correct a track’s title, artist, and picture in the app. A correction wins over whatever you send, field by field, and it survives every later sync, because it is kept apart from the catalogue and keyed by externalId. You are never told about one. This is why a stable externalId matters twice: a track whose id changes is a different track, and starts again without them.