On this page
The Race Control API is the serverless backend that powers Sim RaceCenter. It manages all configuration data, serves the AI Director pipeline, and acts as the bridge between the web interface (Race Control) and the desktop application (Director).
Infrastructure
- Runtime: Node.js 20, TypeScript
- Hosting: Azure Functions (Flex Consumption plan)
- Database: Azure Cosmos DB (NoSQL, serverless mode)
- AI Models: Google Vertex AI (Gemini models) via
@google/genaiSDK - Auth: Microsoft Entra ID (JWT tokens) for API access; Azure Managed Identity for Cosmos DB; Workload Identity Federation for Google Cloud
- Observability: Application Insights for traces, dependencies, and custom metrics
- Spec: OpenAPI 3.0 (served via built-in docs endpoint)
API Surface
The API follows RESTful conventions with a versioned path prefix: /api/director/v1/.
Session Management
| Method | Path | Description |
|---|---|---|
| GET | /sessions | List race sessions for a center |
| GET | /sessions/{id} | Get session details |
| POST | /sessions | Create a new race session |
| PUT | /sessions/{id} | Update session configuration |
| DELETE | /sessions/{id} | Delete a session |
Director Integration
| Method | Path | Description |
|---|---|---|
| POST | /sessions/{id}/checkin | Director check-in (sends capabilities, triggers Planner) |
| POST | /sessions/{id}/sequences/next | Poll for next AI-generated sequence |
| GET | /sessions/{id}/templates | List AI-generated templates for a session |
| POST | /sessions/{id}/commands | Submit async commands (from chat bot) |
Sequence Library
| Method | Path | Description |
|---|---|---|
| GET | /sequences | List all portable sequences in the library |
| GET | /sequences/{id} | Get a specific portable sequence |
| POST | /sequences | Create a new portable sequence |
| PUT | /sequences/{id} | Update a portable sequence |
| DELETE | /sequences/{id} | Delete a portable sequence |
Drivers and Configuration
| Method | Path | Description |
|---|---|---|
| GET | /drivers | List drivers for a center |
| POST | /drivers | Create a driver profile |
| GET | /centers | List centers |
AI Director Pipeline
The AI Director is the core intelligence of Sim RaceCenter. It uses a two-tier architecture to generate broadcast sequences in real time.
Tier 1: Planner
Model: gemini-3.0-pro
Trigger: Fires once per session at check-in time (fire-and-forget)
Purpose: Generates a library of parameterized sequence templates tailored to the specific session
The Planner receives:
- Director capabilities: All intents the Director can execute (from check-in payload)
- Connection health: Which integrations are online (iRacing, OBS, Discord, YouTube)
- Session configuration: Simulator type, schedule type, driver count, OBS scenes
- Operator sequences: Any custom sequences the operator has pre-configured
- Generation parameters: Duration ranges, camera variety settings, narrative priorities
It outputs 20-30 SequenceTemplate documents, each containing:
- Template name and description
- Applicability conditions (when this template should be used)
- Priority level (
normal,incident, orcaution) - Duration range (min/max in milliseconds)
- Parameterized steps with
${variable}placeholders - Variable definitions with types, sources, and constraints
Templates are stored in the sequenceTemplates Cosmos DB container with a 7-day TTL, partitioned by raceSessionId.
Tier 2: Executor
Model: gemini-2.5-flash
Trigger: Every poll request (POST .../sequences/next)
Purpose: Selects the best template and fills variables from live telemetry
The Executor receives:
- All session templates (from Planner output)
- Live telemetry snapshot:
- Race flag state (GREEN, CAUTION, RED, WHITE, CHECKERED)
- Average lap time
- Leaderboard (top 20: position, car number, driver name, last lap time)
- Detected battles (driver pairs within 1.0s gap)
- Last executed sequence ID (for variety)
- Generation rules (camera variety, output format, JSON schema)
It outputs:
- Selected template index
- Concrete variable values (real driver names, camera groups, durations)
- Duration in milliseconds
The response is assembled into a PortableSequence -- the universal wire format that the Director can execute.
Retry and Resilience
The Executor includes retry logic with exponential backoff:
- Maximum 3 retries for transient errors (429 rate limit, 503 service unavailable, network errors)
- Backoff from 1 second to 15 seconds with random jitter
- Non-retryable errors (400 bad request, auth errors) fail immediately
- If all retries fail, a fallback default sequence is returned
Pending Commands
The chat bot (Broadcast Agent) can inject async commands that take priority over AI-generated sequences:
- Commands are stored in the
pendingCommandsCosmos container - On each poll, pending commands are checked first
- If a command exists, it's returned instead of (or interlaced with) the AI sequence
- This enables conversational control: "Show driver 42" → immediate camera switch
Data Model
Cosmos DB Containers
| Container | Partition Key | TTL | Purpose |
|---|---|---|---|
raceSessions | /centerId | None | Race session configuration |
drivers | /centerId | None | Driver profiles |
centers | /id | None | Center (organization) records |
sequenceTemplates | /raceSessionId | 7 days | AI-generated templates per session |
portableSequences | /centerId | None | Pre-built sequence library |
sessionCheckins | /raceSessionId | None | Director check-in records |
telemetry | /raceSessionId | 24 hours | Real-time telemetry frames |
pendingCommands | /raceSessionId | 1 hour | Async commands from chat bot |
userProfiles | /id | None | User accounts and roles |
operatorSequences | /raceSessionId | None | User-configured sequences for a session |
Key Types
PortableSequence -- The universal sequence format sent to the Director:
{
"id": "seq_abc123",
"name": "Battle Camera",
"description": "Close-up coverage of a battle between two drivers",
"priority": false,
"steps": [
{ "id": "s1", "intent": "broadcast.showLiveCam", "payload": { "carNum": "42", "camGroup": 3 } },
{ "id": "s2", "intent": "system.wait", "payload": { "durationMs": 8000 } },
{ "id": "s3", "intent": "broadcast.showLiveCam", "payload": { "carNum": "7", "camGroup": 4 } },
{ "id": "s4", "intent": "system.wait", "payload": { "durationMs": 8000 } }
],
"variables": [],
"metadata": {
"totalDurationMs": 16000,
"generatedAt": "2025-07-14T12:00:00Z",
"source": "ai-director"
}
}SequenceTemplate -- Parameterized blueprint from the Planner:
{
"id": "tmpl_battle_closeup",
"raceSessionId": "sess_xyz",
"name": "Battle Close-Up",
"applicability": "Two or more drivers within 1 second gap, battling for position",
"priority": "normal",
"durationRange": { "min": 15000, "max": 35000 },
"steps": [
{ "id": "s1", "intent": "broadcast.showLiveCam", "payload": { "carNum": "${targetDriver}", "camGroup": "${cameraGroup}" } },
{ "id": "s2", "intent": "system.wait", "payload": { "durationMs": "${durationMs}" } },
{ "id": "s3", "intent": "broadcast.showLiveCam", "payload": { "carNum": "${secondDriver}", "camGroup": "${cameraGroup}" } },
{ "id": "s4", "intent": "system.wait", "payload": { "durationMs": "${durationMs}" } }
],
"variables": [
{ "name": "targetDriver", "label": "First battle driver", "type": "text", "required": true, "source": "cloud" },
{ "name": "secondDriver", "label": "Second battle driver", "type": "text", "required": true, "source": "cloud" },
{ "name": "cameraGroup", "label": "Camera group number", "type": "number", "required": true, "source": "cloud" },
{ "name": "durationMs", "label": "Hold duration (ms)", "type": "number", "required": true, "source": "cloud" }
],
"source": "ai-planner",
"ttl": 604800
}Security
- Authentication: All API endpoints require a valid JWT token from Microsoft Entra ID
- Authorization: Role-based access control --
RaceDirectorrole required for director endpoints - Center scoping: Data access is scoped to the user's center (organization)
- Database: System-assigned managed identity (no connection strings or credentials)
- Google Cloud: Workload Identity Federation -- Azure managed identity exchanges tokens with Google Cloud (no API keys)
- CORS: Configured for the Static Web App and Director origins only