Connect a FitnessPlayer account over OAuth and fetch the user's playlists as rendered, ready-to-play MP3 files.
https://connect.fitnessplayer.iodb modeOAuth 2.0 authorization-code flow, then three authenticated GET
endpoints. Client credentials (client_id,
client_secret, registered redirect_uri) are
provisioned by FitnessPlayer. The client secret is used only
server-side (steps 2 and 5); the browser is involved only in step 1.
An access token is scoped to its user: it cannot read or download
another user's playlists.
Authorization request
browser redirectRedirect the browser to /oauth/authorize. After
sign-in, the browser is redirected to redirect_uri with
a one-time code (5-minute TTL) and the echoed
state. Reject the callback if state does
not match.
https://connect.fitnessplayer.io/oauth/authorize?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/fp/callback
&state=f3a91c…
// callback: https://yourapp.example/fp/callback?code=7de3d4…&state=f3a91c…
Token exchange
server-sidePersist both tokens per user. Access token TTL: 30 days; the refresh token does not expire.
curl -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \ -d grant_type=authorization_code \ -d code=7de3d4… \ -d redirect_uri=https://yourapp.example/fp/callback \ https://connect.fitnessplayer.io/oauth/token
{ "access_token": "6af6cb…", "token_type": "Bearer",
"expires_in": 2592000, "refresh_token": "be582a…" }
Authenticated requests
Authorization: Bearercurl -H "Authorization: Bearer $TOKEN" https://connect.fitnessplayer.io/v1/users/me curl -H "Authorization: Bearer $TOKEN" "https://connect.fitnessplayer.io/v1/playlists?page=1"
Response shapes are specified per endpoint below.
File download
playlistFileUrlcurl -H "Authorization: Bearer $TOKEN" -o mix.mp3 \
https://connect.fitnessplayer.io/v1/playlists/file/483390
Uncached playlists render on demand (up to ~1 minute); afterwards
the file is served from cache until the playlist changes — compare
updated to decide when to re-download. A concurrent
render of the same playlist returns 202 with
Retry-After; repeat the request after the delay.
Token refresh
on 401On 401, rotate the access token and retry the request
once:
curl -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \ -d grant_type=refresh_token -d refresh_token=be582a… \ https://connect.fitnessplayer.io/oauth/token
/oauth/authorize
FitnessPlayer sign-in pageOpen this page in the user's browser. After a successful sign-in the
browser is redirected to your redirect_uri with a one-time
code (valid 5 minutes) and your state.
| name | description | |
|---|---|---|
response_type | required | must be code |
client_id | required | your registered client id |
redirect_uri | required | must exactly match a URI registered for your client (scheme, host, port and path) |
state | required | opaque CSRF value, echoed back to you |
code_challenge | PKCE challenge (optional) | |
code_challenge_method | S256 (the only supported method) |
https://connect.fitnessplayer.io/oauth/authorize?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/callback/fitnessplayer
&state=f3a91c…
https://yourapp.example/callback/fitnessplayer?code=7de3d4…&state=f3a91c…
https://yourapp.example/callback/fitnessplayer?error=unsupported_response_type&error_description=…
/oauth/token
code → tokens, and refreshForm-encoded. Authenticate with your client credentials via HTTP Basic
auth (or client_id/client_secret form fields).
Two grants are supported.
| field | description | |
|---|---|---|
grant_type | required | authorization_code |
code | required | the one-time code from the redirect |
redirect_uri | required | the same value used at /oauth/authorize |
code_verifier | required if PKCE was used |
| field | description | |
|---|---|---|
grant_type | required | refresh_token |
refresh_token | required | rotates the access token; the refresh token itself never expires |
curl -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \ -d grant_type=authorization_code \ -d code=7de3d4… \ -d redirect_uri=https://yourapp.example/callback/fitnessplayer \ https://connect.fitnessplayer.io/oauth/token
200{
"access_token": "6af6cb92079d5d8bcba5335452c1…",
"token_type": "Bearer",
"expires_in": 2592000, // 30 days
"refresh_token": "be582ab47177548ebdf1d9a3ca22…"
}
400 / 401{ "error": "invalid_grant", "error_description": "unknown, expired or already used code" }
/oauth/revoke
disconnectRevokes a token pair (RFC 7009). Pass either the access or the
refresh token; both stop working. Always answers 200.
curl -d token=6af6cb92079d5d8bcba5… https://connect.fitnessplayer.io/oauth/revoke
/v1/users/me
connected identityWho the token belongs to — for showing the connected identity in your
app's connections page. userDisplayName is the field the
integration spec asks for (the user's name, falling back to their
username); the separate fields let you show name and email
independently. name and email may be empty.
curl -H "Authorization: Bearer $TOKEN" https://connect.fitnessplayer.io/v1/users/me
200{
"userDisplayName": "Johan Herelius",
"userId": "48211",
"name": "Johan Herelius",
"email": "johan@example.com",
"username": "johanh"
}
/v1/playlists
the user's playlistsAll playlists the signed-in user owns or follows, newest change first. Paged, 50 per page; an empty or short page means you have everything.
| name | description | |
|---|---|---|
page | 1-based page number (default 1) |
curl -H "Authorization: Bearer $TOKEN" "https://connect.fitnessplayer.io/v1/playlists?page=1"
200[
{
"playlistId": "483390",
"playlistName": "Thursday Intervals",
"playlistFileUrl": "https://connect.fitnessplayer.io/v1/playlists/file/483390",
"durationInMilliseconds": 2685000,
"updated": "2026-09-01-09-30-00", // YYYY-MM-DD-HH-mm-ss
"playlistTracks": [
{
"Title": "Warmup Pulse",
"Artist": "Prodigy",
"BPM": "128", // string; "0" for a pause
"durationInMilliseconds": 40000 // as played in the mix
},
{
"Title": "Pause", // silent gap in the mix
"Artist": "",
"BPM": "0",
"durationInMilliseconds": 20000
}
]
}
]
/v1/playlists/{id}
one playlistA single playlist in the same shape as the list entries. Requesting a
playlist the user cannot see answers 404.
curl -H "Authorization: Bearer $TOKEN" https://connect.fitnessplayer.io/v1/playlists/483390
404{ "error": "playlist not found" }
/v1/playlists/file/{id}
the rendered MP3The playlist baked into one continuous MP3 — cue points, fades, tempo changes, crossfades and pauses exactly as the FitnessPlayer apps play it. The first request renders the file (expect tens of seconds for a long playlist); repeats are served from a cache until the playlist changes, so downloading again after an edit picks up the new mix automatically.
| Content-Type | audio/mpeg (44.1 kHz stereo, LAME VBR) | |
| Range requests | supported (206) — stream, seek, resume | |
| Already rendering | only one render per playlist runs at a time: concurrent requests get 202 with a Retry-After header — wait and repeat the same request | |
| Auth fallback | clients that cannot set headers may pass ?access_token=… |
curl -H "Authorization: Bearer $TOKEN" -o mix.mp3 \
https://connect.fitnessplayer.io/v1/playlists/file/483390
202 { "status": "rendering", "message": "this playlist is already being rendered; retry shortly", "retryAfterSeconds": 15 } 401 { "error": "invalid_token", "error_description": "missing, expired or revoked token" } 404 { "error": "playlist not found" } 422 { "error": "playlist has no items" }
redirect_uri must match a registered value
exactly — scheme, host, port and path — or the authorize request is
refused without redirecting.
/v1 endpoints are CORS-open
(Access-Control-Allow-Origin: *) with the
Authorization header allowed, so browser apps can call them
directly.