✅ Do's
- Store credentials in environment variables
- Implement error handling for all API calls
- Cache responses to reduce API load
- Use TypeScript for better type safety
- Handle maintenance mode gracefully
Build your first iRacing application in 5 minutes! This guide will walk you through creating a simple app that fetches and displays your racing statistics.
A simple Node.js application that:
Create a new project
mkdir my-iracing-appcd my-iracing-appnpm init -yInstall the Data Client
npm install iracing-data-clientnpm install --save-dev typescript @types/node tsxSet up environment variables
Create a .env file in your project root. You’ll need OAuth credentials from iRacing — see OAuth Client Credentials to register.
IRACING_CLIENT_ID=your-client-idIRACING_CLIENT_SECRET=your-client-secretIRACING_USERNAME=your-iracing-emailIRACING_PASSWORD=your-iracing-passwordCreate your application
Create a file named app.ts:
import { IRacingDataClient, IRacingError } from 'iracing-data-client';
async function main() { try { // Initialize the 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!, }, });
// Fetch member info console.log('🏁 Connecting to iRacing...\n'); const memberInfo = await iracing.member.info();
console.log(`👋 Welcome, ${memberInfo.displayName}!`); console.log(`📊 Customer ID: ${memberInfo.custId}`); console.log(`📅 Member Since: ${new Date(memberInfo.memberSince).toLocaleDateString()}`);
// Get recent races console.log('\n🏎️ Recent Races:'); const recentRaces = await iracing.stats.memberRecentRaces({ custId: memberInfo.custId });
recentRaces.races.slice(0, 5).forEach(race => { console.log(` • ${race.seriesName} at ${race.trackName}`); console.log(` Finished P${race.finishPosition} (Started P${race.startPosition})`); console.log(` SOF: ${race.strengthOfField}\n`); });
// Get career stats console.log('📈 Career Statistics:'); const careerStats = await iracing.stats.memberCareer({ custId: memberInfo.custId });
console.log(` • Total Races: ${careerStats.stats[0]?.starts || 0}`); console.log(` • Total Wins: ${careerStats.stats[0]?.wins || 0}`); console.log(` • Average Incidents: ${careerStats.stats[0]?.avgIncidents || 0}`);
} catch (error) { if (error instanceof IRacingError) { console.error('❌ iRacing API Error:', error.message); if (error.isMaintenanceMode) { console.log('🔧 iRacing is currently in maintenance mode.'); } } else { console.error('❌ Unexpected error:', error); } }}
main();Run your application
npx tsx app.tsWhen you run the application, you should see something like:
🏁 Connecting to iRacing...
👋 Welcome, John Doe!📊 Customer ID: 123456📅 Member Since: 1/15/2020
🏎️ Recent Races: • IMSA Sportscar Championship at Road America Finished P3 (Started P5) SOF: 2150
• GT3 Fixed at Spa-Francorchamps Finished P1 (Started P2) SOF: 1875
📈 Career Statistics: • Total Races: 542 • Total Wins: 47 • Average Incidents: 3.2Now that you have a working application, try adding more features:
// Get current season standingsconst seasons = await iracing.season.list({ seasonYear: new Date().getFullYear(), seasonQuarter: Math.ceil((new Date().getMonth() + 1) / 3)});
const mySeries = seasons.find(s => s.seriesName === 'GT3 Fixed');
if (mySeries) { const standings = await iracing.stats.seasonDriverStandings({ seasonId: mySeries.seasonId, carClassId: mySeries.carClassIds[0] });
const myStanding = standings.driverStandings.find( d => d.custId === memberInfo.custId );
console.log(`Position: ${myStanding?.position}`); console.log(`Points: ${myStanding?.points}`);}// Get world records for a trackconst records = await iracing.stats.worldRecords({ trackId: 266, // Spa-Francorchamps carId: 100 // Mercedes AMG GT3});
records.forEach(record => { console.log(`${record.trackConfigName}`); console.log(` Lap: ${record.lapTime}s`); console.log(` By: ${record.displayName}`);});// Get league informationconst leagues = await iracing.league.directory({ search: 'My League Name'});
if (leagues.length > 0) { const leagueDetails = await iracing.league.get({ leagueId: leagues[0].leagueId });
console.log(`League: ${leagueDetails.leagueName}`); console.log(`Members: ${leagueDetails.rosterCount}`); console.log(`Owner: ${leagueDetails.ownerName}`);}Here are more complete example applications:
import { Client, GatewayIntentBits } from 'discord.js';import { IRacingDataClient } from 'iracing-data-client';
const discord = new Client({ intents: [GatewayIntentBits.Guilds],});
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!, },});
discord.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return;
if (interaction.commandName === 'driver') { const custId = interaction.options.getInteger('id')!;
const member = await iracing.member.get({ custIds: [custId], });
await interaction.reply({ embeds: [{ title: member.members[0].displayName, fields: [ { name: 'Last Login', value: member.members[0].lastLogin }, { name: 'Member Since', value: member.members[0].memberSince } ] }] }); }});import express from 'express';import { IRacingDataClient } from 'iracing-data-client';
const app = express();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!, },});
// Cache middlewareconst cache = new Map();const cacheMiddleware = (duration: number) => { return (req, res, next) => { const key = req.originalUrl; const cached = cache.get(key);
if (cached && cached.expires > Date.now()) { return res.json(cached.data); }
res.sendResponse = res.json; res.json = (data) => { cache.set(key, { data, expires: Date.now() + duration * 1000 }); return res.sendResponse(data); }; next(); };};
app.get('/api/member/:id', cacheMiddleware(300), // Cache for 5 minutes async (req, res) => { try { const data = await iracing.member.get({ custIds: [parseInt(req.params.id)] }); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } });
app.listen(3000, () => { console.log('API server running on port 3000');});✅ Do's
❌ Don'ts
async function safeApiCall<T>( apiCall: () => Promise<T>): Promise<T | null> { try { return await apiCall(); } catch (error) { if (error instanceof IRacingError) { if (error.isMaintenanceMode) { console.log('iRacing is in maintenance mode'); } else if (error.isRateLimited) { console.log('Rate limited, waiting...'); await new Promise(r => setTimeout(r, 60000)); return safeApiCall(apiCall); } } console.error('API call failed:', error); return null; }}
// Usageconst data = await safeApiCall(() => iracing.member.info());async function getMembersBatch(custIds: number[]) { const batchSize = 50; // iRacing limit const results = [];
for (let i = 0; i < custIds.length; i += batchSize) { const batch = custIds.slice(i, i + batchSize); const data = await iracing.member.get({ custIds: batch }); results.push(...data.members);
// Respect rate limits if (i + batchSize < custIds.length) { await new Promise(r => setTimeout(r, 100)); } }
return results;}Congratulations! You’ve built your first iRacing application. Here’s what to explore next: