Documentation
Best Practices
Guidelines for building reliable, secure, and performant Strava integrations.
Rate Limiting
Default Rate Limits
- 200requests per 15 minutes
- 2,000requests per day
Requesting Higher Rate Limits
If your app is approaching capacity, you can request a rate limit increase through Strava's Developer Program. Before applying:
- 1.Verify your app is actually hitting limits (check your API settings)
- 2.Optimize your API usage first—use webhooks instead of polling, throttle backfills
- 3.Review the API Agreement and Brand Guidelines
Reading Rate Limit Headers
Every API response includes headers showing your current usage:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed (15min, daily) |
| X-RateLimit-Usage | Current usage (15min, daily) |
Handling Rate Limits
async function stravaRequest(endpoint, accessToken) {
const response = await fetch(`https://www.strava.com/api/v3${endpoint}`, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
// Check rate limits
const limitHeader = response.headers.get('X-RateLimit-Limit');
const usageHeader = response.headers.get('X-RateLimit-Usage');
if (limitHeader && usageHeader) {
const [limit15min, limitDaily] = limitHeader.split(',').map(Number);
const [usage15min, usageDaily] = usageHeader.split(',').map(Number);
console.log(`15min: ${usage15min}/${limit15min}, Daily: ${usageDaily}/${limitDaily}`);
// Warn if approaching limits
if (usage15min > limit15min * 0.8) {
console.warn('Approaching 15-minute rate limit');
}
}
// Handle 429 Too Many Requests
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 900;
console.error(`Rate limited. Retry after ${retryAfter} seconds`);
throw new Error('Rate limited');
}
return response.json();
}Tips to Stay Within Limits
- ✓Cache responses - Store activity data locally instead of fetching repeatedly
- ✓Use webhooks - Get push notifications instead of polling for changes
- ✓Batch requests wisely - Use
per_page=200to get more data per request - ✓Queue and throttle - Spread requests over time for background processing
Security
Protect Your Credentials
- ✗Never commit Client Secret to version control
- ✗Never expose tokens in client-side JavaScript
- ✗Never log tokens or include them in error messages
- ✓Use environment variables for all credentials
- ✓Store tokens encrypted in your database
OAuth Security
- ✓Use the
stateparameter to prevent CSRF attacks - ✓Validate that returned scopes match what you requested
- ✓Exchange authorization codes immediately (they expire quickly)
- ✓Always use HTTPS for your callback URL in production
// Using state parameter to prevent CSRF
const state = crypto.randomBytes(16).toString('hex');
// Store state in session
req.session.oauthState = state;
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', 'activity:read_all');
authUrl.searchParams.set('state', state); // Include state
// In callback, verify state matches
app.get('/callback', (req, res) => {
if (req.query.state !== req.session.oauthState) {
return res.status(403).send('Invalid state parameter');
}
// ... continue with token exchange
});Error Handling
| Status Code | Meaning | Action |
|---|---|---|
| 400 | Bad Request | Check request parameters |
| 401 | Unauthorized | Token expired or invalid - refresh or re-auth |
| 403 | Forbidden | Missing required scope |
| 404 | Not Found | Resource doesn't exist or no access |
| 429 | Too Many Requests | Rate limited - wait and retry |
| 500 | Server Error | Strava issue - retry with backoff |
Robust Error Handler
class StravaAPIError extends Error {
constructor(status, message, response) {
super(message);
this.status = status;
this.response = response;
}
}
async function stravaFetch(endpoint, accessToken, options = {}) {
const response = await fetch(`https://www.strava.com/api/v3${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
...options.headers
}
});
if (!response.ok) {
const errorBody = await response.text();
switch (response.status) {
case 401:
throw new StravaAPIError(401, 'Token expired or invalid', errorBody);
case 403:
throw new StravaAPIError(403, 'Insufficient permissions', errorBody);
case 404:
throw new StravaAPIError(404, 'Resource not found', errorBody);
case 429:
const retryAfter = response.headers.get('Retry-After') || 900;
throw new StravaAPIError(429, `Rate limited. Retry after ${retryAfter}s`, errorBody);
default:
throw new StravaAPIError(response.status, 'API request failed', errorBody);
}
}
return response.json();
}
// Usage with error handling
try {
const activity = await stravaFetch(`/activities/${id}`, accessToken);
} catch (error) {
if (error instanceof StravaAPIError) {
if (error.status === 401) {
// Refresh token and retry
const newToken = await refreshTokens(userId);
const activity = await stravaFetch(`/activities/${id}`, newToken);
} else if (error.status === 429) {
// Schedule retry
await scheduleRetry(task, 15 * 60 * 1000);
}
}
}Token Management
1
Store refresh tokens separately
Keep refresh tokens in a secure, separate location from access tokens
2
Refresh proactively
Refresh tokens before they expire (e.g., when <5 minutes remaining)
3
Handle token rotation
Always save the new refresh token returned after each refresh
4
Handle deauthorization
When tokens fail, prompt user to re-authorize rather than retrying indefinitely
Production Deployment Checklist
- Update callback URL from localhost to production domain
- Ensure callback URL uses HTTPS
- Store credentials in environment variables
- Implement token refresh logic
- Set up webhook subscription for real-time updates
- Implement rate limit handling and backoff
- Add error logging and monitoring
- Cache API responses to reduce requests
- Handle user deauthorization gracefully
- Review Strava API Terms of Service
Strava Brand Guidelines
When building apps that integrate with Strava, follow their brand guidelines:
✓Use "Compatible with Strava" or "Works with Strava" messaging
✓Display "Powered by Strava" logo when showing Strava data
✗Don't imply endorsement or partnership with Strava
✗Don't modify the Strava logo or use it as your app icon