---
title: "How to Integrate the Strava API (2026)"
canonical: "https://aifitnessapi.com/integrate/strava-api"
cluster: "Integration Guides"
primary_query: "how to integrate the Strava API"
last_reviewed: "2026-07-09"
description: "Integrate the Strava API: OAuth 2.0 setup, token exchange, fetching athlete activities, rotating refresh tokens, and the Events API webhook handshake."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"How to Integrate the Strava API (2026)\", AIFitnessAPI, https://aifitnessapi.com/integrate/strava-api"
---

# How to Integrate the Strava API (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.

- Canonical: https://aifitnessapi.com/integrate/strava-api
- Last reviewed: 2026-07-09
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "How to Integrate the Strava API (2026)", AIFitnessAPI, https://aifitnessapi.com/integrate/strava-api

---

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](/fitness-apis/wearable-data-apis) layer and the app-shape decisions in [how to build a running app](/build/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:

| Scope | Grants |
|---|---|
| `read` | Public profile info |
| `activity:read` | Activities visible to Everyone or Followers |
| `activity:read_all` | All activities, including private "Only You" activities |
| `activity:write` | Create 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:

```bash
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:

```json
{
  "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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```json
{"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:

```json
{
  "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](/fitness-apis/wearable-data-apis). For the surrounding product build, see [how to build a running app](/build/running-app).

## FAQ

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

[Permalink](https://aifitnessapi.com/integrate/strava-api#faq-1)

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

[Permalink](https://aifitnessapi.com/integrate/strava-api#faq-2)

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

[Permalink](https://aifitnessapi.com/integrate/strava-api#faq-3)

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

[Permalink](https://aifitnessapi.com/integrate/strava-api#faq-4)

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

[Permalink](https://aifitnessapi.com/integrate/strava-api#faq-5)
