Code Examples

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

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',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        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');
const FormData = require('form-data');

async function uploadActivity(accessToken, filePath, options = {}) {
  const form = new FormData();
  form.append('file', fs.createReadStream(filePath));
  form.append('data_type', options.dataType || 'fit'); // fit, tcx, or gpx

  if (options.name) form.append('name', options.name);
  if (options.description) form.append('description', options.description);
  if (options.activityType) form.append('activity_type', options.activityType);

  const response = await fetch('https://www.strava.com/api/v3/uploads', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      ...form.getHeaders()
    },
    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',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        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)
  • stravaio (pip)