oura-client.ts•10.5 kB
import axios, { AxiosInstance, AxiosError } from 'axios';
import {
PersonalInfoResponse,
SleepResponse,
ActivityResponse,
ReadinessResponse,
HeartRateResponse,
WorkoutResponse,
SessionResponse,
TagResponse,
DailyActivityResponse,
DailySleepResponse,
DailyReadinessResponse,
DailyStressResponse,
WebhookSubscriptionResponse,
DateRangeParams,
WebhookSubscriptionCreateParams,
PersonalInfoResponseSchema,
SleepResponseSchema,
ActivityResponseSchema,
ReadinessResponseSchema,
HeartRateResponseSchema,
WorkoutResponseSchema,
SessionResponseSchema,
TagResponseSchema,
DailyActivityResponseSchema,
DailySleepResponseSchema,
DailyReadinessResponseSchema,
DailyStressResponseSchema,
WebhookSubscriptionResponseSchema,
} from './types.js';
export interface OuraClientConfig {
accessToken: string;
baseUrl?: string;
clientId?: string;
clientSecret?: string;
}
export class OuraAPIError extends Error {
constructor(
message: string,
public statusCode?: number,
public response?: any
) {
super(message);
this.name = 'OuraAPIError';
}
}
export class OuraClient {
private client: AxiosInstance;
private accessToken: string;
private clientId?: string;
private clientSecret?: string;
constructor(config: OuraClientConfig) {
this.accessToken = config.accessToken;
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
this.client = axios.create({
baseURL: config.baseUrl || 'https://api.ouraring.com',
timeout: 30000,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
});
// Add response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response) {
throw new OuraAPIError(
`API Error: ${error.response.statusText}`,
error.response.status,
error.response.data
);
} else if (error.request) {
throw new OuraAPIError('Network Error: No response received');
} else {
throw new OuraAPIError(`Request Error: ${error.message}`);
}
}
);
}
private formatParams(params: DateRangeParams): URLSearchParams {
const searchParams = new URLSearchParams();
if (params.start_date) searchParams.append('start_date', params.start_date);
if (params.end_date) searchParams.append('end_date', params.end_date);
if (params.next_token) searchParams.append('next_token', params.next_token);
return searchParams;
}
// Personal Info
async getPersonalInfo(): Promise<PersonalInfoResponse> {
const response = await this.client.get('/v2/usercollection/personal_info');
const parsed = PersonalInfoResponseSchema.parse(response.data);
// Handle both wrapped and unwrapped response formats
if ('data' in parsed) {
return parsed;
} else {
return { data: parsed };
}
}
// Sleep data
async getSleep(params: DateRangeParams = {}): Promise<SleepResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/sleep${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return SleepResponseSchema.parse(response.data);
}
// Activity data
async getActivity(params: DateRangeParams = {}): Promise<ActivityResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/activity${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return ActivityResponseSchema.parse(response.data);
}
// Readiness data
async getReadiness(params: DateRangeParams = {}): Promise<ReadinessResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/readiness${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return ReadinessResponseSchema.parse(response.data);
}
// Heart Rate data
async getHeartRate(params: DateRangeParams = {}): Promise<HeartRateResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/heartrate${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return HeartRateResponseSchema.parse(response.data);
}
// Workout data
async getWorkouts(params: DateRangeParams = {}): Promise<WorkoutResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/workout${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return WorkoutResponseSchema.parse(response.data);
}
// Session data
async getSessions(params: DateRangeParams = {}): Promise<SessionResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/session${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return SessionResponseSchema.parse(response.data);
}
// Tag data
async getTags(params: DateRangeParams = {}): Promise<TagResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/tag${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return TagResponseSchema.parse(response.data);
}
// Enhanced Tag data
async getEnhancedTags(params: DateRangeParams = {}): Promise<TagResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/enhanced_tag${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return TagResponseSchema.parse(response.data);
}
// Daily Activity data
async getDailyActivity(params: DateRangeParams = {}): Promise<DailyActivityResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/daily_activity${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return DailyActivityResponseSchema.parse(response.data);
}
// Daily Sleep data
async getDailySleep(params: DateRangeParams = {}): Promise<DailySleepResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/daily_sleep${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return DailySleepResponseSchema.parse(response.data);
}
// Daily Readiness data
async getDailyReadiness(params: DateRangeParams = {}): Promise<DailyReadinessResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/daily_readiness${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return DailyReadinessResponseSchema.parse(response.data);
}
// Daily Stress data
async getDailyStress(params: DateRangeParams = {}): Promise<DailyStressResponse> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/daily_stress${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return DailyStressResponseSchema.parse(response.data);
}
// Webhook Subscriptions
async getWebhookSubscriptions(): Promise<WebhookSubscriptionResponse> {
if (!this.clientId || !this.clientSecret) {
throw new OuraAPIError('Client ID and Client Secret are required for webhook operations');
}
const response = await this.client.get('/v2/webhook/subscription', {
headers: {
'x-client-id': this.clientId,
'x-client-secret': this.clientSecret,
},
});
return WebhookSubscriptionResponseSchema.parse(response.data);
}
async createWebhookSubscription(params: WebhookSubscriptionCreateParams): Promise<any> {
if (!this.clientId || !this.clientSecret) {
throw new OuraAPIError('Client ID and Client Secret are required for webhook operations');
}
const response = await this.client.post('/v2/webhook/subscription', params, {
headers: {
'x-client-id': this.clientId,
'x-client-secret': this.clientSecret,
},
});
return response.data;
}
async updateWebhookSubscription(id: string, params: Partial<WebhookSubscriptionCreateParams>): Promise<any> {
if (!this.clientId || !this.clientSecret) {
throw new OuraAPIError('Client ID and Client Secret are required for webhook operations');
}
const response = await this.client.put(`/v2/webhook/subscription/${id}`, params, {
headers: {
'x-client-id': this.clientId,
'x-client-secret': this.clientSecret,
},
});
return response.data;
}
async deleteWebhookSubscription(id: string): Promise<void> {
if (!this.clientId || !this.clientSecret) {
throw new OuraAPIError('Client ID and Client Secret are required for webhook operations');
}
await this.client.delete(`/v2/webhook/subscription/${id}`, {
headers: {
'x-client-id': this.clientId,
'x-client-secret': this.clientSecret,
},
});
}
// Sleep Time data
async getSleepTime(params: DateRangeParams = {}): Promise<any> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/sleep_time${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return response.data;
}
// Rest Mode Periods
async getRestModePeriods(params: DateRangeParams = {}): Promise<any> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/rest_mode_period${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return response.data;
}
// Ring Configuration
async getRingConfiguration(params: DateRangeParams = {}): Promise<any> {
const searchParams = this.formatParams(params);
const url = `/v2/usercollection/ring_configuration${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
const response = await this.client.get(url);
return response.data;
}
}