- Docs
- Managed Transfers
Pre-authorized sessions
Use a pre-authorized session when your users are already signed in to your music service inside your own app. Your backend sends us the user's access token, we connect that account as the destination, and the user connects only the source. The wizard shows the destination as already connected.
- 01Your backend
POST /public/transfer-sessionswith the user's token - 02MusicAPIChecks the token at the service and answers a
launchUrl - 03Your appOpens the
launchUrl. The user connects the source. - 04TransferRuns. You push a new token when
user.auth_failedasks for one.
Requirements
- Managed Transfers is on for your account, and your Transfer destination is set.
- The destination is one of: Spotify, Deezer, Tidal, YouTube, SoundCloud, Boomplay. These services take a plain OAuth access token. For any other destination the call answers
400. - The token has the scopes your destination integration asks for in a normal sign-in: read the library, create and edit playlists, save tracks, albums and artists.
- Call the three endpoints from your backend only. They take Basic (Client Secret) or a DevToken without
sub. A Client ID token, a one-time token, a DevToken withsuband a docs session get403.
Create a session
curl -X POST "https://api.musicapi.com/public/transfer-sessions" \
-u "$MUSICAPI_CLIENT_ID:$MUSICAPI_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"target": {
"authData": {
"accessToken": "BQD...user-access-token",
"refreshToken": "AQB...user-refresh-token",
"expiresAt": "2026-10-16T15:00:00Z"
}
},
"externalUserId": "your-user-8841",
"returnUrl": "https://app.example.com/library"
}'
https URL.en or pt-BR. Accepted now so your call does not change when the wizard gets more languages.{
"sessionUuid": "0f1e2d3c-4b5a-4968-8776-655443322110",
"launchUrl": "https://app.musicapi.com/acme/transfer#session=0f1e2d3c-4b5a-4968-8776-655443322110&launch=8b1Xv...",
"launchExpiresAt": "2026-10-16T14:15:00.000Z",
"expiresAt": "2026-10-17T14:00:00.000Z",
"externalUserId": "your-user-8841",
"target": {
"integrationType": "spotify",
"integrationUserUuid": "9c8b7a65-4321-4f0e-9d8c-7b6a59483726",
"displayName": "Mica Nowak"
}
}
A second call for the same music account updates the stored token of that account. It makes no second account. Each call makes a new session, and sends user.authorized with "preauthorized": true.
| Status | Why |
|---|---|
400 | The body is not valid, no destination is set, or the destination does not support this flow. |
401 | No credential. |
403 | The credential is not secret, or Managed Transfers is off for your account. |
422 | The music service refused the access token. We store nothing. |
503 | We could not reach the music service to check the token. Try again later. We store nothing. |
The launch URL
Open the launchUrl in your app as it is: a web view or the browser. It works once, and only for 15 minutes (launchExpiresAt). Create the session when the user taps "Transfer", not in advance. The session itself lasts 24 hours (expiresAt).
The single-use code sits in the URL fragment, after #. A browser never sends the fragment to a server, so the code stays out of access logs and Referer headers. The wizard trades the code for a session and removes it from the address bar.
With a verified custom domain, the launchUrl uses your domain.
Token lifetime
We store the token encrypted, and never send it back in an answer or a webhook. When the destination refuses it during a transfer, we try to renew it once:
- When your destination integration has OAuth client credentials that can renew this token, we renew it with
refreshTokenand the transfer continues. - Otherwise, or when the renewal fails, the transfer stops with status
waitingForReauthand we senduser.auth_failed. The transfer keeps everything it already moved.
Push a new token
curl -X PUT "https://api.musicapi.com/public/transfer-sessions/0f1e2d3c-4b5a-4968-8776-655443322110/target-auth" \
-u "$MUSICAPI_CLIENT_ID:$MUSICAPI_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"authData": {
"accessToken": "BQE...new-access-token",
"refreshToken": "AQB...user-refresh-token",
"expiresAt": 1792170000
}
}'
{
"sessionUuid": "0f1e2d3c-4b5a-4968-8776-655443322110",
"target": {
"integrationType": "spotify",
"integrationUserUuid": "9c8b7a65-4321-4f0e-9d8c-7b6a59483726",
"displayName": "Mica Nowak"
},
"transferResumed": true
}
- The token must open the same account as the session's destination. A token of another account answers
409. - We replace the stored token. Leave out
refreshToken, and we store none. - A transfer of this session that waits for the destination continues at once:
transferResumed: true. - You can push a token at any time, also before the old one expires.
- A session of another partner answers
404. A session you ended answers409.
The loop for a long transfer:
- You get
user.auth_failedwith"side": "target","preauthorized": trueand thesessionUuid. - Your backend gets a fresh token for that user and calls
PUT …/target-auth. - The answer has
"transferResumed": true. The user's progress page continues without any action from the user.
While the transfer waits, the progress page tells the user that your app renews the connection. It does not ask the user to sign in to the destination.
End a session
curl -X DELETE "https://api.musicapi.com/public/transfer-sessions/0f1e2d3c-4b5a-4968-8776-655443322110" \
-u "$MUSICAPI_CLIENT_ID:$MUSICAPI_CLIENT_SECRET"
{
"sessionUuid": "0f1e2d3c-4b5a-4968-8776-655443322110",
"transferCancelled": true,
"credentialsErased": true
}
Ending a session:
- closes the wizard of this session. A browser that started a transfer can still read its progress and results for 30 days;
- cancels its transfer when it still runs (you get
transfer.cancelled); - erases the stored token, name, email and library copy of the destination account, unless another live session or a running transfer still uses that account. Then
credentialsErasedisfalse.
A second call answers the same way and changes nothing.
Security
- The access token and your Client Secret never go into your app's code.
- The
launchUrlis a credential for 15 minutes. Do not log it, and do not send it by email. - We check every token with the music service before we store it. A token of another account cannot replace the destination of a session.
- The wizard cannot detach or replace a pre-authorized destination.
- Call
DELETEwhen the user signs out of your app or deletes their account. Outside a session, use Disconnect users.