For training platforms that send their riders' finished rides to PeerTier. A rider connects your app once with OAuth 2.0. After that you push each finished ride as a FIT file.
Email hello@peertier.app with your app name and your https redirect URI(s). You receive a client_id and a client_secret. The secret is shown once: keep it on your servers, never in an app or a browser.
Send the rider to:
GET https://peertier.app/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://your.app/callback &scope=rides:write &state=RANDOM_VALUE &code_challenge=PKCE_CHALLENGE&code_challenge_method=S256 (recommended)
The rider signs in to PeerTier and allows or cancels. PeerTier then sends the rider back to your redirect URI:
allowed: https://your.app/callback?code=CODE&state=RANDOM_VALUE cancelled: https://your.app/callback?error=access_denied&state=RANDOM_VALUE
Check that state is the value you sent. The code works once and only for a few minutes.
POST https://peertier.app/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&client_id=…&client_secret=… &redirect_uri=https://your.app/callback&code=CODE&code_verifier=PKCE_VERIFIER
{"token_type": "Bearer", "expires_in": 21600, "access_token": "…", "refresh_token": "…"}
An access token lasts 6 hours. Get a new one with the refresh token:
grant_type=refresh_token&refresh_token=…&client_id=…&client_secret=…
Every refresh returns a new refresh token and the old one stops working: store the new one each time. A refresh token expires after 180 days.
POST https://peertier.app/api/partner/v1/rides Authorization: Bearer ACCESS_TOKEN Accept: application/json Content-Type: multipart/form-data file the ride as a FIT file (max 25 MB) external_id your own id for this ride: letters, digits, . _ : - (max 100)
Send each ride once it is finished. Sending the same external_id again is safe: it never makes a second ride.
201 imported | {"status": "imported", "ride_id": 123} The ride is stored. If the rider already had an empty copy of the same ride, it is filled instead of doubled. |
200 duplicate | {"status": "duplicate", "ride_id": 123} Already here, nothing changed. |
200 removed | The rider deleted this ride on PeerTier. It stays deleted. Do not retry. |
200 ambiguous | The file matches more than one of the rider's rides. Nothing was written. Do not retry. |
| 401 | The access token expired or was revoked. Refresh it. If the refresh fails, the rider disconnected your app: ask them to connect again. |
| 403 | Your app is not set up to upload rides. Contact us. |
| 422 | Unreadable FIT file or a missing field. Do not retry the same request. |
| 429 | More than 60 pushes a minute for one rider. Retry after the Retry-After header. |
Rides ridden before the rider first connected your app count as history: they fill the rider's fitness (training load, FTP), never XP. Rides after that count in full.
Your app can only upload rides. It can never read the rider's rides, fitness or profile, and never change or delete anything.
The rider can disconnect your app at any time in Settings, Connected apps. Your tokens stop working at once.