rusty-telemetry — REST API Reference

# rusty-telemetry — REST API Reference

> **Parent note:** [Sim Racing Telemetry Analysis Platform — Project Plan](joplin://6c0dcb2a567348fd9796f50c790082e4)
> **Repo:** `~/RustroverProjects/rusty-telemetry`
> **Companion:** [racecraft — Vue.js Web Client Notes](joplin://50a09627d5d347009197b94bcee90411)
> **Current version:** v0.7.1 (28 endpoints)

---

## Server Configuration

| Env Var | Purpose | Default |
|---------|---------|---------|
| `RUSTY_TELEMETRY_API_PORT` | REST API server port | `8080` |
| `RUSTY_TELEMETRY_API_HOST` | REST API server bind address | `127.0.0.1` |
| `RUSTY_TELEMETRY_GAME_HOST` | IP of the sim machine (KSUDP handshake target, AC :9996) | `127.0.0.1` |
| `RUSTY_TELEMETRY_LISTEN` | Local bind address for incoming feeds (TT :10101, PCARS :5606) | `0.0.0.0` |
| `RUSTY_TELEMETRY_BIND` | Legacy fallback for both GAME_HOST and LISTEN | — |

---

## Endpoints

### Live Telemetry

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/live` | Live telemetry snapshot (all feeds: health, frequency, speed, RPM, gear, inputs, lap time) |

### Recordings

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/recordings` | List all recordings |
| POST | `/api/recordings` | Start new recording (JSON: name, game, track, car, use_case, notes — all optional) |
| GET | `/api/recordings/{id}` | Get recording manifest |
| POST | `/api/recordings/stop` | Stop active recording |
| POST | `/api/recordings/{id}/stop` | Stop specific recording |
| DELETE | `/api/recordings/{id}` | Delete recording (removes directory) |
| GET | `/api/recordings/{id}/data/{feed_name}` | Download raw feed binary |

### Tracks

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/tracks` | List all stored track models (summaries with game, track_name, corner_count) |
| GET | `/api/tracks/{game}/{track_name}` | Get full track model (geometry, corners, sectors, curvature) |
| DELETE | `/api/tracks/{game}/{track_name}` | Delete track model |
| GET | `/api/tracks/{game}/{track_name}/corners` | Get corners only for a track model |
| POST | `/api/tracks/build` | Build track model from completed `track_mapping` session |

### Car Characteristics (v0.7.0)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/characteristics/cars` | List all cars with aggregated shift-point data (session_count, recommended_shift_rpm) |
| GET | `/api/characteristics/cars/{car}` | Get detailed per-gear shift-point profile for a car |

### Metadata

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/use-cases` | List valid use cases (shift_points, car_grip, track_mapping, general) |
| GET | `/api/games` | List supported games (assetto_corsa, assetto_corsa_competizione, project_cars_1, project_cars_2) |

### Sessions

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/sessions` | List all sessions (loaded from persistent storage on startup) |
| POST | `/api/sessions` | Create analysis session (JSON: use_case, label) |
| GET | `/api/sessions/{id}` | Get session details |
| DELETE | `/api/sessions/{id}` | Delete session |
| POST | `/api/sessions/{id}/complete` | Complete session and auto-analyze |
| GET | `/api/sessions/{id}/analysis` | Get analysis results |
| POST | `/api/sessions/{id}/analyze` | Re-run analysis on completed session |
| POST | `/api/sessions/{id}/reopen` | Re-open a completed session for further recording (v0.6.0) |

### Runs

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/sessions/{id}/runs` | Start a run (JSON: feed_name, label, merge_feeds) |
| POST | `/api/sessions/{id}/runs/{run_id}/stop` | Stop a run |
| DELETE | `/api/sessions/{id}/runs/{run_id}` | Delete a run from a session (v0.6.0) |

---

## Request/Response Formats

### Start Recording

```json
// POST /api/recordings
{
  "name": "optional name",
  "game": "assetto_corsa",
  "track": "optional track name",
  "car": "optional car name",
  "use_case": "shift_points",
  "notes": "optional notes"
}
```

### Create Session

```json
// POST /api/sessions
{
  "use_case": "shift_points",
  "label": "optional label"
}
```

### Start Run

```json
// POST /api/sessions/{id}/runs
{
  "feed_name": "ksudp",
  "label": "optional label",
  "merge_feeds": ["telemetry_tool"]
}
```

`merge_feeds` is optional (v0.6.0). When provided, the run merges data from the listed additional feeds with the primary `feed_name`, deduplicating by timestamp.

### Build Track Model

```json
// POST /api/tracks/build
{
  "session_id": "uuid",
  "game": "optional override (defaults to recording's game)",
  "track_name": "optional override (defaults to recording's track)"
}
```

Requires a completed `track_mapping` session with exactly 2 boundary runs (labeled "left" and "right"). Returns the full `TrackModel` with 201 Created on success.

### Run Response

```json
{
  "id": "uuid",
  "label": "run label",
  "feed_name": "ksudp",
  "status": "recording | stopped",
  "start_marker": 0,
  "end_marker": null,
  "frame_count": 0,
  "data_truncated": false
}
```

### Session Response

```json
{
  "id": "uuid",
  "use_case": "shift_points",
  "label": "session label",
  "status": "active | completed",
  "created_at": "ISO 8601",
  "completed_at": "ISO 8601 | null",
  "runs": [],
  "analysis": null,
  "guidance": "string | null",
  "recording_id": "uuid | null",
  "car_name": "Ferrari 488 GT3 | null"
}
```

`car_name` field added in v0.7.0 — cached during run finalization from telemetry frames.

### Car Grip Analysis Response (v0.7.1)

Produced by the `car_grip` use case. Uses **envelope extraction**: for each 10 km/h speed bin, finds the peak |lateral G|, peak acceleration G, and peak braking G. The harder you drive, the more complete the profile.

```json
{
  "summary": {
    "max_lateral_g": 1.42,
    "max_accel_g": 0.85,
    "max_brake_g": 1.35,
    "max_combined_g": 1.58,
    "peak_lateral_speed_kmh": 120.0
  },
  "grip_envelope": [
    { "speed_kmh": 45.0, "max_lateral_g": 1.2, "max_accel_g": 0.8, "max_brake_g": 1.1, "sample_count": 340 }
  ],
  "grip_circle": [
    { "longitudinal_g": 0.5, "lateral_g": -1.2 }
  ],
  "steering_response": [
    { "steer": 0.15, "lateral_g": -1.1, "speed_kmh": 95.0 }
  ],
  "runs_analyzed": 3,
  "total_frames_analyzed": 8400
}
```

### Track Model Response

```json
// GET /api/tracks/{game}/{track_name}
{
  "game": "assetto_corsa",
  "track_name": "monza",
  "center_line": [[0.35, 0.72], ...],
  "left_boundary": [[0.33, 0.74], ...],
  "right_boundary": [[0.37, 0.70], ...],
  "track_width": [12.5, ...],
  "heading": [1.57, ...],
  "curvature": [0.002, ...],
  "corners": [...],
  "sectors": [...],
  "source": "boundary_recording",
  "created_at": "2026-06-08T14:17:39+02:00"
}
```

### Track Summary (List)

```json
// GET /api/tracks
[
  { "game": "assetto_corsa", "track_name": "monza", "source": "boundary_recording", "created_at": "...", "corner_count": 11 }
]
```

### Car Characteristics List Response

```json
// GET /api/characteristics/cars
[
  { "car_name": "Ferrari 488 GT3", "session_count": 3, "recommended_shift_rpm": 7500.0 }
]
```

### Car Characteristics Detail Response

```json
// GET /api/characteristics/cars/{car}
{
  "car_name": "Ferrari 488 GT3",
  "gears": [{ "gear": 2, "min_rpm": 3200, "max_rpm": 8500, "min_speed_kmh": 45.0, "max_speed_kmh": 120.0 }],
  "recommended_shift_rpm": 7500.0,
  "session_count": 3
}
```

### Delete Run Response

```
// DELETE /api/sessions/{id}/runs/{run_id}
// Returns 204 No Content on success
```

### Reopen Session Response

```
// POST /api/sessions/{id}/reopen
// Returns updated UseCaseSession (status: "active", analysis cleared)
```

---

## Session Persistence (v0.7.0)

Sessions are persistent — they survive server restart.

### Storage Layout

```
./data/sessions/
├── <session_id>.json          # Metadata (runs, analysis results, car_name)
└── <session_id>.frames.json   # Telemetry frames (lazy-loaded on demand)
```

### Load Behavior

- **Startup:** `load_all()` reads only metadata JSON files — fast, no frame data loaded
- **Lazy load:** `ensure_frames_loaded()` loads frames for a specific session on first access
- **Normalization:** On reload, any Recording runs are set to Stopped (stale ring-buffer markers)

### Write Behavior

- **Atomic:** Writes to `<file>.json.tmp`, then renames to final name
- **Compact:** Frames serialized to compact JSON (no pretty-printing)
- **Off-thread:** Frame data cloned under lock, serialization + disk write offloaded to `std::thread::spawn`

---

## UDP Feed Ports

| Port | Feed | Protocol | Status |
|------|------|----------|--------|
| 9996 | KSUDP | AC 3-phase handshake | Optional — handshake to GAME_HOST |
| 10101 | Telemetry Tool | AC binary (357-byte driver struct) | Optional — bind to LISTEN |
| 5606 | PCARS | Binary (auto-detect V1/V2) | Optional — bind to LISTEN |

### Port 9996 (KSUDP) — 3-Phase Handshake Protocol
- AC binds to `0.0.0.0:9996` — accepts handshakes from any IP
- rusty-telemetry sends handshake to `$GAME_HOST:9996`
- AC responds to the source address of the handshake → works across machines
- **Reference**: https://docs.google.com/document/d/1KfkZiIluXZ6mMhLWfDX1qAGbvhGRC3ZUzjVIt5FQpp4/pub

### Port 10101 (Telemetry Tool Plugin) — OPTIONAL
- Config: `<AC>/apps/python/Telemetry_Tool_plugin/config.ini`
- Set `to_ip` to the analysis machine's IP for remote operation

### Port 5606 (PCARS) — Bind/Listen
- PCARS sends to a configured target IP:port (default 5606)
- rusty-telemetry binds to `$LISTEN:5606` and listens
- **V1 position data** (added 2026-06-08): reads `world_position` (3×i16), `current_lap_distance`, derives `spline_position`

---

## Version History

### v0.7.1 (2026-06-24) — Car Grip Analysis
- **New use case: `car_grip`** — envelope extraction grip analysis from race-pace laps
  - `analyze_car_grip()` in `analysis.rs` — speed-binned peak G (lateral/accel/brake)
  - Grip circle scatter (longitudinal vs lateral G), steering response scatter
  - Downsampled to ~800 scatter points per dataset
  - Guidance: "Drive 3–5 laps at race pace. Brake hard, carry speed through corners..."
- Added to `UseCase` enum, `all()`, `guidance()`, and analysis dispatch
- 33 tests (unchanged)

### v0.7.0 (2026-06-24) — Session Persistence + Car Characteristics
- **Persistent sessions** — sessions, runs, and analysis survive server restart
  - Metadata at `data/sessions/<id>.json`, frames at `<id>.frames.json` (split, lazy-loaded)
  - Atomic writes (temp + rename), off-thread frame serialization
  - `car_name: Option<String>` cached on `UseCaseSession`
- **Car characteristics** — `GET /api/characteristics/cars`, `GET /api/characteristics/cars/{car}`
  - New module: `characteristics.rs`
- **Removed** obsolete `track_map` use case
- `Deserialize` added to `TelemetryFrame` and `FeedSource`
- 33 unit tests

### Post-v0.6.0 (2026-06-08) — Track Mapping Implementation
- Track API endpoints (`GET /api/tracks`, `GET/DELETE /api/tracks/{game}/{track_name}`, `POST /api/tracks/build`)
- New use case: `track_mapping` — boundary recording workflow
- PCARS V1 position data, dual position mode
- New modules: `track_model.rs` (TrackModel, Corner, Sector, TrackModelStore)

### v0.6.0 (2026-06-06) — Run Management Enhancements
- `DELETE /api/sessions/{id}/runs/{run_id}`, `POST /api/sessions/{id}/reopen`
- Extended `POST /api/sessions/{id}/runs` with `merge_feeds`

### v0.5.0 (2026-06-05) — REST API, Recording Manager, Session Analysis
### v0.4.1 (2026-06-04) — Remote Operation Support
### v0.4.0 (2026-06-04) — PCARS Support
### v0.3.0 (2026-06-04) — TUI + Binary Parsers

---

## Cross-References

- **Main project plan:** [Sim Racing Telemetry Analysis Platform — Project Plan](joplin://6c0dcb2a567348fd9796f50c790082e4)
- **racecraft web client:** [racecraft — Vue.js Web Client Notes](joplin://50a09627d5d347009197b94bcee90411)
- **AC protocol spec:** [Assetto Corsa — UDP Telemetry Protocol Specification](joplin://aed9f3be040943048273a16e05a8100f)
- **ACC protocol spec:** [ACC — UDP Telemetry Protocol Specification](joplin://6ae7005d9810437093d63470cff98b59)
- **Architecture note:** [Architecture & Infrastructure](joplin://c1c3a7b2055642268ab230b95551f470)

---

*Last updated: 2026-06-24 — v0.7.1: car_grip use case with envelope extraction grip analysis. v0.7.0: session persistence, car characteristics DB. 28 total endpoints.*

id: 0c837f4e6b7e462a997cbc19e47c864a
parent_id: 0e8e00b432a840628faa4df5bc2068bc
created_time: 2026-06-06T08:09:46.470Z
updated_time: 2026-06-24T08:23:10.084Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author: 
source_url: 
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin-desktop
source_application: net.cozic.joplin-desktop
application_data: 
order: 1780733386470
user_created_time: 2026-06-06T08:09:46.470Z
user_updated_time: 2026-06-24T08:23:10.084Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 0
type_: 1