Connecting a provider
A provider needs a sync token before it can say anything to the service, and it gets one the way a television gets an account: it shows a code, a person approves that code somewhere they are already signed in, and the provider collects its credential. Nothing is copied, typed, or pasted.
This replaced showing the owner a token to paste into a command. The app no longer displays a sync token to anyone, so pairing is the only way to get one.
The flow
- The provider asks the service for a pairing, and is given a code, a link, and a poll secret.
- The provider opens the link in a browser, or, if it has no screen, prints it with the code. The link works on any device, so the owner can open it on their phone.
- On that page the owner signs in, or creates an account, names the library, and approves. The page says which device is asking and shows the code, so the owner can tell that the request is theirs.
- The provider polls with its poll secret. The first poll after approval returns the sync token, and no later one will.
Starting
POST https://api.lets.dj/providers/pair/start
Content-Type: application/json
{ "deviceName": "Ryan’s MacBook" }
This route takes no credential, because a provider that has not been paired has none. The body is optional. deviceName is shown to the owner on the approval page beside the code, so make it something they will recognise. It is the softer of the two checks — a name is whatever a machine called itself, where the code is a value both ends hold independently — but it is the one that tells them which of their computers is asking. It is cut to 60 characters and control characters are replaced with spaces.
{
"code": "K7QM-X4NP",
"pollSecret": "…",
"pairUrl": "https://app.lets.dj/pair?code=K7QM-X4NP",
"expiresAt": 1790000000000,
"pollIntervalSeconds": 2
}
codeis eight characters from an alphabet with nothing easily mistaken for anything else (no 0 or O, no 1 or I or L), shown as two groups of four. People read these aloud and retype them when a link will not open. It is how the owner’s page finds the pairing. It is not what you prove yourself with.pollSecretis what you prove yourself with. Keep it to yourself and use it only for polling.pairUrlis the link to open. Use it as given rather than building it.expiresAtis milliseconds since the epoch.pollIntervalSecondsis how long to wait between polls.
A 429 with "code": "too_many_pairings" and a Retry-After header means the service has a ceiling on pairings open at once, which exists because this is the one route anybody can call. Wait and ask again.
How long it lasts
A pairing is open for thirty minutes from the moment it is created. Each time the owner reaches the approval page while signed in, the clock restarts, but never beyond two hours from creation. Once approved, there is a further thirty minutes to collect the token.
The generosity is deliberate and is for the person this exists for, who may have to create an account, wait for a verification email, and find it in their spam folder before they can approve anything. Ten minutes was the first figure and it would have failed exactly them.
Treat the figures as how the service behaves today and go by expiresAt, which the poll returns and which moves. When a pairing runs out, ask for a fresh one and show the new code. A provider that does this can be left running while somebody signs up.
Polling
POST https://api.lets.dj/providers/pair/poll
Authorization: Bearer <poll secret>
This is the only route where the bearer is not a sync token.
While the owner has not yet approved:
{ "status": "pending", "expiresAt": 1790000000000 }
After approval, once:
{
"status": "approved",
"syncToken": "…",
"bridgeId": "0123456789abcdef",
"hostname": "0123456789abcdef.letsdj.io",
"label": "Karaoke at Ryan’s"
}
syncTokenis created at the moment this response is built. The service keeps only a hash of it and cannot say it again. Store it before you do anything else that could fail. If it is lost, the only remedy is to pair again, and pairing again makes a new library; the old one is left empty for the owner to delete.bridgeIdandhostnameare the library’s name on the service’s domain, described in A name and a certificate. Usehostnameas given.labelis what the owner called the library.
The responses are marked Cache-Control: no-store, because a token that was cached somewhere on the way is a token somebody else holds.
The failures are all 4xx with a code:
- 410
expired: the pairing ran out. Start a new one. - 410
already_collected: the token was already delivered, perhaps to a poll whose answer you never received. Start a new one. - 403
unknown_pairing: the poll secret matches no pairing. Start a new one. - 401
missing_token: there was no bearer.
After pairing
The library now exists and is empty, and the owner’s page says it is connected. It becomes ready when you send your first catalogue: see Sending a catalogue.
A sync token does not expire. It stops working when the owner deletes the library, and every route answers 403 from then on, which is the signal described under Conventions.
Do not share a sync token between two programs. Each sync replaces the address and the media token the library has on file, so the second would quietly take the library from the first.