Fully typed JavaScript client for the Strava API. Types are generated from Strava's official OpenAPI spec with @hey-api/openapi-ts; the resource layer is hand-written for a flat, ergonomic call shape.
npm install stravaRequires Node 18+ (uses native fetch / FormData).
import { Strava } from 'strava'
const strava = new Strava({
client_id: '123',
client_secret: 'abc',
refresh_token: 'def',
})
const activities = await strava.activities.getLoggedInAthleteActivities()
const activity = await strava.activities.getActivityById(12345, {
include_all_efforts: true,
})
const stats = await strava.athletes.getStats(987)Path IDs are positional. Everything else (query, body) lives in a single options object — Stripe-style.
The client refreshes the access token automatically when it's missing or near expiry. Provide an access_token to skip the initial refresh:
const strava = new Strava({
client_id: '123',
client_secret: 'abc',
access_token: {
access_token: '...',
refresh_token: '...',
expires_at: 1735689600,
},
on_token_refresh: (token) => {
db.set('strava_token', token)
},
})import { Strava } from 'strava'
const { strava, token } = await Strava.fromAuthorizationCode(
{ client_id: '123', client_secret: 'abc' },
code,
)
if (token.athlete) console.log('first login by', token.athlete.id)For building the authorize URL:
import { OAuth } from 'strava'
const url = new OAuth().authorizeUrl({
client_id: '123',
redirect_uri: 'https://app.example.com/oauth/callback',
scope: ['read', 'activity:read_all'],
})strava.getRateLimit()
// { shortTermLimit, shortTermUsage, longTermLimit, longTermUsage, timestamp }Or via callback:
new Strava({
client_id,
client_secret,
refresh_token,
on_rate_limit_update: (rl) => console.log(rl),
})Non-2xx responses throw StravaApiError with status, statusText, and data (parsed Fault when JSON).
import { StravaApiError } from 'strava'
try {
await strava.activities.getActivityById(1)
} catch (e) {
if (e instanceof StravaApiError) console.log(e.status, e.data)
}import { readFileSync } from 'node:fs'
const file = new Blob([readFileSync('ride.fit')])
const upload = await strava.uploads.createUpload({
file,
data_type: 'fit',
name: 'Morning ride',
})Not in the OpenAPI spec; hand-typed module:
const sub = await strava.subscriptions.create({
callback_url: 'https://app.example.com/strava/webhook',
verify_token: 'STRAVA',
})
await strava.subscriptions.list()
await strava.subscriptions.delete(sub.id)For ops not wrapped, drop down to the typed HTTP layer:
const data = await strava.http.request<MyResponse>('GET', '/some/path', {
query: { foo: 'bar' },
})npm install
npm test # vitest run
npm run test:watch
npm run lint
npm run buildnpm run generateDownloads swagger.json, converts to OpenAPI 3, patches known spec bugs, and regenerates src/generated/types.gen.ts. The output is committed — contributors don't need to run this unless the spec changes.