---
title: "Strava API Code Examples | Apps for Strava"
canonical: https://appsforstrava.com/developers/examples
source: Markdown mirror of https://appsforstrava.com/developers/examples — the HTML page is canonical.
---

# Code Examples

Ready-to-use code snippets to help you get started quickly. Examples available in JavaScript/TypeScript and Python.

## On This Page

-   [Complete OAuth Flow](#oauth-flow)
-   [Fetch Athlete Activities](#fetch-activities)
-   [Get Activity Streams (GPS, Heart Rate, Power)](#activity-streams)
-   [Upload Activity File](#upload-activity)
-   [Webhook Server](#webhook-server)
-   [Automatic Token Refresh](#token-refresh)

## Complete OAuth Flow

A complete example showing how to implement the OAuth authorization flow.

### JavaScript (Node.js + Express)

```
const express = require('express');
const app = express();

const CLIENT_ID = process.env.STRAVA_CLIENT_ID;
const CLIENT_SECRET = process.env.STRAVA_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/callback';

// Step 1: Redirect to Strava authorization
app.get('/auth', (req, res) => {
  const authUrl = new URL('https://www.strava.com/oauth/authorize');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'read,activity:read_all');

  res.redirect(authUrl.toString());
});

// Step 2: Handle callback and exchange code for tokens
app.get('/callback', async (req, res) => {
  const { code } = req.query;

  if (!code) {
    return res.status(400).send('Missing authorization code');
  }

  try {
    const response = await fetch('https://www.strava.com/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        code: code,
        grant_type: 'authorization_code'
      })
    });

    const data = await response.json();

    // Store tokens securely (database, session, etc.)
    // data.access_token, data.refresh_token, data.expires_at

    res.json({
      message: 'Authorization successful!',
      athlete: data.athlete,
      expiresAt: new Date(data.expires_at * 1000)
    });
  } catch (error) {
    console.error('Token exchange failed:', error);
    res.status(500).send('Authorization failed');
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));
```

### Python (Flask)

```
from flask import Flask, redirect, request, jsonify
import requests
import os

app = Flask(__name__)

CLIENT_ID = os.environ['STRAVA_CLIENT_ID']
CLIENT_SECRET = os.environ['STRAVA_CLIENT_SECRET']
REDIRECT_URI = 'http://localhost:5000/callback'

@app.route('/auth')
def auth():
    auth_url = (
        f"https://www.strava.com/oauth/authorize"
        f"?client_id={CLIENT_ID}"
        f"&redirect_uri={REDIRECT_URI}"
        f"&response_type=code"
        f"&scope=read,activity:read_all"
    )
    return redirect(auth_url)

@app.route('/callback')
def callback():
    code = request.args.get('code')
    if not code:
        return 'Missing authorization code', 400

    response = requests.post(
        'https://www.strava.com/oauth/token',
        data={
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'code': code,
            'grant_type': 'authorization_code'
        }
    )

    data = response.json()

    # Store tokens securely
    # data['access_token'], data['refresh_token'], data['expires_at']

    return jsonify({
        'message': 'Authorization successful!',
        'athlete': data['athlete']
    })

if __name__ == '__main__':
    app.run(port=5000)
```

## Fetch Athlete Activities

### JavaScript

```
async function getActivities(accessToken, page = 1, perPage = 30) {
  const response = await fetch(
    `https://www.strava.com/api/v3/athlete/activities?page=${page}&per_page=${perPage}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  return response.json();
}

// Get activities from the last 30 days
async function getRecentActivities(accessToken) {
  const thirtyDaysAgo = Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60);

  const response = await fetch(
    `https://www.strava.com/api/v3/athlete/activities?after=${thirtyDaysAgo}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  return response.json();
}

// Usage
const activities = await getActivities(accessToken);
console.log(`Found ${activities.length} activities`);

activities.forEach(activity => {
  console.log(`${activity.name}: ${(activity.distance / 1000).toFixed(2)}km`);
});
```

### Python

```
import requests
from datetime import datetime, timedelta

def get_activities(access_token, page=1, per_page=30):
    response = requests.get(
        'https://www.strava.com/api/v3/athlete/activities',
        headers={'Authorization': f'Bearer {access_token}'},
        params={'page': page, 'per_page': per_page}
    )
    response.raise_for_status()
    return response.json()

def get_recent_activities(access_token, days=30):
    after = int((datetime.now() - timedelta(days=days)).timestamp())

    response = requests.get(
        'https://www.strava.com/api/v3/athlete/activities',
        headers={'Authorization': f'Bearer {access_token}'},
        params={'after': after}
    )
    response.raise_for_status()
    return response.json()

# Usage
activities = get_activities(access_token)
print(f"Found {len(activities)} activities")

for activity in activities:
    distance_km = activity['distance'] / 1000
    print(f"{activity['name']}: {distance_km:.2f}km")
```

## Get Activity Streams

Streams contain detailed time-series data like GPS coordinates, heart rate, power, and elevation.

### JavaScript

```
async function getActivityStreams(accessToken, activityId) {
  const streamTypes = ['time', 'distance', 'latlng', 'altitude', 'heartrate', 'watts', 'cadence'];

  const response = await fetch(
    `https://www.strava.com/api/v3/activities/${activityId}/streams?keys=${streamTypes.join(',')}&key_by_type=true`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  return response.json();
}

// Usage
const streams = await getActivityStreams(accessToken, activityId);

// Access individual streams
if (streams.heartrate) {
  const avgHR = streams.heartrate.data.reduce((a, b) => a + b, 0) / streams.heartrate.data.length;
  console.log(`Average heart rate: ${avgHR.toFixed(0)} bpm`);
}

if (streams.watts) {
  const avgPower = streams.watts.data.reduce((a, b) => a + b, 0) / streams.watts.data.length;
  console.log(`Average power: ${avgPower.toFixed(0)} watts`);
}

if (streams.latlng) {
  console.log(`GPS points: ${streams.latlng.data.length}`);
  // streams.latlng.data is array of [lat, lng] coordinates
}
```

### Python

```
import requests

def get_activity_streams(access_token, activity_id):
    stream_types = ['time', 'distance', 'latlng', 'altitude', 'heartrate', 'watts', 'cadence']

    response = requests.get(
        f'https://www.strava.com/api/v3/activities/{activity_id}/streams',
        headers={'Authorization': f'Bearer {access_token}'},
        params={
            'keys': ','.join(stream_types),
            'key_by_type': 'true'
        }
    )
    response.raise_for_status()
    return response.json()

# Usage
streams = get_activity_streams(access_token, activity_id)

if 'heartrate' in streams:
    hr_data = streams['heartrate']['data']
    avg_hr = sum(hr_data) / len(hr_data)
    print(f"Average heart rate: {avg_hr:.0f} bpm")

if 'watts' in streams:
    power_data = streams['watts']['data']
    avg_power = sum(power_data) / len(power_data)
    print(f"Average power: {avg_power:.0f} watts")
```

## Upload Activity File

### JavaScript (Node.js)

```
const fs = require('fs');

async function uploadActivity(accessToken, filePath, options = {}) {
  // Uses the built-in FormData and fs.openAsBlob (Node 19.8+)
  const form = new FormData();
  form.append('file', await fs.openAsBlob(filePath), 'activity.fit');
  form.append('data_type', options.dataType || 'fit'); // fit, fit.gz, tcx, tcx.gz, gpx, or gpx.gz

  if (options.name) form.append('name', options.name);
  if (options.description) form.append('description', options.description);
  if (options.externalId) form.append('external_id', options.externalId);

  const response = await fetch('https://www.strava.com/api/v3/uploads', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`
    },
    body: form
  });

  return response.json();
}

async function checkUploadStatus(accessToken, uploadId) {
  const response = await fetch(
    `https://www.strava.com/api/v3/uploads/${uploadId}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }
  );

  return response.json();
}

// Usage
const upload = await uploadActivity(accessToken, './morning_ride.fit', {
  name: 'Morning Ride',
  description: 'Beautiful weather today!'
});

console.log(`Upload ID: ${upload.id}`);

// Poll for completion
const checkStatus = async () => {
  const status = await checkUploadStatus(accessToken, upload.id);

  if (status.activity_id) {
    console.log(`Activity created: ${status.activity_id}`);
    return status.activity_id;
  } else if (status.error) {
    console.error(`Upload failed: ${status.error}`);
  } else {
    console.log('Processing...');
    setTimeout(checkStatus, 2000);
  }
};

checkStatus();
```

## Webhook Server

### JavaScript (Express)

```
const express = require('express');
const app = express();
app.use(express.json());

const VERIFY_TOKEN = process.env.STRAVA_VERIFY_TOKEN;
const SUBSCRIPTION_ID = process.env.STRAVA_SUBSCRIPTION_ID;

// Verification endpoint (GET)
app.get('/webhook', (req, res) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === VERIFY_TOKEN) {
    console.log('Webhook verified successfully');
    res.json({ 'hub.challenge': challenge });
  } else {
    res.status(403).send('Verification failed');
  }
});

// Event handler (POST)
app.post('/webhook', async (req, res) => {
  const event = req.body;

  // Verify this is from our subscription
  if (event.subscription_id !== parseInt(SUBSCRIPTION_ID)) {
    console.warn('Unknown subscription ID');
    return res.status(200).send('OK');
  }

  // Respond immediately
  res.status(200).send('OK');

  // Process event asynchronously
  try {
    await handleWebhookEvent(event);
  } catch (error) {
    console.error('Error processing webhook:', error);
  }
});

async function handleWebhookEvent(event) {
  console.log(`Received: ${event.object_type} ${event.aspect_type}`);

  switch (event.object_type) {
    case 'activity':
      await handleActivityEvent(event);
      break;
    case 'athlete':
      await handleAthleteEvent(event);
      break;
  }
}

async function handleActivityEvent(event) {
  const { object_id: activityId, owner_id: athleteId, aspect_type } = event;

  switch (aspect_type) {
    case 'create':
      console.log(`New activity ${activityId} from athlete ${athleteId}`);
      // Fetch activity details and process
      break;
    case 'update':
      console.log(`Activity ${activityId} updated: ${JSON.stringify(event.updates)}`);
      break;
    case 'delete':
      console.log(`Activity ${activityId} deleted`);
      // Remove from your database
      break;
  }
}

async function handleAthleteEvent(event) {
  if (event.updates?.authorized === 'false') {
    console.log(`Athlete ${event.owner_id} deauthorized the app`);
    // Clean up athlete data
  }
}

app.listen(3000, () => console.log('Webhook server running on port 3000'));
```

## Automatic Token Refresh

A helper class that automatically refreshes tokens when they expire.

### JavaScript/TypeScript

```
class StravaClient {
  constructor(clientId, clientSecret, tokens, onTokenRefresh) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.tokens = tokens; // { accessToken, refreshToken, expiresAt }
    this.onTokenRefresh = onTokenRefresh; // Callback to save new tokens
  }

  async getValidAccessToken() {
    // Check if token expires in the next 5 minutes
    const expiresIn = this.tokens.expiresAt - Math.floor(Date.now() / 1000);

    if (expiresIn < 300) {
      await this.refreshTokens();
    }

    return this.tokens.accessToken;
  }

  async refreshTokens() {
    const response = await fetch('https://www.strava.com/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        client_id: this.clientId,
        client_secret: this.clientSecret,
        refresh_token: this.tokens.refreshToken,
        grant_type: 'refresh_token'
      })
    });

    const data = await response.json();

    this.tokens = {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt: data.expires_at
    };

    // Save new tokens to database
    if (this.onTokenRefresh) {
      await this.onTokenRefresh(this.tokens);
    }
  }

  async request(endpoint, options = {}) {
    const accessToken = await this.getValidAccessToken();

    const response = await fetch(`https://www.strava.com/api/v3${endpoint}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        ...options.headers
      }
    });

    if (!response.ok) {
      throw new Error(`Strava API error: ${response.status}`);
    }

    return response.json();
  }

  // Convenience methods
  async getAthlete() {
    return this.request('/athlete');
  }

  async getActivities(params = {}) {
    const query = new URLSearchParams(params).toString();
    return this.request(`/athlete/activities?${query}`);
  }

  async getActivity(id) {
    return this.request(`/activities/${id}`);
  }
}

// Usage
const client = new StravaClient(
  process.env.STRAVA_CLIENT_ID,
  process.env.STRAVA_CLIENT_SECRET,
  userTokens, // From database
  async (newTokens) => {
    await db.updateUserTokens(userId, newTokens);
  }
);

const activities = await client.getActivities({ per_page: 10 });
```

## Community Libraries

While these examples use raw HTTP requests, community-maintained libraries can simplify development:

### JavaScript

-   • `strava-v3` (npm)
-   • `strava-api-client` (npm)

### Python

-   • `stravalib` (pip)

## Next Steps

- [Webhooks Guide](https://appsforstrava.com/developers/webhooks): Subscription setup and event payloads behind the webhook server example
- [Best Practices](https://appsforstrava.com/developers/best-practices): Rate limits, error handling, and production hardening for these snippets
