IntervalsClient accepts either an API key or an OAuth access token in IntervalsConfig. Pass one, not both: when both are set the bearer token wins and the API key is silently ignored — the client builds a single Authorization header and checks accessToken first.
For personal use, pass apiKey. The SDK sends it as HTTP Basic authentication, with the literal username API_KEY and the key itself as the password:
Authorization: Basic base64("API_KEY:" + apiKey)
This is the SDK's own request-building behaviour, not a documented endpoint. The key comes from Intervals.icu under Settings → Developer. See IntervalsConfig for the full set of construction options.
For apps acting on behalf of another athlete, pass accessToken instead. The SDK sends it as a bearer token:
Authorization: Bearer <accessToken>
The SDK does not implement the OAuth authorization flow itself (there is no method to exchange a code for a token); obtain the token through Intervals.icu's OAuth flow separately, then hand it to the client:
const client = new IntervalsClient({
accessToken: process.env.INTERVALS_ACCESS_TOKEN ?? '',
athleteId: process.env.INTERVALS_ATHLETE_ID,
});
const me = await client.athletes.getAthlete();
console.log(`Hello ${me.name ?? me.id}`);
Most routes accept either credential. Two are scoped to the credential itself rather than to an athlete, and each works with only one of them.
Every athlete-scoped service method takes an optional trailing athleteId parameter. When omitted, the client falls back to the athleteId passed to its constructor (which itself defaults to '0', resolved by the API to the authenticated athlete). One route rejects the '0' alias: GET /athlete/{id}/activity-pace-curves (and its CSV variant) answers 403 unless a real athlete id is passed, so give PerformanceService.getActivityPaceCurves an explicit athleteId (see API behaviour). Passing an explicit id lets one authenticated client act on behalf of a different athlete, such as a coach acting for an athlete they coach:
const client = new IntervalsClient({ apiKey });
const me = await client.athletes.getAthlete();
const athletes = await client.athletes.listAthletes();
for (const athlete of athletes) {
console.log(`${athlete.id} ${athlete.name ?? ''}`);
}
const localDate = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
const now = new Date(); // one timestamp for both bounds
const weekAgo = new Date(now);
weekAgo.setDate(weekAgo.getDate() - 6);
const newest = localDate(now);
const oldest = localDate(weekAgo);
const coached = athletes.find((athlete) => athlete.id !== me.id);
if (coached) {
const activities = await client.activities.listActivities({ oldest, newest }, coached.id);
console.log(`${coached.name ?? coached.id}: ${activities.length} activities in the last 7 days`);
} else {
console.log('No coached athletes');
}