Back to blog

Strava API Developer Guide: Activities, Heart Rate and GPS Data

Open Wearables Team · · 7 min read

Key Takeaways

  • The Strava API gives access to activities, GPS traces, heart rate, power, pace and elevation, but not passive health metrics like sleep or HRV
  • OAuth 2.0 with short-lived access tokens requires an active token refresh strategy in your backend
  • Strava enforces strict rate limits: 100 requests per 15 minutes and 1000 per day per user
  • Webhooks are the right way to receive new activity data in near real time, polling is not
  • Open Wearables handles OAuth, token refresh, rate limits and webhooks alongside 8 other providers in a single self-hosted deployment

Introduction

Strava has over 100 million users logging runs, rides, swims and hikes every day. For developers building fitness apps, coaching platforms or health dashboards, that volume of activity data is genuinely valuable. A user who logs every workout on Strava has months or years of structured training history: pace progression, power output, elevation trends, heart rate zones. For a coaching app, that's the foundation of any meaningful analysis.

But connecting to Strava and keeping data flowing reliably in production is more engineering work than it initially appears. OAuth 2.0 is the starting point, not the finish line. Tokens expire every six hours and need to be refreshed proactively. Rate limits are enforced per athlete, not globally, which means you need per-user tracking in your infrastructure. Webhooks need a validated public endpoint before Strava will send you anything. And all of this needs to be running cleanly before you can even think about the product layer.

This guide walks through what the Strava API actually provides, how authentication and rate limits work in practice, and what the real operational costs are when you move beyond a basic prototype.

What the Strava API Covers

Strava's API is purpose-built for sports activities. It is not a general-purpose health data API. Understanding what it does and doesn't include upfront saves significant time when scoping an integration.

Activity data you can access:

Strava exposes structured data for every recorded activity. This includes the activity type (Strava supports over 80 sport types, from outdoor runs and rides to indoor rowing and virtual cycling), distance, duration, moving time, and elevation. For activities with heart rate monitors, you get average heart rate and max heart rate per session. For cycling with a power meter, you get watts, normalized power, weighted average power and intensity factor. For running, you get pace, splits per kilometer or mile, and cadence if the user's watch records it. Calories burned are available for all activity types.

GPS data is one of Strava's strengths. Every outdoor activity with location tracking includes a full GPS trace encoded as a polyline, which you can decode to a sequence of latitude and longitude coordinates. This enables route visualization, elevation profiling and segment matching. Lap data is available for pool swims, track workouts and any activity where the user manually pressed the lap button.

What Strava does not provide:

Strava is an activity platform, not a recovery or health monitoring platform. It does not expose resting heart rate, heart rate variability, sleep data, body composition, stress scores or any continuous (non-workout) biometric data. If your app needs those signals, you need additional providers like Garmin, Whoop or Oura alongside Strava.

Authentication and Token Management

Strava uses OAuth 2.0 with the authorization code flow. Users are redirected to Strava's authorization page, approve access, and are redirected back to your app with a one-time authorization code. You exchange that code for an access token and a refresh token.

The critical operational detail: Strava access tokens expire after six hours. This is significantly shorter than most other provider tokens. In practice, it means you cannot store an access token at connection time and use it later. You need a token refresh mechanism that checks expiry before every API call and refreshes automatically when the token is near expiration.

Refresh tokens do not expire on a fixed schedule, but they are invalidated if the user deauthorizes your application in Strava's settings. Your code needs to handle 401 responses gracefully, detect deauthorization, and surface the appropriate re-authentication flow to users when it happens.

The scopes you need depend on what data you're accessing. For reading non-private activities, request activity:read. For reading all activities including those the user has marked private, request activity:read_all. For basic profile information, request read. Requesting the minimum necessary scope improves authorization conversion rates since users see exactly what your app will access.

Rate Limits in Production

Strava enforces two rate limit windows: 100 requests per 15-minute window per athlete, and 1000 requests per day per athlete. These are independent limits and both apply simultaneously. Every API response includes X-RateLimit-Limit and X-RateLimit-Usage headers that tell you the current usage against both windows.

In development with a single test account, rate limits feel generous. In production with real users who have years of activity history, the constraints become meaningful quickly. Consider the math: if you want to backfill six months of activities for a new user, and each user has been training five days per week, that's roughly 130 activities. Fetching activity summaries, then fetching per-activity stream data for GPS and heart rate, you could easily spend 260 or more API calls on initial sync for a single user.

For ongoing sync, webhooks are the answer. Instead of polling for new activities on a schedule (which burns through rate limits), you subscribe to Strava's webhook events and receive a notification whenever a user creates, updates or deletes an activity. Your server fetches only the new or updated activity when notified. This keeps daily API usage low regardless of how active your users are.

Strava returns HTTP 429 when a rate limit is exceeded. Implement exponential backoff with jitter for 429 handling, and make sure your webhook processing is asynchronous so a slow downstream operation doesn't time out the webhook delivery.

Webhook Setup

Strava's webhook subscription requires a publicly reachable HTTPS endpoint that can respond to a validation challenge. During setup, Strava sends a GET request to your endpoint with a hub.challenge parameter. Your server must respond with that value. Only after successful validation does Strava start sending event notifications.

Event payloads are lightweight. They include the activity ID, the athlete ID, the event type (create, update, delete) and a timestamp. They do not include the activity data itself. Your handler receives the notification, identifies which user the athlete ID corresponds to, and then makes a separate API call to fetch the full activity.

            @app.route('/strava/webhook', methods=['GET', 'POST'])
def strava_webhook():
    if request.method == 'GET':
        return jsonify({'hub.challenge': request.args.get('hub.challenge')})
    event = request.json
    if event['aspect_type'] == 'create' and event['object_type'] == 'activity':
        sync_activity.delay(event['owner_id'], event['object_id'])
    return '', 200
          

Keep the webhook handler response time under two seconds. Queue the actual data fetching asynchronously using Celery, RQ or any task queue. Strava will retry delivery if your endpoint returns a non-200 response, so make activity processing idempotent.

Data Normalization Across Providers

If Strava is one of several providers your app supports, data normalization becomes a significant design concern. Strava's activity schema is specific to Strava: field names, units, and data structures differ from Garmin, Polar or other providers. Your product layer should not depend on provider-specific schemas.

The practical approach is a canonical activity model in your database. Define what an activity looks like in your system: sport type, start time, duration, distance, heart rate, elevation, GPS coordinates. Map each provider's output to that canonical model at ingestion time. Your product code only ever reads from the canonical model, never from provider-specific data.

This pays dividends when you add a new provider. Strava users who also have a Garmin device should see their training history in one unified view, not siloed by device.

Open Wearables and Strava

Open Wearables is an open-source platform that handles Strava's OAuth flow, token refresh, rate limit tracking and webhook management alongside eight other providers in a single self-hosted deployment.

When a user connects Strava through Open Wearables, the platform manages the OAuth exchange, stores tokens securely, registers the webhook subscription, and handles incoming activity events. Your application queries a single normalized API endpoint and receives activity data in a consistent schema regardless of whether it came from Strava, Garmin or Polar.

The platform is MIT licensed, self-hosted via Docker Compose or Railway, and charges zero per-user fees. Your infrastructure costs are the only costs: typically $200 to $500 per month for a startup-scale deployment regardless of user count.

FAQ

Does the Strava API provide sleep or HRV data?

No. Strava is an activity platform. It does not expose sleep, resting heart rate, HRV, recovery scores or any continuous biometric data. For those signals, you need providers like Whoop, Oura, Garmin or Apple Health.

How do Strava's rate limits work across a large user base?

Rate limits are per athlete, not global. 1000 requests per day per athlete means a user base of 10,000 users gives you 10 million daily requests across all users combined. The constraint is usually initial sync for new users with large activity histories, not ongoing operations.

Can I access data without the user re-authenticating?

Yes, as long as you store and refresh the refresh token correctly. The refresh token remains valid until the user explicitly deauthorizes your app in Strava's connected applications settings.

What scopes do I need?

For most use cases: activity:read_all to access all activities including private ones, and read for basic athlete profile information. Request only what you need.

Does Strava support bulk activity export?

There is no bulk export endpoint. Fetch the activity list via GET /athlete/activities with pagination, then fetch individual activities as needed. For initial sync, this means multiple API calls. Budget your rate limit accordingly and spread backfill across multiple days if the user has a large history.

Never miss an update

Stay updated with the latest in open wearables, developer tools, and health data integration.

Join our Community. No spam ever.