basePath: / definitions: transport.BatteryWire: properties: capacity_mah: type: integer cells: type: integer chemistry: type: string voltage_v: type: number type: object transport.CarAck: properties: car: $ref: '#/definitions/transport.CarWire' error: type: string id: type: string ok: type: boolean type: object transport.CarCreateRequest: properties: car: $ref: '#/definitions/transport.CarWire' type: object transport.CarPatch: properties: active: type: boolean avatar_url: type: string battery: $ref: '#/definitions/transport.BatteryWire' chassis: $ref: '#/definitions/transport.ChassisWire' color_hex: type: string drive: type: string height_mm: type: number length_mm: type: number motor: $ref: '#/definitions/transport.MotorWire' name: type: string scale: type: integer top_speed_ms: type: number visibility: type: string weight_g: type: number width_mm: type: number type: object transport.CarUpdateRequest: properties: id: type: string patch: $ref: '#/definitions/transport.CarPatch' type: object transport.CarWire: properties: active: type: boolean avatar_url: type: string battery: $ref: '#/definitions/transport.BatteryWire' best_lap_ms: type: integer chassis: $ref: '#/definitions/transport.ChassisWire' color_hex: type: string created_ms: type: integer drive: type: string height_mm: type: number id: type: string length_mm: type: number motor: $ref: '#/definitions/transport.MotorWire' name: type: string owner_id: type: string scale: type: integer top_speed_ms: type: number total_distance_m: type: number total_laps: type: integer total_races: type: integer updated_ms: type: integer visibility: type: string weight_g: type: number width_mm: type: number type: object transport.ChassisWire: properties: material: type: string model: type: string printed: type: boolean track_mm: type: number wheelbase_mm: type: number type: object transport.Clan: properties: avatar_url: example: https://cdn.example.com/c/ace.png type: string created_ms: type: integer id: example: clan-1782000-1 type: string name: example: Ace Racing type: string tag: example: ACE type: string updated_ms: type: integer type: object transport.ClanCreateRequest: properties: avatar_url: example: https://cdn.example.com/c/ace.png type: string id: example: clan-1782000-1 type: string name: example: Ace Racing type: string tag: example: ACE type: string type: object transport.ClanListResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.Clan' type: array type: object transport.ClanUpdateRequest: properties: avatar_url: example: https://cdn.example.com/c/ace.png type: string name: example: Ace Racing type: string type: object transport.Driver: properties: avatar_url: example: https://cdn.example.com/u/ace.png type: string clan_id: example: clan-1782000-1 type: string created_ms: type: integer id: example: driver-1782000-123 type: string name: example: Alice type: string nickname: example: ACE type: string updated_ms: type: integer type: object transport.DriverCreateRequest: properties: avatar_url: example: https://cdn.example.com/u/ace.png type: string clan_id: example: clan-1782000-1 type: string id: example: driver-1782000-123 type: string name: example: Alice type: string nickname: example: ACE type: string type: object transport.DriverListResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.Driver' type: array type: object transport.DriverUpdateRequest: properties: avatar_url: example: https://cdn.example.com/u/ace.png type: string clan_id: example: clan-1782000-1 type: string name: example: Alice type: string type: object transport.Envelope: properties: payload: {} seq: type: integer ts_ms: type: integer type: $ref: '#/definitions/transport.MessageType' type: object transport.LeaderboardEntry: properties: avatar_url: example: https://cdn.example.com/u/ace.png type: string best_pos: description: Only populated for track-specific leaderboard example: 1 type: integer best_time_ms: example: 15000 type: integer clan_id: example: clan-seed-001 type: string clan_tag: example: ACE type: string driver_id: example: driver-seed-001 type: string name: example: Alice type: string nickname: example: ACE type: string points: example: 25 type: integer rank: example: 1 type: integer type: object transport.LeaderboardResponse: properties: current_driver: $ref: '#/definitions/transport.LeaderboardEntry' items: items: $ref: '#/definitions/transport.LeaderboardEntry' type: array limit: example: 50 type: integer offset: example: 0 type: integer total: example: 8 type: integer type: object transport.LobbyRace: properties: created_ms: type: integer driver_ids: items: type: string type: array id: type: string laps: type: integer max_cars: type: integer name: type: string podium: description: Podium is populated only for finished races. Empty for live ones. items: $ref: '#/definitions/transport.RacePodiumEntry' type: array started_ms: type: integer status: type: string time_limit_s: type: integer track_id: type: string type: object transport.MessageType: enum: - client_hello - join_race - leave_race - input_state - chat_message - ping - lobby_list - lobby_create - lobby_quick_play - lobby_join_race - lobby_leave_race - lobby_kick_driver - lobby_delete - track_list - track_get - track_create - track_update - track_delete - car_list - car_get - car_create - car_update - car_delete - stats_request - server_hello - race_snapshot - input_ack - correction - race_event - pong - error - lobby_snapshot - track_snapshot - track_ack - car_snapshot - car_ack - catalog_summary - stats_snapshot - driver_profile - lap_recorded - race_result - race_standings type: string x-enum-varnames: - TypeClientHello - TypeJoinRace - TypeLeaveRace - TypeInputState - TypeChatMessage - TypePing - TypeLobbyList - TypeLobbyCreate - TypeLobbyQuickPlay - TypeLobbyJoinRace - TypeLobbyLeaveRace - TypeLobbyKickDriver - TypeLobbyDelete - TypeTrackList - TypeTrackGet - TypeTrackCreate - TypeTrackUpdate - TypeTrackDelete - TypeCarList - TypeCarGet - TypeCarCreate - TypeCarUpdate - TypeCarDelete - TypeStatsRequest - TypeServerHello - TypeRaceSnapshot - TypeInputAck - TypeCorrection - TypeRaceEvent - TypePong - TypeError - TypeLobbySnapshot - TypeTrackSnapshot - TypeTrackAck - TypeCarSnapshot - TypeCarAck - TypeCatalogSummary - TypeStatsSnapshot - TypeDriverProfile - TypeLapRecorded - TypeRaceResult - TypeRaceStandings transport.MotorWire: properties: class: type: string kind: type: string kv: type: integer power_w: type: number type: object transport.RaceListResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.LobbyRace' type: array next_cursor: example: MTcwMDAwMDAwMDAwMDpyYWNlLWFiYzpmaW5pc2hlZA type: string type: object transport.RacePlan: properties: count: example: 0 type: integer created_ms: type: integer enabled: example: true type: boolean fires_done: type: integer id: example: plan-1730000-1 type: string interval_s: example: 3600 type: integer laps: example: 10 type: integer max_cars: example: 4 type: integer name: example: Daily Monza type: string next_fire_ms: type: integer start_at_ms: example: 1730000000000 type: integer time_limit_s: type: integer track_id: example: default type: string updated_ms: type: integer type: object transport.RacePlanCreateRequest: properties: count: example: 0 type: integer enabled: example: true type: boolean id: example: plan-1730000-1 type: string interval_s: example: 3600 type: integer laps: example: 10 type: integer max_cars: example: 4 type: integer name: example: Daily Monza type: string start_at_ms: example: 1730000000000 type: integer time_limit_s: type: integer track_id: example: default type: string type: object transport.RacePlanListResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.RacePlan' type: array next_cursor: type: string type: object transport.RacePodiumEntry: properties: avatar_url: type: string clan_id: example: clan-bob type: string driver_id: example: driver-alice type: string name: example: driver-alice type: string nickname: example: driver-alice type: string position: example: 1 type: integer total_time_ms: example: 123456 type: integer type: object transport.RaceQueueEntry: properties: driver_id: example: driver-42 type: string enqueued_ms: example: 1730000000000 type: integer plan_id: example: plan-1730000-1 type: string race_id: example: race-1730000-7 type: string type: object transport.RaceQueueJoinRequest: properties: driver_id: example: driver-42 type: string limit: example: 3 type: integer type: object transport.RaceQueueJoinResponse: properties: joined: items: $ref: '#/definitions/transport.RaceQueueEntry' type: array skipped: example: - race-1 - race-2 items: type: string type: array type: object transport.RaceQueueListResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.RaceQueueEntry' type: array type: object transport.RaceUpcomingItem: properties: meta: $ref: '#/definitions/transport.LobbyRace' plan_id: type: string queue_len: type: integer start_at_ms: type: integer type: object transport.RaceUpcomingResponse: properties: count: type: integer items: items: $ref: '#/definitions/transport.RaceUpcomingItem' type: array type: object transport.TrackAck: properties: error: type: string id: type: string ok: type: boolean track: $ref: '#/definitions/transport.TrackWire' type: object transport.TrackBounds: properties: length_m: type: number max_x: type: number max_y: type: number min_x: type: number min_y: type: number width_m: type: number type: object transport.TrackCreateRequest: properties: track: $ref: '#/definitions/transport.TrackWire' type: object transport.TrackPatch: properties: centerline: items: $ref: '#/definitions/transport.TrackWaypoint' type: array description: type: string lane_width_m: type: number length_m: type: number name: type: string scale: type: integer surface: type: string tags: items: type: string type: array visibility: type: string width_m: type: number type: object transport.TrackUpdateRequest: properties: id: type: string patch: $ref: '#/definitions/transport.TrackPatch' type: object transport.TrackWaypoint: properties: curvature: type: number heading_rad: type: number speed_ms: type: number x: type: number "y": type: number type: object transport.TrackWire: properties: author_id: type: string best_lap_holder: type: string best_lap_ms: type: integer bounds: $ref: '#/definitions/transport.TrackBounds' centerline: items: $ref: '#/definitions/transport.TrackWaypoint' type: array created_ms: type: integer description: type: string id: type: string lane_width_m: type: number length_m: type: number name: type: string scale: type: integer surface: type: string tags: items: type: string type: array updated_ms: type: integer visibility: type: string width_m: type: number type: object transport.VersionResponse: properties: version: example: 0.1.0-poc type: string type: object transport.WebRTCConnectRequest: properties: driver_id: example: driver-alice type: string sdp: type: string type: example: offer type: string type: object transport.WebRTCConnectResponse: properties: sdp: type: string type: example: answer type: string type: object host: localhost:8080 info: contact: name: x0gp description: |- Minimal HTTP+WebSocket server for the x0gp RC racing PoC. REST endpoints under /api/* manage the track/car catalog; /health and /stats expose server liveness and realtime metrics; /ws carries the realtime race protocol. Auth is intentionally disabled in PoC mode (X0GP_DEV_MODE=true). All catalog mutations are allowed without a JWT. license: name: MIT title: x0gp PoC server API version: 0.1.0-poc paths: /api/cars: get: description: 'Returns all cars visible to the caller. Optional query filters: `scope=system|public|private`, `owner_id=`.' parameters: - description: Filter by visibility enum: - system - public - private in: query name: scope type: string - description: Filter by owner id in: query name: owner_id type: string produces: - application/json responses: "200": description: List of cars with count schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: List cars tags: - cars post: consumes: - application/json description: Creates a new car. The server fills `id` if the client sends an empty one. `owner_id` should be the caller's id in production (auth is disabled in PoC). parameters: - description: Car to create in: body name: car required: true schema: $ref: '#/definitions/transport.CarCreateRequest' produces: - application/json responses: "201": description: Created schema: $ref: '#/definitions/transport.CarAck' "400": description: Invalid input schema: additionalProperties: true type: object "409": description: Car id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Create a car tags: - cars /api/cars/{id}: delete: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/cars/{id}`). System cars are immutable and cannot be updated or deleted. parameters: - description: Car id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: car schema: $ref: '#/definitions/transport.CarUpdateRequest' produces: - application/json responses: "200": description: Car payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.CarWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.CarAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System car is immutable schema: additionalProperties: true type: object "404": description: Car not found schema: additionalProperties: true type: object "409": description: Car id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a car by id tags: - cars get: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/cars/{id}`). System cars are immutable and cannot be updated or deleted. parameters: - description: Car id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: car schema: $ref: '#/definitions/transport.CarUpdateRequest' produces: - application/json responses: "200": description: Car payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.CarWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.CarAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System car is immutable schema: additionalProperties: true type: object "404": description: Car not found schema: additionalProperties: true type: object "409": description: Car id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a car by id tags: - cars put: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/cars/{id}`). System cars are immutable and cannot be updated or deleted. parameters: - description: Car id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: car schema: $ref: '#/definitions/transport.CarUpdateRequest' produces: - application/json responses: "200": description: Car payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.CarWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.CarAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System car is immutable schema: additionalProperties: true type: object "404": description: Car not found schema: additionalProperties: true type: object "409": description: Car id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a car by id tags: - cars /api/catalog: get: description: 'Returns the full in-memory catalog: all tracks, all cars, and aggregate counters. This is the cheapest way for a UI to bootstrap its initial state.' produces: - application/json responses: "200": description: Snapshot of tracks, cars and summary counters schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Combined catalog snapshot tags: - catalog /api/clans: get: description: Returns clans ordered by tag. Limit 1..200, default 50. parameters: - description: Page size in: query name: limit type: integer - description: Page offset in: query name: offset type: integer produces: - application/json responses: "200": description: Page of clans schema: $ref: '#/definitions/transport.ClanListResponse' "500": description: Internal server error schema: additionalProperties: true type: object summary: List clans tags: - clans post: consumes: - application/json description: Creates a new clan. `tag` must be exactly 3 uppercase ASCII letters (e.g. "ACE") and unique. parameters: - description: Clan to create in: body name: clan required: true schema: $ref: '#/definitions/transport.ClanCreateRequest' produces: - application/json responses: "201": description: Created clan schema: $ref: '#/definitions/transport.Clan' "400": description: Invalid input (tag must be 3 uppercase letters) schema: additionalProperties: true type: object "409": description: Clan with that tag already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Create a clan tags: - clans /api/clans/{id}: delete: description: Dispatches on HTTP method. parameters: - description: Clan id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: clan schema: $ref: '#/definitions/transport.ClanUpdateRequest' produces: - application/json responses: "200": description: Clan payload or update result schema: $ref: '#/definitions/transport.Clan' "204": description: Delete ack schema: $ref: '#/definitions/transport.Clan' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Clan not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a clan by id tags: - clans get: description: Dispatches on HTTP method. parameters: - description: Clan id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: clan schema: $ref: '#/definitions/transport.ClanUpdateRequest' produces: - application/json responses: "200": description: Clan payload or update result schema: $ref: '#/definitions/transport.Clan' "204": description: Delete ack schema: $ref: '#/definitions/transport.Clan' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Clan not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a clan by id tags: - clans put: description: Dispatches on HTTP method. parameters: - description: Clan id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: clan schema: $ref: '#/definitions/transport.ClanUpdateRequest' produces: - application/json responses: "200": description: Clan payload or update result schema: $ref: '#/definitions/transport.Clan' "204": description: Delete ack schema: $ref: '#/definitions/transport.Clan' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Clan not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a clan by id tags: - clans /api/drivers: get: description: Returns drivers ordered by nickname. Optional `clan_id` filter. parameters: - description: Page size in: query name: limit type: integer - description: Page offset in: query name: offset type: integer - description: Filter by clan id in: query name: clan_id type: string produces: - application/json responses: "200": description: Page of drivers schema: $ref: '#/definitions/transport.DriverListResponse' "500": description: Internal server error schema: additionalProperties: true type: object summary: List drivers tags: - drivers post: consumes: - application/json description: Creates a new driver. `nickname` must be exactly 3 uppercase ASCII letters (e.g. "ACE") and unique. `clan_id` is optional and must reference an existing clan. parameters: - description: Driver to create in: body name: driver required: true schema: $ref: '#/definitions/transport.DriverCreateRequest' produces: - application/json responses: "201": description: Created driver schema: $ref: '#/definitions/transport.Driver' "400": description: Invalid input (nickname must be 3 uppercase letters) schema: additionalProperties: true type: object "409": description: Driver with that nickname already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Create a driver tags: - drivers /api/drivers/{id}: delete: description: Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname. parameters: - description: Driver id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: driver schema: $ref: '#/definitions/transport.DriverUpdateRequest' produces: - application/json responses: "200": description: Driver payload or update result schema: $ref: '#/definitions/transport.Driver' "204": description: Delete ack schema: $ref: '#/definitions/transport.Driver' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Driver not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a driver by id tags: - drivers get: description: Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname. parameters: - description: Driver id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: driver schema: $ref: '#/definitions/transport.DriverUpdateRequest' produces: - application/json responses: "200": description: Driver payload or update result schema: $ref: '#/definitions/transport.Driver' "204": description: Delete ack schema: $ref: '#/definitions/transport.Driver' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Driver not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a driver by id tags: - drivers put: description: Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname. parameters: - description: Driver id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: driver schema: $ref: '#/definitions/transport.DriverUpdateRequest' produces: - application/json responses: "200": description: Driver payload or update result schema: $ref: '#/definitions/transport.Driver' "204": description: Delete ack schema: $ref: '#/definitions/transport.Driver' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Driver not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a driver by id tags: - drivers /api/drivers/{id}/device: put: description: Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname. parameters: - description: Driver id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: driver schema: $ref: '#/definitions/transport.DriverUpdateRequest' produces: - application/json responses: "200": description: Driver payload or update result schema: $ref: '#/definitions/transport.Driver' "204": description: Delete ack schema: $ref: '#/definitions/transport.Driver' "400": description: Bad request schema: additionalProperties: true type: object "404": description: Driver not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a driver by id tags: - drivers /api/leaderboard: get: description: Returns paginated leaderboard list. Supports overall leaderboard or track-specific leaderboard for the current year. parameters: - description: Track ID. If empty, returns overall leaderboard. in: query name: track_id type: string - description: Year. Defaults to current year. in: query name: year type: integer - description: Limit (1..200, default 50) in: query name: limit type: integer - description: Offset (default 0) in: query name: offset type: integer - description: Current driver ID. Defaults to driver-seed-001 (mock Alice). in: header name: X-Driver-Id type: string produces: - application/json responses: "200": description: Leaderboard page and current driver position schema: $ref: '#/definitions/transport.LeaderboardResponse' "400": description: Invalid input schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Leaderboard tags: - leaderboard /api/races: get: description: |- Returns one keyset page of races. Live races (status lobby|racing) come from in-memory lobby.Service; finished races come from Postgres. The two sources are merged and ordered by (started_ms DESC, id ASC). The cursor is opaque: pass back the next_cursor verbatim from a previous page. ## Filters - `status` (comma-separated, e.g. "racing,finished"). Recognised values: all, lobby, racing, finished. - `track_id` (exact match against a physical track id; custom lobbies are not supported in this build). - `limit` (1..200, default 50) parameters: - description: Comma-separated status filter enum: - all - lobby - racing - finished in: query name: status type: string - description: Filter by physical track id in: query name: track_id type: string - description: Opaque keyset cursor (next_cursor from previous page) in: query name: cursor type: string - description: Page size (1..200, default 50) in: query name: limit type: integer produces: - application/json responses: "200": description: Page of races schema: $ref: '#/definitions/transport.RaceListResponse' "400": description: Bad request / invalid cursor / invalid status schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: List races (keyset paginated) tags: - races /api/races/plans: get: description: Returns race plans in ascending start_at order. Pass back the `next_cursor` to get the next page. parameters: - description: Opaque keyset cursor in: query name: cursor type: string - description: Page size (default 50) in: query name: limit type: integer produces: - application/json responses: "200": description: Page of plans schema: $ref: '#/definitions/transport.RacePlanListResponse' "400": description: Invalid cursor schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: List race plans (keyset paginated) tags: - plans post: consumes: - application/json description: Creates a recurring or one-off scheduled race. The scheduler materialises a lobby race when `next_fire_ms` <= now. If `interval_s` > 0 the plan repeats; if `count` > 0 the plan disables itself after that many fires. parameters: - description: Plan to create in: body name: plan required: true schema: $ref: '#/definitions/transport.RacePlanCreateRequest' produces: - application/json responses: "201": description: Created plan schema: $ref: '#/definitions/transport.RacePlan' "400": description: Invalid input schema: additionalProperties: true type: object "409": description: Plan id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Create a race plan tags: - plans /api/races/plans/{id}: delete: description: Removes a plan by id. Materialised races already in the lobby are not affected. parameters: - description: Plan id in: path name: id required: true type: string produces: - application/json responses: "200": description: ok schema: additionalProperties: true type: object "404": description: Plan not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Delete a race plan tags: - plans /api/races/queue: delete: description: Removes a single (driver, race) entry from the queue. Does not detach the driver from the race if already joined. parameters: - description: Driver id in: header name: X-Driver-Id type: string - description: Driver id in: query name: driver_id type: string - description: Race id in: query name: race_id required: true type: string produces: - application/json responses: "200": description: ok schema: additionalProperties: true type: object "400": description: driver_id and race_id required schema: additionalProperties: true type: object "404": description: queue entry not found schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Leave a queue entry tags: - races get: description: Returns the queue entries for a driver, oldest first. parameters: - description: Driver id in: header name: X-Driver-Id type: string - description: Driver id (alternative to header) in: query name: driver_id type: string produces: - application/json responses: "200": description: Queue entries schema: $ref: '#/definitions/transport.RaceQueueListResponse' "400": description: driver_id required schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: List driver's queue entries tags: - races /api/races/queue/join: post: consumes: - application/json description: Puts the driver in the queue for each of the next N (default 3) lobby|countdown races, soonest first. If a race has a free slot, the driver is also attached to it (best effort). Idempotent per (driver, race). parameters: - description: Driver id (alternative to body) in: header name: X-Driver-Id type: string - description: Driver id and optional limit in: body name: body schema: $ref: '#/definitions/transport.RaceQueueJoinRequest' produces: - application/json responses: "200": description: Queue entries created schema: $ref: '#/definitions/transport.RaceQueueJoinResponse' "400": description: driver_id required schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Enqueue driver on next N upcoming races tags: - races /api/races/upcoming: get: description: Returns up to `limit` races currently in status lobby|countdown, ordered soonest first. Useful for the "join queue" UI and dashboard. parameters: - description: Number of races to return (1..16, default 3) in: query name: limit type: integer produces: - application/json responses: "200": description: Upcoming races schema: $ref: '#/definitions/transport.RaceUpcomingResponse' "500": description: Internal server error schema: additionalProperties: true type: object summary: Next N upcoming races tags: - races /api/tracks: get: description: 'Returns all tracks visible to the caller. Optional query filters: `scope=system|public|private`, `tag=`.' parameters: - description: Filter by visibility enum: - system - public - private in: query name: scope type: string - description: Filter by tag (case-insensitive) in: query name: tag type: string produces: - application/json responses: "200": description: List of tracks with count schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: List tracks tags: - tracks post: consumes: - application/json description: Creates a new track. The server fills `id` if the client sends an empty one. `author_id` should be the caller's id in production (auth is disabled in PoC). parameters: - description: Track to create in: body name: track required: true schema: $ref: '#/definitions/transport.TrackCreateRequest' produces: - application/json responses: "201": description: Created schema: $ref: '#/definitions/transport.TrackAck' "400": description: Invalid input schema: additionalProperties: true type: object "409": description: Track id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Create a track tags: - tracks /api/tracks/{id}: delete: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/tracks/{id}`). System tracks are immutable and cannot be updated or deleted. parameters: - description: Track id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: track schema: $ref: '#/definitions/transport.TrackUpdateRequest' produces: - application/json responses: "200": description: Track payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.TrackWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.TrackAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System track is immutable schema: additionalProperties: true type: object "404": description: Track not found schema: additionalProperties: true type: object "409": description: Track id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a track by id tags: - tracks get: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/tracks/{id}`). System tracks are immutable and cannot be updated or deleted. parameters: - description: Track id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: track schema: $ref: '#/definitions/transport.TrackUpdateRequest' produces: - application/json responses: "200": description: Track payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.TrackWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.TrackAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System track is immutable schema: additionalProperties: true type: object "404": description: Track not found schema: additionalProperties: true type: object "409": description: Track id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a track by id tags: - tracks put: description: Dispatches on HTTP method. The id is taken from the URL path (`/api/tracks/{id}`). System tracks are immutable and cannot be updated or deleted. parameters: - description: Track id in: path name: id required: true type: string - description: Patch payload (only for PUT) in: body name: track schema: $ref: '#/definitions/transport.TrackUpdateRequest' produces: - application/json responses: "200": description: Track payload (GET) or update ack (PUT) schema: $ref: '#/definitions/transport.TrackWire' "204": description: Delete ack schema: $ref: '#/definitions/transport.TrackAck' "400": description: Bad request / invalid input schema: additionalProperties: true type: object "403": description: System track is immutable schema: additionalProperties: true type: object "404": description: Track not found schema: additionalProperties: true type: object "409": description: Track id already exists schema: additionalProperties: true type: object "500": description: Internal server error schema: additionalProperties: true type: object summary: Get / update / delete a track by id tags: - tracks /api/version: get: description: Returns the current server version. produces: - application/json responses: "200": description: Success schema: $ref: '#/definitions/transport.VersionResponse' summary: Get server version tags: - system /api/video/control: post: description: Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888). parameters: - description: Device ID (0..127) in: query name: id type: integer - description: Command text in: query name: cmd required: true type: string produces: - application/json responses: "200": description: Command sent schema: additionalProperties: true type: object "400": description: Missing cmd parameter schema: type: string "500": description: Internal error schema: type: string summary: Send command to ESP32 tags: - video /api/video/stream: get: description: Streams video frames received via UDP as multipart/x-mixed-replace. Suitable for raw img tags in browser. parameters: - description: Device ID (0..127) in: query name: id type: integer produces: - multipart/x-mixed-replace responses: "200": description: MJPEG stream schema: type: string summary: Get MJPEG video stream from ESP32 tags: - video /api/webrtc/connect: post: consumes: - application/json description: Accepts SDP Offer, negotiates peer connection, and returns SDP Answer. Routes video and control data via DataChannels. parameters: - description: SDP Offer details in: body name: body required: true schema: $ref: '#/definitions/transport.WebRTCConnectRequest' produces: - application/json responses: "200": description: SDP Answer negotiation success schema: $ref: '#/definitions/transport.WebRTCConnectResponse' summary: Establish WebRTC connection for driver-car 1-to-1 proxying tags: - webrtc /health: get: description: Returns 200 OK with current engine phase, WS connection count and total snapshot drops. Intended for load-balancer health checks. produces: - application/json responses: "200": description: Server is up schema: additionalProperties: true type: object summary: Liveness probe tags: - system /stats: get: description: 'Returns the current race phase plus realtime counters: active WS connections and total snapshots dropped due to slow consumers.' produces: - application/json responses: "200": description: Runtime counters schema: additionalProperties: true type: object summary: Realtime runtime stats tags: - system /ws: get: description: |- Opens a bidirectional WebSocket for the realtime race protocol. The server replies with `server_hello`, then broadcasts `race_snapshot` frames at `X0GP_SNAPSHOT_HZ`. ## Frame format Every frame is a JSON envelope of the form: ```json { "type": "", "seq": 42, "ts_ms": 1700000000000, "payload": { ... } } ``` `type` discriminates the payload; `payload` is documented per message type in the schemas listed in `components/schemas` (see `Envelope`, `ClientHello`, `ServerHello`, `InputState`, `JoinRace`, `LeaveRace`, `RaceSnapshot`, `InputAck`, `RaceEvent`, `Ping`, `Pong`, `ErrorMsg`, plus the catalog/lobby/stats envelopes). ## Client -> Server - `client_hello` — first frame after connect. Sent once. - `join_race` / `leave_race` — join or leave the race session. - `input_state` — high-rate control (steering/throttle/brake/gear). Send at 60-120 Hz. - `ping` — round-trip time probe. - `track_*` / `car_*` — catalog CRUD (mirror of the REST /api/* endpoints). - `lobby_*` — lobby management. - `stats_request` — ask for a stats snapshot. ## Server -> Client - `server_hello` — handshake reply; includes session id and engine config. - `race_snapshot` — periodic authoritative state broadcast. - `input_ack` — per-input acknowledgement (optional; drop-tolerant). - `race_event` — start/lap/sector/dnf/finish. - `track_snapshot` / `car_snapshot` / `catalog_summary` — catalog feeds on change. - `lobby_snapshot` — full lobby state on change. - `stats_snapshot` / `race_standings` / `lap_recorded` / `race_result` — stats feeds. - `error` — protocol or validation error. ## Notes - Origin checks are disabled in PoC mode (`CheckOrigin: always-allow`). - Ping interval: 20 s. Pong wait: 60 s. Max frame size: 8 KiB. - Input acks are best-effort and silently dropped under load. responses: "101": description: Switching protocols. Subsequent frames follow the envelope schema. schema: $ref: '#/definitions/transport.Envelope' summary: WebSocket endpoint (binary JSON envelope protocol) tags: - websocket schemes: - http - ws swagger: "2.0"