Back to blog
apiarchitectureai-director

Race Control API

A technical overview of the serverless backend that powers Sim RaceCenter — the AI Director pipeline, data model, and security architecture.

·Sim RaceCenter Team·6 min read
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/genai SDK
  • 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

MethodPathDescription
GET/sessionsList race sessions for a center
GET/sessions/{id}Get session details
POST/sessionsCreate a new race session
PUT/sessions/{id}Update session configuration
DELETE/sessions/{id}Delete a session

Director Integration

MethodPathDescription
POST/sessions/{id}/checkinDirector check-in (sends capabilities, triggers Planner)
POST/sessions/{id}/sequences/nextPoll for next AI-generated sequence
GET/sessions/{id}/templatesList AI-generated templates for a session
POST/sessions/{id}/commandsSubmit async commands (from chat bot)

Sequence Library

MethodPathDescription
GET/sequencesList all portable sequences in the library
GET/sequences/{id}Get a specific portable sequence
POST/sequencesCreate a new portable sequence
PUT/sequences/{id}Update a portable sequence
DELETE/sequences/{id}Delete a portable sequence

Drivers and Configuration

MethodPathDescription
GET/driversList drivers for a center
POST/driversCreate a driver profile
GET/centersList 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, or caution)
  • 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 pendingCommands Cosmos 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

ContainerPartition KeyTTLPurpose
raceSessions/centerIdNoneRace session configuration
drivers/centerIdNoneDriver profiles
centers/idNoneCenter (organization) records
sequenceTemplates/raceSessionId7 daysAI-generated templates per session
portableSequences/centerIdNonePre-built sequence library
sessionCheckins/raceSessionIdNoneDirector check-in records
telemetry/raceSessionId24 hoursReal-time telemetry frames
pendingCommands/raceSessionId1 hourAsync commands from chat bot
userProfiles/idNoneUser accounts and roles
operatorSequences/raceSessionIdNoneUser-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 -- RaceDirector role 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