Documentation
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)