Automatic OAuth2 Authentication
Handles the complete OAuth2 flow including token acquisition, Bearer header injection, and automatic token refresh.
The client provides the core functionality for connecting to the iRacing Data API, handling OAuth2 authentication, and managing requests.
The main entry point for using the Data Client.
import { IRacingDataClient } from 'iracing-data-client';
const iracing = new IRacingDataClient(options);interface IRacingClientOptions { auth: AuthConfig; fetchFn?: FetchLike; validateParams?: boolean; validateSemanticParams?: boolean; stores?: HttpClientStores;}Type: AuthConfig (required)
Authentication configuration. A discriminated union of two OAuth2 flows:
PasswordLimitedAuth — for scripts, CLIs, and backend servicesAuthorizationCodeAuth — for web apps acting on behalf of other usersSee Authentication Configuration below for full details.
const iracing = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, username: process.env.IRACING_USERNAME!, password: process.env.IRACING_PASSWORD!, },});Type: FetchLike (optional)
Custom fetch implementation for HTTP requests. Useful for testing or environments without global fetch.
const iracing = new IRacingDataClient({ auth: { /* ... */ }, fetchFn: myCustomFetch,});Type: boolean (optional, default: true)
Enable or disable runtime parameter validation using Zod schemas.
const iracing = new IRacingDataClient({ auth: { /* ... */ }, validateParams: false,});Type: boolean (optional, default: true)
Enable or disable semantic validation for known endpoint parameter combinations (e.g. validating that a seasonId/carClassId pair exists).
const iracing = new IRacingDataClient({ auth: { /* ... */ }, validateSemanticParams: false,});Type: HttpClientStores (optional)
Pluggable stores for caching, deduplication, and rate limiting, powered by @http-client-toolkit/core. When omitted, the SDK operates without external stores.
Available store keys:
cache — A CacheStore for caching S3 responses. TTL is automatically derived from presigned URL expiry.rateLimit — A RateLimitStore or AdaptiveRateLimitStore for automatic request throttling.dedupe — A DedupeStore for deduplicating concurrent identical requests.const iracing = new IRacingDataClient({ auth: { /* ... */ }, stores: { cache: myCacheStore, rateLimit: myRateLimitStore, dedupe: myDedupeStore, },});See the Caching Guide and Rate Limiting Guide for detailed examples.
The auth option accepts a discriminated union (AuthConfig) of two OAuth2 flows.
For scripts, CLI tools, and backend services running on behalf of the registered client owner. This is the most common flow.
interface PasswordLimitedAuth { type: 'password-limited'; clientId: string; clientSecret: string; username: string; password: string; tokens?: { accessToken: string; refreshToken?: string; expiresAt?: number; }; onTokenRefresh?: OnTokenRefresh;}The client authenticates automatically on the first API call. You can optionally provide pre-obtained tokens to skip the initial token request, and an onTokenRefresh callback to persist tokens for reuse across sessions.
const iracing = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, username: process.env.IRACING_USERNAME!, password: process.env.IRACING_PASSWORD!, onTokenRefresh: async (tokens) => { await fs.writeFile('tokens.json', JSON.stringify(tokens)); }, },});For web and desktop applications that access data on behalf of other users. Requires completing the OAuth flow externally before creating the client.
interface AuthorizationCodeAuth { type: 'authorization-code'; clientId: string; clientSecret: string; tokens: { accessToken: string; refreshToken?: string; expiresAt?: number; }; onTokenRefresh?: OnTokenRefresh;}Use the exported helper functions to complete the flow:
import { IRacingDataClient, buildAuthorizationUrl, exchangeAuthorizationCode,} from 'iracing-data-client';
// Step 1: Build the authorization URL and redirect the userconst { url, state, pkce } = await buildAuthorizationUrl({ clientId: process.env.IRACING_CLIENT_ID!, redirectUri: 'https://myapp.com/callback', usePKCE: true,});
// Step 2: Exchange the authorization code for tokens (in your callback handler)const tokens = await exchangeAuthorizationCode({ clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, code: callbackCode, redirectUri: 'https://myapp.com/callback', codeVerifier: pkce?.verifier,});
// Step 3: Create the client with the obtained tokensconst iracing = new IRacingDataClient({ auth: { type: 'authorization-code', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, tokens: { accessToken: tokens.access_token, refreshToken: tokens.refresh_token, }, onTokenRefresh: (newTokens) => { // Persist refreshed tokens for the user's session }, },});The SDK instance provides access to all service classes:
const iracing = new IRacingDataClient(options);
// Access services as propertiesiracing.car // CarServiceiracing.carclass // CarclassServiceiracing.constants // ConstantsServiceiracing.driverStatsByCategory // DriverStatsByCategoryServiceiracing.hosted // HostedServiceiracing.league // LeagueServiceiracing.lookup // LookupServiceiracing.member // MemberServiceiracing.results // ResultsServiceiracing.season // SeasonServiceiracing.series // SeriesServiceiracing.stats // StatsServiceiracing.team // TeamServiceiracing.timeAttack // TimeAttackServiceiracing.track // TrackServiceThe underlying client that handles HTTP requests. You typically don’t interact with this directly.
Automatic OAuth2 Authentication
Handles the complete OAuth2 flow including token acquisition, Bearer header injection, and automatic token refresh.
S3 Link Following
Automatically follows presigned S3 URLs returned by the API for accessing data.
Pluggable Stores
Supports optional caching, deduplication, and rate limiting via @http-client-toolkit/core stores.
Parameter Transformation
Automatically converts between camelCase (SDK) and snake_case (API) formats.
The client provides these internal methods (used by services):
get<T>(url, options)Makes a GET request to the API.
async get<T>( url: string, options?: { params?: Record<string, unknown>; schema?: ZodSchema; }): Promise<T>isAuthenticated()Returns true if the client has valid, non-expired tokens.
isAuthenticated(): booleanclearTokens()Clears stored tokens, requiring re-authentication on the next request.
clearTokens(): voidError thrown for iRacing Data API errors (non-2xx HTTP responses).
class IRacingError extends Error { status?: number; // HTTP status code statusText?: string; // HTTP status text responseData?: unknown; // Response body
// Computed helper properties get isMaintenanceMode(): boolean; // True if 503 maintenance get isRateLimited(): boolean; // True if 429 rate limit get isUnauthorized(): boolean; // True if 401 unauthorized}Error thrown for OAuth-related failures (token requests, invalid credentials).
class OAuthError extends Error { code: OAuthErrorCode | string; description?: string; uri?: string;
get isInvalidGrant(): boolean; get isInvalidClient(): boolean; get isRateLimited(): boolean; get isUnauthorizedClient(): boolean;}Error thrown when token refresh fails and no recovery is possible. Extends OAuthError.
class TokenRefreshError extends OAuthError { cause?: Error;}import { IRacingError } from 'iracing-data-client';
try { const data = await iracing.member.info();} catch (error) { if (error instanceof IRacingError) { console.error(`API Error: ${error.message}`); console.error(`Status: ${error.status}`); }}import { IRacingError, OAuthError, TokenRefreshError } from 'iracing-data-client';
try { const data = await iracing.member.info();} catch (error) { if (error instanceof TokenRefreshError) { console.log('Session expired - user needs to re-authenticate'); } else if (error instanceof OAuthError) { if (error.isInvalidClient) { console.log('Invalid client credentials'); } } else if (error instanceof IRacingError) { if (error.isMaintenanceMode) { console.log('iRacing is in maintenance mode'); } else if (error.isRateLimited) { console.log('Rate limited - wait before retry'); } else if (error.isUnauthorized) { console.log('Authentication failed'); } }}import { IRacingError } from 'iracing-data-client';
async function withRetry<T>( fn: () => Promise<T>, retries = 3): Promise<T> { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (error) { if (error instanceof IRacingError) { // Don't retry auth errors if (error.isUnauthorized) throw error;
// Retry rate limits with backoff if (error.isRateLimited && i < retries - 1) { await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000) ); continue; } } throw error; } } throw new Error('Max retries exceeded');}
// Usageconst data = await withRetry(() => iracing.member.info());The SDK exports all TypeScript types for responses and parameters:
import type { // Auth types AuthConfig, PasswordLimitedAuth, AuthorizationCodeAuth, TokenResponse, OnTokenRefresh, FetchLike,
// Client types IRacingClientOptions, HttpClientStores,
// Response types MemberInfoResponse, CarGetResponse, TrackAssetsResponse,
// Parameter types MemberGetParams, ResultsSearchSeriesParams, StatsSeasonDriverStandingsParams,} from 'iracing-data-client';
// Classes — use value imports for instanceof checksimport { IRacingError, OAuthError, TokenRefreshError, buildAuthorizationUrl, exchangeAuthorizationCode,} from 'iracing-data-client';import { IRacingDataClient } from 'iracing-data-client';
const iracing = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, username: process.env.IRACING_USERNAME!, password: process.env.IRACING_PASSWORD!, },});import * as fs from 'node:fs/promises';import { IRacingDataClient } from 'iracing-data-client';
const iracing = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, username: process.env.IRACING_USERNAME!, password: process.env.IRACING_PASSWORD!, onTokenRefresh: async (tokens) => { await fs.writeFile('tokens.json', JSON.stringify(tokens)); }, },});import * as fs from 'node:fs/promises';import { IRacingDataClient } from 'iracing-data-client';
const savedTokens = JSON.parse(await fs.readFile('tokens.json', 'utf-8'));
const iracing = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.IRACING_CLIENT_ID!, clientSecret: process.env.IRACING_CLIENT_SECRET!, username: process.env.IRACING_USERNAME!, password: process.env.IRACING_PASSWORD!, tokens: { accessToken: savedTokens.access_token, refreshToken: savedTokens.refresh_token, }, onTokenRefresh: async (tokens) => { await fs.writeFile('tokens.json', JSON.stringify(tokens)); }, },});const iracing = new IRacingDataClient({ auth: { /* ... */ }, validateParams: false, validateSemanticParams: false,});const mainAccount = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.MAIN_CLIENT_ID!, clientSecret: process.env.MAIN_CLIENT_SECRET!, username: process.env.MAIN_USERNAME!, password: process.env.MAIN_PASSWORD!, },});
const altAccount = new IRacingDataClient({ auth: { type: 'password-limited', clientId: process.env.ALT_CLIENT_ID!, clientSecret: process.env.ALT_CLIENT_SECRET!, username: process.env.ALT_USERNAME!, password: process.env.ALT_PASSWORD!, },});When a CacheStore is provided via the stores option, the client caches S3 responses with TTL derived from the presigned URL expiry:
expires fieldstores.cache, no caching is performedSee the Caching Guide for setup and store implementations.
Best practices:
RateLimitStore via stores.rateLimit for automatic throttlingThe client reuses the same authenticated session across all requests:
const iracing = new IRacingDataClient(options);
// All these requests share the same OAuth tokensawait iracing.member.info();await iracing.car.get();await iracing.track.assets();