Skip to content
AFAIFitnessAPI
Integration Guides

How to Integrate the Strava API (2026)

Updated July 9, 2026

The Strava API gives you an athlete's activities — runs, rides, and other workouts with GPS routes, distance, pace, time, elevation, and heart rate — plus profile and segment data. Authentication is OAuth 2.0 Authorization Code: you send the athlete to Strava to approve scopes, exchange the returned code for a short-lived access token, and call the v3 REST API with a Bearer token. Access tokens last about 6 hours and refresh tokens rotate on every refresh, so persist the newest one. Subscribe to the Events API webhook for near-real-time activity updates instead of polling.

The Strava API gives you an athlete's activities — runs, rides, and other workouts with their GPS routes, distance, pace, time, elevation, and heart rate — plus profile, segment, and route data. Authentication is OAuth 2.0 Authorization Code: you send the athlete to Strava to approve scopes, exchange the returned code for a short-lived access token, and then call the v3 REST API with a Bearer token, refreshing as needed and subscribing to webhooks for near-real-time updates.

Strava is the natural integration for a social running or cycling app, where athletes already record activities and expect them to sync. If you are building that kind of product, this pairs well with the wider wearable data APIs layer and the app-shape decisions in how to build a running app.

Verify before you ship. Endpoints, scopes, token lifetimes, and rate limits below are accurate as of 2026, but Strava changes them. Confirm the current values in the official Strava developer docs (developers.strava.com) before you go to production, and treat the exact per-app rate quotas as defaults that can vary.

What you'll need

  • A Strava account, and a registered API application (this gives you a Client ID and Client Secret).
  • An Authorization Callback Domain registered on the app — Strava validates that your redirect_uri lives under this domain.
  • A server-side place to hold the Client Secret and to store each athlete's rotating refresh token. Never ship the secret in a mobile or browser client.
  • For webhooks: a publicly reachable HTTPS callback URL that can answer a validation handshake within 2 seconds.

Step 1: Register your Strava API application

Create an application at https://www.strava.com/settings/api. Strava issues a Client ID and Client Secret, and asks for an Authorization Callback Domain. Only the domain is registered — the full redirect_uri you pass at authorize time must be a URL under that domain (for example, registering yourapp.example.com allows https://yourapp.example.com/exchange_token).

Decide your scopes up front. The ones most integrations need:

ScopeGrants
readPublic profile info
activity:readActivities visible to Everyone or Followers
activity:read_allAll activities, including private "Only You" activities
activity:writeCreate or update activities

Request the least you need. To read an athlete's full activity history including private activities — and to receive webhook events for those private activities — you need activity:read_all. Plain activity:read only ever sees activities the athlete has shared beyond "Only You".

Step 2: Send the athlete through the OAuth authorize screen

Redirect the athlete to Strava's authorize endpoint. Multiple scopes are comma-separated (not space-separated as in most OAuth providers):

https://www.strava.com/oauth/authorize
  ?client_id=CLIENT_ID
  &redirect_uri=https://yourapp.example.com/exchange_token
  &response_type=code
  &approval_prompt=auto
  &scope=read,activity:read_all

approval_prompt=auto skips the consent screen if the athlete has already granted these scopes; use force to always show it. On approval, Strava redirects back to your redirect_uri with a one-time code and the scopes actually granted:

https://yourapp.example.com/exchange_token?code=AUTH_CODE&scope=read,activity:read_all&state=...

Always read the returned scope parameter and confirm it contains what you need — the athlete can deselect scopes on the consent screen, so a granted set narrower than what you requested is normal and you must handle it.

Step 3: Exchange the code for tokens

Trade the authorization code for an access token from your server, where the Client Secret is safe:

curl -X POST "https://www.strava.com/oauth/token" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=CLIENT_SECRET" \
  -d "code=AUTH_CODE" \
  -d "grant_type=authorization_code"

The response carries the access token, a refresh token, and expiry:

{
  "token_type": "Bearer",
  "expires_at": 1752091200,
  "expires_in": 21600,
  "refresh_token": "e5n567...",
  "access_token": "a4b945...",
  "athlete": { "id": 12345, "username": "..." }
}

Access tokens are short-lived: they expire 6 hours after creation (expires_in of 21600 seconds, as of 2026 — verify the current value). Store expires_at alongside the tokens so you know when to refresh. Note that the OAuth token exchange and refresh calls do not count against your API rate limit.

Step 4: Fetch the athlete's activities

Call the v3 REST API with the access token in an Authorization: Bearer header. To list the authenticated athlete's activities, newest first:

curl -X GET "https://www.strava.com/api/v3/athlete/activities?per_page=30&page=1" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Useful query parameters: before and after (epoch seconds) to window the results, plus page and per_page (default 30, up to 200 per page) to paginate. To read the athlete's own profile:

curl -X GET "https://www.strava.com/api/v3/athlete" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Remember that without activity:read_all, this listing omits the athlete's private "Only You" activities.

Step 5: Refresh the rotating tokens

When an access token is near or past expires_at, exchange the stored refresh token for a new access token:

curl -X POST "https://www.strava.com/oauth/token" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=CLIENT_SECRET" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=REFRESH_TOKEN"

Strava uses rotating refresh tokens: each refresh returns a new access token AND a new refresh token. You must persist the newest refresh token and discard the old one — if you keep reusing a stale refresh token, subsequent refreshes will fail and you will have to send the athlete back through consent. Store the refresh token per athlete and update it on every refresh.

Step 6: Subscribe to the Events API webhook

Polling burns your rate limit. Instead, subscribe once (only one subscription is allowed per application) so Strava pushes events when an athlete creates, updates, or deletes an activity:

curl -X POST "https://www.strava.com/api/v3/push_subscriptions" \
  -F client_id=CLIENT_ID \
  -F client_secret=CLIENT_SECRET \
  -F callback_url=https://yourapp.example.com/webhook \
  -F verify_token=YOUR_VERIFY_TOKEN

Immediately after this POST, Strava sends a GET to your callback_url to validate it:

GET https://yourapp.example.com/webhook
    ?hub.mode=subscribe
    &hub.verify_token=YOUR_VERIFY_TOKEN
    &hub.challenge=15f7d1a91c1f40f8a748fd134752feb3

Your endpoint must confirm hub.verify_token matches what you sent, then respond within 2 seconds with HTTP 200, Content-Type: application/json, echoing the challenge back exactly:

{"hub.challenge":"15f7d1a91c1f40f8a748fd134752feb3"}

If the handshake times out or the echoed challenge does not match, the subscription is not created. Once it is live, Strava POSTs an event to the same URL whenever something changes:

{
  "aspect_type": "create",
  "event_time": 1752091200,
  "object_id": 987654321,
  "object_type": "activity",
  "owner_id": 12345,
  "subscription_id": 1234,
  "updates": {}
}

The payload is a pointer, not the data — take the owner_id and object_id and call GET /activities/{id} (or the athlete's activity list) with that athlete's token to fetch the full record. object_type is activity or athlete, and aspect_type is create, update, or delete. Watch for an athlete update carrying updates.authorized: "false", which signals the athlete deauthorized your app — clean up their stored tokens when you see it. To receive events for private activities, that athlete's token must have activity:read_all. Acknowledge every event POST with HTTP 200 within 2 seconds and do the actual fetching and processing asynchronously.

Gotchas and production notes

  • Rate limits. The application default is 200 requests per 15 minutes and 2,000 requests per day (non-upload), as of 2026. Usage is reported in the X-RateLimit-Limit and X-RateLimit-Usage response headers (each a "15-minute, daily" pair); exceeding a limit returns HTTP 429. Read those headers and back off rather than hammering. Higher limits require requesting an increase from Strava, and per-app quotas can vary — verify against the current docs.
  • Rotating refresh tokens are the number-one integration bug. If a background refresh and a user-triggered refresh race, or you forget to persist the new refresh token, athletes silently drop off. Serialize refreshes per athlete and always write back the newest token.
  • Private activities need activity:read_all. Both the REST listings and webhook events hide "Only You" activities without it. If athletes report "missing" runs, this scope is usually why.
  • Keep the Client Secret server-side. The token exchange, refresh, and subscription calls all require it, so run them from your backend, never from a mobile or browser client.
  • Webhooks are pointers, and one per app. Every event needs a follow-up API call to fetch the data, and you get a single subscription for the whole application — fan out to individual athletes on your side using owner_id.

For where Strava fits among the broader wearable and device integrations — and when an aggregator that collapses many providers behind one API is a better fit than integrating each directly — see wearable data APIs. For the surrounding product build, see how to build a running app.

Frequently asked questions

Does the Strava API require approval to use?
No formal partner-approval program is needed to start: you register an application at the Strava API settings page and immediately get a Client ID and Secret to build against. The main constraint is rate limits — the default is around 200 requests per 15 minutes and 2,000 per day as of 2026 — and raising those requires requesting an increase from Strava. Verify the current quotas and any program terms in the official docs before you scale.
How long do Strava access tokens last?
Access tokens are short-lived and expire about 6 hours after they are created (an expires_in of 21600 seconds as of 2026). Store the expires_at value returned with the token so you know when to refresh, and confirm the current lifetime in the docs since Strava can change it.
Why do my Strava refreshes keep failing?
Strava uses rotating refresh tokens: each refresh returns a new access token AND a new refresh token, and the old refresh token stops working. If you keep reusing a stale refresh token, or a background job and a user action race and one overwrites the other, refreshes fail and the athlete has to reconsent. Persist the newest refresh token per athlete and serialize refreshes.
How do I get private activities from Strava?
You need the activity:read_all scope. Plain activity:read only returns activities the athlete has shared beyond 'Only You', and the same applies to webhook events. Request activity:read_all if your product needs the athlete's full history or real-time updates for private activities, and check the returned scope since the athlete can deselect it.
Does the Strava webhook include the activity data?
No. The webhook payload is a pointer with fields like object_id, owner_id, object_type, and aspect_type, not the activity itself. When you receive an event you acknowledge it with HTTP 200 within 2 seconds, then make a follow-up API call with that athlete's token to fetch the full record. You also get only one subscription per application, so fan out to individual athletes on your side.

Keep reading

Independent comparison, last reviewed July 9, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.

← All integration guides · by AIFitnessAPI