FitnessPlayer Music Provider API

Connect a FitnessPlayer account over OAuth and fetch the user's playlists as rendered, ready-to-play MP3 files.

https://connect.fitnessplayer.iodb mode

OAuth 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.

Integration walkthrough

STEP 1Authorization request browser redirect

Redirect 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…
STEP 2Token exchange server-side

Persist 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…" }
STEP 3Authenticated requests Authorization: Bearer
curl -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.

STEP 4File download playlistFileUrl
curl -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.

STEP 5Token refresh on 401

On 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

Authentication (OAuth 2.0, authorization code)

GET/oauth/authorize FitnessPlayer sign-in page

Open 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.

Query parameters

namedescription
response_typerequiredmust be code
client_idrequiredyour registered client id
redirect_urirequiredmust exactly match a URI registered for your client (scheme, host, port and path)
staterequiredopaque CSRF value, echoed back to you
code_challengePKCE challenge (optional)
code_challenge_methodS256 (the only supported method)

Example request

https://connect.fitnessplayer.io/oauth/authorize?response_type=code
    &client_id=YOUR_CLIENT_ID
    &redirect_uri=https://yourapp.example/callback/fitnessplayer
    &state=f3a91c…

Success redirect

https://yourapp.example/callback/fitnessplayer?code=7de3d4…&state=f3a91c…

Error redirect

https://yourapp.example/callback/fitnessplayer?error=unsupported_response_type&error_description=
POST/oauth/token code → tokens, and refresh

Form-encoded. Authenticate with your client credentials via HTTP Basic auth (or client_id/client_secret form fields). Two grants are supported.

Grant: authorization_code

fielddescription
grant_typerequiredauthorization_code
coderequiredthe one-time code from the redirect
redirect_urirequiredthe same value used at /oauth/authorize
code_verifierrequired if PKCE was used

Grant: refresh_token

fielddescription
grant_typerequiredrefresh_token
refresh_tokenrequiredrotates the access token; the refresh token itself never expires

Example request

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

Example response 200

{
  "access_token":  "6af6cb92079d5d8bcba5335452c1…",
  "token_type":    "Bearer",
  "expires_in":    2592000,               // 30 days
  "refresh_token": "be582ab47177548ebdf1d9a3ca22…"
}

Error response 400 / 401

{ "error": "invalid_grant", "error_description": "unknown, expired or already used code" }
POST/oauth/revoke disconnect

Revokes a token pair (RFC 7009). Pass either the access or the refresh token; both stop working. Always answers 200.

Example request

curl -d token=6af6cb92079d5d8bcba5… https://connect.fitnessplayer.io/oauth/revoke

API (Bearer token required)

GET/v1/users/me connected identity

Who 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.

Example request

curl -H "Authorization: Bearer $TOKEN" https://connect.fitnessplayer.io/v1/users/me

Example response 200

{
  "userDisplayName": "Johan Herelius",
  "userId":          "48211",
  "name":            "Johan Herelius",
  "email":           "johan@example.com",
  "username":        "johanh"
}
GET/v1/playlists the user's playlists

All playlists the signed-in user owns or follows, newest change first. Paged, 50 per page; an empty or short page means you have everything.

Query parameters

namedescription
page1-based page number (default 1)

Example request

curl -H "Authorization: Bearer $TOKEN" "https://connect.fitnessplayer.io/v1/playlists?page=1"

Example response 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
      }
    ]
  }
]
GET/v1/playlists/{id} one playlist

A single playlist in the same shape as the list entries. Requesting a playlist the user cannot see answers 404.

Example request

curl -H "Authorization: Bearer $TOKEN" https://connect.fitnessplayer.io/v1/playlists/483390

Error response 404

{ "error": "playlist not found" }
GET/v1/playlists/file/{id} the rendered MP3

The 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.

Behavior

Content-Typeaudio/mpeg (44.1 kHz stereo, LAME VBR)
Range requestssupported (206) — stream, seek, resume
Already renderingonly one render per playlist runs at a time: concurrent requests get 202 with a Retry-After header — wait and repeat the same request
Auth fallbackclients that cannot set headers may pass ?access_token=…

Example request

curl -H "Authorization: Bearer $TOKEN" -o mix.mp3 \
  https://connect.fitnessplayer.io/v1/playlists/file/483390

Other responses

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" }

Notes

Registration. Client ids, secrets and redirect URIs are provisioned by FitnessPlayer. A redirect_uri must match a registered value exactly — scheme, host, port and path — or the authorize request is refused without redirecting.
CORS. The /v1 endpoints are CORS-open (Access-Control-Allow-Origin: *) with the Authorization header allowed, so browser apps can call them directly.