An open API service indexing awesome lists of open source software.

https://github.com/paladini/node-intervals-icu

A lightweight TypeScript / Javascript client library for the Intervals.icu API.
https://github.com/paladini/node-intervals-icu

intervals intervals-icu

Last synced: 5 months ago
JSON representation

A lightweight TypeScript / Javascript client library for the Intervals.icu API.

Awesome Lists containing this project

README

          

# intervals-icu

A lightweight TypeScript client library for the [Intervals.icu](https://intervals.icu) API. Supports all major endpoints including athletes, events, wellness, workouts, and activities.

## Features

- 🚀 **Lightweight** - Zero dependencies beyond axios
- 📘 **Fully Typed** - Written in TypeScript with comprehensive type definitions
- 🌳 **Tree-shakeable** - ESM exports for optimal bundle size
- 🔐 **Authentication** - Built-in support for API key authentication
- ⚡ **Rate Limiting Awareness** - Tracks and exposes rate limit information
- 🛡️ **Error Handling** - Robust error handling with custom error types
- 📖 **Well Documented** - JSDoc comments on all public methods

## Installation

```bash
npm install intervals-icu
```

## Quick Start

```typescript
import { IntervalsClient } from 'intervals-icu';

// Initialize the client
const client = new IntervalsClient({
apiKey: 'your-api-key-here',
athleteId: 'i12345' // optional, defaults to 'me'
});

// Get athlete information
const athlete = await client.getAthlete();
console.log(`Athlete: ${athlete.name}, FTP: ${athlete.ftp}`);

// Get events
const events = await client.getEvents({
oldest: '2024-01-01',
newest: '2024-12-31'
});

// Create a wellness entry
await client.createWellness({
date: '2024-01-15',
weight: 70,
restingHR: 50,
sleepSecs: 28800
});
```

## API Reference

### Client Configuration

```typescript
interface IntervalsConfig {
apiKey: string; // Required: Your Intervals.icu API key
athleteId?: string; // Optional: Athlete ID (defaults to 'me')
baseURL?: string; // Optional: API base URL
timeout?: number; // Optional: Request timeout in ms (default: 10000)
}
```

### Athlete Methods

#### `getAthlete(athleteId?: string): Promise`

Get athlete information.

```typescript
const athlete = await client.getAthlete();
console.log(athlete.name, athlete.ftp, athlete.weight);
```

#### `updateAthlete(data: Partial, athleteId?: string): Promise`

Update athlete information.

```typescript
const updated = await client.updateAthlete({
ftp: 250,
weight: 70
});
```

### Event Methods

#### `getEvents(options?: PaginationOptions, athleteId?: string): Promise`

Get calendar events.

```typescript
const events = await client.getEvents({
oldest: '2024-01-01',
newest: '2024-12-31'
});
```

#### `getEvent(eventId: number, athleteId?: string): Promise`

Get a specific event by ID.

```typescript
const event = await client.getEvent(12345);
```

#### `createEvent(data: EventInput, athleteId?: string): Promise`

Create a new calendar event.

```typescript
const event = await client.createEvent({
start_date_local: '2024-01-15',
name: 'Race Day',
category: 'RACE',
description: 'Important race'
});
```

#### `updateEvent(eventId: number, data: Partial, athleteId?: string): Promise`

Update an existing event.

```typescript
await client.updateEvent(12345, {
name: 'Updated Race Day'
});
```

#### `deleteEvent(eventId: number, athleteId?: string): Promise`

Delete an event.

```typescript
await client.deleteEvent(12345);
```

#### Filtering Events by Type

Events now include a `type` field that allows you to reliably differentiate between different event types (Run, Ride, Swim, Strength, etc.) without relying on name patterns:

```typescript
// Get all events
const allEvents = await client.getEvents({
oldest: '2024-01-01',
newest: '2024-12-31'
});

// Filter only running events
const runEvents = allEvents.filter(event => event.type === 'Run');

// Filter only cycling events
const rideEvents = allEvents.filter(event => event.type === 'Ride');

// Filter only swimming events
const swimEvents = allEvents.filter(event => event.type === 'Swim');

// Filter only strength training events
const strengthEvents = allEvents.filter(event => event.type === 'Strength');

// Find a specific running workout
const runWorkout = allEvents.find(e =>
e.category === 'WORKOUT' && e.type === 'Run'
);
```

This approach is:
- ✅ Reliable and type-safe
- ✅ Language-agnostic (works with any language/locale)
- ✅ More maintainable than name-based pattern matching

### Wellness Methods

#### `getWellness(options?: PaginationOptions, athleteId?: string): Promise`

Get wellness data.

```typescript
const wellness = await client.getWellness({
oldest: '2024-01-01',
newest: '2024-01-31'
});
```

#### `createWellness(data: WellnessInput, athleteId?: string): Promise`

Create a new wellness entry.

```typescript
const wellness = await client.createWellness({
date: '2024-01-15',
weight: 70,
restingHR: 50,
hrv: 65,
sleepSecs: 28800,
sleepQuality: 8
});
```

#### `updateWellness(date: string, data: Partial, athleteId?: string): Promise`

Update a wellness entry for a specific date.

```typescript
await client.updateWellness('2024-01-15', {
weight: 69.5,
mood: 8
});
```

#### `deleteWellness(date: string, athleteId?: string): Promise`

Delete a wellness entry.

```typescript
await client.deleteWellness('2024-01-15');
```

### Workout Methods

#### `getWorkouts(options?: PaginationOptions, athleteId?: string): Promise`

Get planned workouts.

```typescript
const workouts = await client.getWorkouts({
oldest: '2024-01-01',
newest: '2024-01-31'
});
```

#### `getWorkout(workoutId: number, athleteId?: string): Promise`

Get a specific workout by ID.

```typescript
const workout = await client.getWorkout(12345);
```

#### `createWorkout(data: WorkoutInput, athleteId?: string): Promise`

Create a new workout.

```typescript
const workout = await client.createWorkout({
start_date_local: '2024-01-15',
name: 'Tempo Run',
description: '45 min tempo at threshold',
duration_secs: 2700,
tss: 65
});
```

#### `updateWorkout(workoutId: number, data: Partial, athleteId?: string): Promise`

Update an existing workout.

```typescript
await client.updateWorkout(12345, {
name: 'Updated Tempo Run',
tss: 70
});
```

#### `deleteWorkout(workoutId: number, athleteId?: string): Promise`

Delete a workout.

```typescript
await client.deleteWorkout(12345);
```

### Activity Methods

#### `getActivities(options?: PaginationOptions, athleteId?: string): Promise`

Get recorded activities.

```typescript
const activities = await client.getActivities({
oldest: '2024-01-01',
newest: '2024-01-31'
});
```

#### `getActivity(activityId: number, athleteId?: string): Promise`

Get a specific activity by ID.

```typescript
const activity = await client.getActivity(12345);
```

#### `updateActivity(activityId: number, data: ActivityInput, athleteId?: string): Promise`

Update an existing activity.

```typescript
await client.updateActivity(12345, {
name: 'Morning Run',
description: 'Easy recovery run',
feel: 8
});
```

#### `deleteActivity(activityId: number, athleteId?: string): Promise`

Delete an activity.

```typescript
await client.deleteActivity(12345);
```

## Rate Limiting

The client automatically tracks rate limit information from the API responses:

```typescript
// Make some API calls
await client.getAthlete();

// Check rate limit status
const remaining = client.getRateLimitRemaining();
const resetTime = client.getRateLimitReset();

console.log(`Remaining: ${remaining}, Resets at: ${resetTime}`);
```

## Error Handling

The client throws `IntervalsAPIError` for all API-related errors:

```typescript
import { IntervalsClient, IntervalsAPIError } from 'intervals-icu';

try {
const athlete = await client.getAthlete();
} catch (error) {
if (error instanceof IntervalsAPIError) {
console.error(`API Error: ${error.message}`);
console.error(`Status: ${error.status}`);
console.error(`Code: ${error.code}`);

// Handle specific error types
if (error.code === 'RATE_LIMIT_EXCEEDED') {
console.error('Rate limit exceeded, try again later');
} else if (error.code === 'AUTH_FAILED') {
console.error('Invalid API key');
}
}
}
```

## TypeScript Support

All methods and responses are fully typed. Import types as needed:

```typescript
import type {
Athlete,
Event,
Wellness,
Workout,
Activity,
PaginationOptions,
IntervalsConfig
} from 'node-intervals-icu';
```

## Authentication

To get your API key:

1. Log in to [Intervals.icu](https://intervals.icu)
2. Go to Settings → API Key
3. Copy your API key

Your athlete ID can be found in the URL when viewing your profile (e.g., `i12345`), or you can use `'me'` to refer to the authenticated athlete.

## License

MIT License - Copyright (c) 2025 Fernando Paladini

See the [LICENSE](./LICENSE) file for details.

## Acknowledgments

Special thanks to Filipe for the inspiration and the initial idea that led to the creation of this library. Your project was the spark that made this happen! 🙏

## Contributing

Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) to learn how you can help make this project better.

## Publishing

For maintainers: See the [Publishing Guide](./docs/PUBLISHING.md) for detailed instructions on publishing this library to NPM.

## Links

- [Intervals.icu API Documentation](https://intervals.icu/api/v1/docs)
- [Intervals.icu Website](https://intervals.icu)
- [GitHub Repository](https://github.com/paladini/intervals-icu)