{ "schemes": [ "http", "ws" ], "swagger": "2.0", "info": { "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.\nAuth is intentionally disabled in PoC mode (X0GP_DEV_MODE=true). All catalog mutations are allowed without a JWT.", "title": "x0gp PoC server API", "contact": { "name": "x0gp" }, "license": { "name": "MIT" }, "version": "0.1.0-poc" }, "host": "localhost:8080", "basePath": "/", "paths": { "/api/cars": { "get": { "description": "Returns all cars visible to the caller. Optional query filters: `scope=system|public|private`, `owner_id=\u003cstring\u003e`.", "produces": [ "application/json" ], "tags": [ "cars" ], "summary": "List cars", "parameters": [ { "enum": [ "system", "public", "private" ], "type": "string", "description": "Filter by visibility", "name": "scope", "in": "query" }, { "type": "string", "description": "Filter by owner id", "name": "owner_id", "in": "query" } ], "responses": { "200": { "description": "List of cars with count", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "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).", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "cars" ], "summary": "Create a car", "parameters": [ { "description": "Car to create", "name": "car", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.CarCreateRequest" } } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/transport.CarAck" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Car id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/cars/{id}": { "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.", "produces": [ "application/json" ], "tags": [ "cars" ], "summary": "Get / update / delete a car by id", "parameters": [ { "type": "string", "description": "Car id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "car", "in": "body", "schema": { "$ref": "#/definitions/transport.CarUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System car is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Car not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Car id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "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.", "produces": [ "application/json" ], "tags": [ "cars" ], "summary": "Get / update / delete a car by id", "parameters": [ { "type": "string", "description": "Car id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "car", "in": "body", "schema": { "$ref": "#/definitions/transport.CarUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System car is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Car not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Car id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "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.", "produces": [ "application/json" ], "tags": [ "cars" ], "summary": "Get / update / delete a car by id", "parameters": [ { "type": "string", "description": "Car id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "car", "in": "body", "schema": { "$ref": "#/definitions/transport.CarUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System car is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Car not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Car id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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" ], "tags": [ "catalog" ], "summary": "Combined catalog snapshot", "responses": { "200": { "description": "Snapshot of tracks, cars and summary counters", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/clans": { "get": { "description": "Returns clans ordered by tag. Limit 1..200, default 50.", "produces": [ "application/json" ], "tags": [ "clans" ], "summary": "List clans", "parameters": [ { "type": "integer", "description": "Page size", "name": "limit", "in": "query" }, { "type": "integer", "description": "Page offset", "name": "offset", "in": "query" } ], "responses": { "200": { "description": "Page of clans", "schema": { "$ref": "#/definitions/transport.ClanListResponse" } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "description": "Creates a new clan. `tag` must be exactly 3 uppercase ASCII letters (e.g. \"ACE\") and unique.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "clans" ], "summary": "Create a clan", "parameters": [ { "description": "Clan to create", "name": "clan", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.ClanCreateRequest" } } ], "responses": { "201": { "description": "Created clan", "schema": { "$ref": "#/definitions/transport.Clan" } }, "400": { "description": "Invalid input (tag must be 3 uppercase letters)", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Clan with that tag already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/clans/{id}": { "get": { "description": "Dispatches on HTTP method.", "produces": [ "application/json" ], "tags": [ "clans" ], "summary": "Get / update / delete a clan by id", "parameters": [ { "type": "string", "description": "Clan id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "clan", "in": "body", "schema": { "$ref": "#/definitions/transport.ClanUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Clan not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "put": { "description": "Dispatches on HTTP method.", "produces": [ "application/json" ], "tags": [ "clans" ], "summary": "Get / update / delete a clan by id", "parameters": [ { "type": "string", "description": "Clan id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "clan", "in": "body", "schema": { "$ref": "#/definitions/transport.ClanUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Clan not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "delete": { "description": "Dispatches on HTTP method.", "produces": [ "application/json" ], "tags": [ "clans" ], "summary": "Get / update / delete a clan by id", "parameters": [ { "type": "string", "description": "Clan id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "clan", "in": "body", "schema": { "$ref": "#/definitions/transport.ClanUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Clan not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/drivers": { "get": { "description": "Returns drivers ordered by nickname. Optional `clan_id` filter.", "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "List drivers", "parameters": [ { "type": "integer", "description": "Page size", "name": "limit", "in": "query" }, { "type": "integer", "description": "Page offset", "name": "offset", "in": "query" }, { "type": "string", "description": "Filter by clan id", "name": "clan_id", "in": "query" } ], "responses": { "200": { "description": "Page of drivers", "schema": { "$ref": "#/definitions/transport.DriverListResponse" } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "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.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "Create a driver", "parameters": [ { "description": "Driver to create", "name": "driver", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.DriverCreateRequest" } } ], "responses": { "201": { "description": "Created driver", "schema": { "$ref": "#/definitions/transport.Driver" } }, "400": { "description": "Invalid input (nickname must be 3 uppercase letters)", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Driver with that nickname already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/drivers/{id}": { "get": { "description": "Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname.", "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "Get / update / delete a driver by id", "parameters": [ { "type": "string", "description": "Driver id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "driver", "in": "body", "schema": { "$ref": "#/definitions/transport.DriverUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Driver not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "put": { "description": "Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname.", "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "Get / update / delete a driver by id", "parameters": [ { "type": "string", "description": "Driver id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "driver", "in": "body", "schema": { "$ref": "#/definitions/transport.DriverUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Driver not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "delete": { "description": "Dispatches on HTTP method. Path `/api/drivers/by-nick/{nick}` is a convenience that returns the driver by 3-letter nickname.", "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "Get / update / delete a driver by id", "parameters": [ { "type": "string", "description": "Driver id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "driver", "in": "body", "schema": { "$ref": "#/definitions/transport.DriverUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Driver not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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.", "produces": [ "application/json" ], "tags": [ "drivers" ], "summary": "Get / update / delete a driver by id", "parameters": [ { "type": "string", "description": "Driver id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "driver", "in": "body", "schema": { "$ref": "#/definitions/transport.DriverUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "404": { "description": "Driver not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/leaderboard": { "get": { "description": "Returns paginated leaderboard list. Supports overall leaderboard or track-specific leaderboard for the current year.", "produces": [ "application/json" ], "tags": [ "leaderboard" ], "summary": "Leaderboard", "parameters": [ { "type": "string", "description": "Track ID. If empty, returns overall leaderboard.", "name": "track_id", "in": "query" }, { "type": "integer", "description": "Year. Defaults to current year.", "name": "year", "in": "query" }, { "type": "integer", "description": "Limit (1..200, default 50)", "name": "limit", "in": "query" }, { "type": "integer", "description": "Offset (default 0)", "name": "offset", "in": "query" }, { "type": "string", "description": "Current driver ID. Defaults to driver-seed-001 (mock Alice).", "name": "X-Driver-Id", "in": "header" } ], "responses": { "200": { "description": "Leaderboard page and current driver position", "schema": { "$ref": "#/definitions/transport.LeaderboardResponse" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/leaderboard/tracks": { "get": { "description": "Returns the list of scheduled tracks with their status (finished/active/planned), top 3 drivers, and current driver position, sorted by calendar start date.", "produces": [ "application/json" ], "tags": [ "leaderboard" ], "summary": "Track Calendar Leaderboards", "parameters": [ { "type": "integer", "description": "Year. Defaults to current year.", "name": "year", "in": "query" }, { "type": "string", "description": "Current driver ID. Defaults to driver-seed-001 (mock Alice).", "name": "X-Driver-Id", "in": "header" } ], "responses": { "200": { "description": "Calendar leaderboards list", "schema": { "$ref": "#/definitions/transport.TrackCalendarLeaderboardResponse" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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.\n## Filters\n- `status` (comma-separated, e.g. \"racing,finished\"). Recognised values: all, lobby, racing, finished.\n- `track_id` (exact match against a physical track id; custom lobbies are not supported in this build).\n- `limit` (1..200, default 50)", "produces": [ "application/json" ], "tags": [ "races" ], "summary": "List races (keyset paginated)", "parameters": [ { "enum": [ "all", "lobby", "racing", "finished" ], "type": "string", "description": "Comma-separated status filter", "name": "status", "in": "query" }, { "type": "string", "description": "Filter by physical track id", "name": "track_id", "in": "query" }, { "type": "string", "description": "Opaque keyset cursor (next_cursor from previous page)", "name": "cursor", "in": "query" }, { "type": "integer", "description": "Page size (1..200, default 50)", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "Page of races", "schema": { "$ref": "#/definitions/transport.RaceListResponse" } }, "400": { "description": "Bad request / invalid cursor / invalid status", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/races/plans": { "get": { "description": "Returns race plans in ascending start_at order. Pass back the `next_cursor` to get the next page.", "produces": [ "application/json" ], "tags": [ "plans" ], "summary": "List race plans (keyset paginated)", "parameters": [ { "type": "string", "description": "Opaque keyset cursor", "name": "cursor", "in": "query" }, { "type": "integer", "description": "Page size (default 50)", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "Page of plans", "schema": { "$ref": "#/definitions/transport.RacePlanListResponse" } }, "400": { "description": "Invalid cursor", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "description": "Creates a recurring or one-off scheduled race. The scheduler materialises a lobby race when `next_fire_ms` \u003c= now. If `interval_s` \u003e 0 the plan repeats; if `count` \u003e 0 the plan disables itself after that many fires.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "plans" ], "summary": "Create a race plan", "parameters": [ { "description": "Plan to create", "name": "plan", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.RacePlanCreateRequest" } } ], "responses": { "201": { "description": "Created plan", "schema": { "$ref": "#/definitions/transport.RacePlan" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Plan id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/races/plans/{id}": { "delete": { "description": "Removes a plan by id. Materialised races already in the lobby are not affected.", "produces": [ "application/json" ], "tags": [ "plans" ], "summary": "Delete a race plan", "parameters": [ { "type": "string", "description": "Plan id", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "ok", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Plan not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/races/queue": { "get": { "description": "Returns the queue entries for a driver, oldest first.", "produces": [ "application/json" ], "tags": [ "races" ], "summary": "List driver's queue entries", "parameters": [ { "type": "string", "description": "Driver id", "name": "X-Driver-Id", "in": "header" }, { "type": "string", "description": "Driver id (alternative to header)", "name": "driver_id", "in": "query" } ], "responses": { "200": { "description": "Queue entries", "schema": { "$ref": "#/definitions/transport.RaceQueueListResponse" } }, "400": { "description": "driver_id required", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "delete": { "description": "Removes a single (driver, race) entry from the queue. Does not detach the driver from the race if already joined.", "produces": [ "application/json" ], "tags": [ "races" ], "summary": "Leave a queue entry", "parameters": [ { "type": "string", "description": "Driver id", "name": "X-Driver-Id", "in": "header" }, { "type": "string", "description": "Driver id", "name": "driver_id", "in": "query" }, { "type": "string", "description": "Race id", "name": "race_id", "in": "query", "required": true } ], "responses": { "200": { "description": "ok", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "driver_id and race_id required", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "queue entry not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/races/queue/join": { "post": { "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).", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "races" ], "summary": "Enqueue driver on next N upcoming races", "parameters": [ { "type": "string", "description": "Driver id (alternative to body)", "name": "X-Driver-Id", "in": "header" }, { "description": "Driver id and optional limit", "name": "body", "in": "body", "schema": { "$ref": "#/definitions/transport.RaceQueueJoinRequest" } } ], "responses": { "200": { "description": "Queue entries created", "schema": { "$ref": "#/definitions/transport.RaceQueueJoinResponse" } }, "400": { "description": "driver_id required", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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.", "produces": [ "application/json" ], "tags": [ "races" ], "summary": "Next N upcoming races", "parameters": [ { "type": "integer", "description": "Number of races to return (1..16, default 3)", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "Upcoming races", "schema": { "$ref": "#/definitions/transport.RaceUpcomingResponse" } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/tracks": { "get": { "description": "Returns all tracks visible to the caller. Optional query filters: `scope=system|public|private`, `tag=\u003cstring\u003e`.", "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "List tracks", "parameters": [ { "enum": [ "system", "public", "private" ], "type": "string", "description": "Filter by visibility", "name": "scope", "in": "query" }, { "type": "string", "description": "Filter by tag (case-insensitive)", "name": "tag", "in": "query" } ], "responses": { "200": { "description": "List of tracks with count", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "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).", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Create a track", "parameters": [ { "description": "Track to create", "name": "track", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.TrackCreateRequest" } } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/transport.TrackAck" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Track id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/tracks/calendar": { "get": { "description": "Returns the full calendar of track active periods.", "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Get Track Calendar", "responses": { "200": { "description": "List of scheduled active periods", "schema": { "$ref": "#/definitions/transport.TrackCalendarResponse" } }, "500": { "description": "Internal error", "schema": { "type": "object", "additionalProperties": true } } } }, "post": { "description": "Schedules an active period for a physical track.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Schedule Track Active Period", "parameters": [ { "description": "Calendar entry details", "name": "payload", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.TrackCalendarCreateRequest" } } ], "responses": { "201": { "description": "Created calendar entry", "schema": { "$ref": "#/definitions/transport.TrackCalendarEntry" } }, "400": { "description": "Invalid input", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Track not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/tracks/calendar/{id}": { "delete": { "description": "Deletes a scheduled active period by ID.", "tags": [ "tracks" ], "summary": "Delete Scheduled Period", "parameters": [ { "type": "integer", "description": "Calendar Entry ID", "name": "id", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "404": { "description": "Not found", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/tracks/{id}": { "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.", "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Get / update / delete a track by id", "parameters": [ { "type": "string", "description": "Track id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "track", "in": "body", "schema": { "$ref": "#/definitions/transport.TrackUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System track is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Track not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Track id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "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.", "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Get / update / delete a track by id", "parameters": [ { "type": "string", "description": "Track id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "track", "in": "body", "schema": { "$ref": "#/definitions/transport.TrackUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System track is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Track not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Track id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } }, "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.", "produces": [ "application/json" ], "tags": [ "tracks" ], "summary": "Get / update / delete a track by id", "parameters": [ { "type": "string", "description": "Track id", "name": "id", "in": "path", "required": true }, { "description": "Patch payload (only for PUT)", "name": "track", "in": "body", "schema": { "$ref": "#/definitions/transport.TrackUpdateRequest" } } ], "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": { "type": "object", "additionalProperties": true } }, "403": { "description": "System track is immutable", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Track not found", "schema": { "type": "object", "additionalProperties": true } }, "409": { "description": "Track id already exists", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal server error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/api/version": { "get": { "description": "Returns the current server version.", "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Get server version", "responses": { "200": { "description": "Success", "schema": { "$ref": "#/definitions/transport.VersionResponse" } } } } }, "/api/video/control": { "post": { "description": "Sends a text command (e.g. start, stop, left, right) to the ESP32 command port (UDP 8888).", "produces": [ "application/json" ], "tags": [ "video" ], "summary": "Send command to ESP32", "parameters": [ { "type": "integer", "description": "Device ID (0..127)", "name": "id", "in": "query" }, { "type": "string", "description": "Command text", "name": "cmd", "in": "query", "required": true } ], "responses": { "200": { "description": "Command sent", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "Missing cmd parameter", "schema": { "type": "string" } }, "500": { "description": "Internal error", "schema": { "type": "string" } } } } }, "/api/video/stream": { "get": { "description": "Streams video frames received via UDP as multipart/x-mixed-replace. Suitable for raw img tags in browser.", "produces": [ "multipart/x-mixed-replace" ], "tags": [ "video" ], "summary": "Get MJPEG video stream from ESP32", "parameters": [ { "type": "integer", "description": "Device ID (0..127)", "name": "id", "in": "query" } ], "responses": { "200": { "description": "MJPEG stream", "schema": { "type": "string" } } } } }, "/api/webrtc/connect": { "post": { "description": "Accepts SDP Offer, negotiates peer connection, and returns SDP Answer. Routes video and control data via DataChannels.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "webrtc" ], "summary": "Establish WebRTC connection for driver-car 1-to-1 proxying", "parameters": [ { "description": "SDP Offer details", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/transport.WebRTCConnectRequest" } } ], "responses": { "200": { "description": "SDP Answer negotiation success", "schema": { "$ref": "#/definitions/transport.WebRTCConnectResponse" } } } } }, "/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" ], "tags": [ "system" ], "summary": "Liveness probe", "responses": { "200": { "description": "Server is up", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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" ], "tags": [ "system" ], "summary": "Realtime runtime stats", "responses": { "200": { "description": "Runtime counters", "schema": { "type": "object", "additionalProperties": true } } } } }, "/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`.\n## Frame format\nEvery frame is a JSON envelope of the form:\n```json\n{ \"type\": \"\u003cmessage_type\u003e\", \"seq\": 42, \"ts_ms\": 1700000000000, \"payload\": { ... } }\n```\n`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).\n## Client -\u003e Server\n- `client_hello` — first frame after connect. Sent once.\n- `join_race` / `leave_race` — join or leave the race session.\n- `input_state` — high-rate control (steering/throttle/brake/gear). Send at 60-120 Hz.\n- `ping` — round-trip time probe.\n- `track_*` / `car_*` — catalog CRUD (mirror of the REST /api/* endpoints).\n- `lobby_*` — lobby management.\n- `stats_request` — ask for a stats snapshot.\n## Server -\u003e Client\n- `server_hello` — handshake reply; includes session id and engine config.\n- `race_snapshot` — periodic authoritative state broadcast.\n- `input_ack` — per-input acknowledgement (optional; drop-tolerant).\n- `race_event` — start/lap/sector/dnf/finish.\n- `track_snapshot` / `car_snapshot` / `catalog_summary` — catalog feeds on change.\n- `lobby_snapshot` — full lobby state on change.\n- `stats_snapshot` / `race_standings` / `lap_recorded` / `race_result` — stats feeds.\n- `error` — protocol or validation error.\n## Notes\n- Origin checks are disabled in PoC mode (`CheckOrigin: always-allow`).\n- Ping interval: 20 s. Pong wait: 60 s. Max frame size: 8 KiB.\n- Input acks are best-effort and silently dropped under load.", "tags": [ "websocket" ], "summary": "WebSocket endpoint (binary JSON envelope protocol)", "responses": { "101": { "description": "Switching protocols. Subsequent frames follow the envelope schema.", "schema": { "$ref": "#/definitions/transport.Envelope" } } } } } }, "definitions": { "transport.BatteryWire": { "type": "object", "properties": { "capacity_mah": { "type": "integer" }, "cells": { "type": "integer" }, "chemistry": { "type": "string" }, "voltage_v": { "type": "number" } } }, "transport.CarAck": { "type": "object", "properties": { "car": { "$ref": "#/definitions/transport.CarWire" }, "error": { "type": "string" }, "id": { "type": "string" }, "ok": { "type": "boolean" } } }, "transport.CarCreateRequest": { "type": "object", "properties": { "car": { "$ref": "#/definitions/transport.CarWire" } } }, "transport.CarPatch": { "type": "object", "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" } } }, "transport.CarUpdateRequest": { "type": "object", "properties": { "id": { "type": "string" }, "patch": { "$ref": "#/definitions/transport.CarPatch" } } }, "transport.CarWire": { "type": "object", "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" } } }, "transport.ChassisWire": { "type": "object", "properties": { "material": { "type": "string" }, "model": { "type": "string" }, "printed": { "type": "boolean" }, "track_mm": { "type": "number" }, "wheelbase_mm": { "type": "number" } } }, "transport.Clan": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/c/ace.png" }, "created_ms": { "type": "integer" }, "id": { "type": "string", "example": "clan-1782000-1" }, "name": { "type": "string", "example": "Ace Racing" }, "tag": { "type": "string", "example": "ACE" }, "updated_ms": { "type": "integer" } } }, "transport.ClanCreateRequest": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/c/ace.png" }, "id": { "type": "string", "example": "clan-1782000-1" }, "name": { "type": "string", "example": "Ace Racing" }, "tag": { "type": "string", "example": "ACE" } } }, "transport.ClanListResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.Clan" } } } }, "transport.ClanUpdateRequest": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/c/ace.png" }, "name": { "type": "string", "example": "Ace Racing" } } }, "transport.Driver": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/u/ace.png" }, "clan_id": { "type": "string", "example": "clan-1782000-1" }, "created_ms": { "type": "integer" }, "id": { "type": "string", "example": "driver-1782000-123" }, "name": { "type": "string", "example": "Alice" }, "nickname": { "type": "string", "example": "ACE" }, "updated_ms": { "type": "integer" } } }, "transport.DriverCreateRequest": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/u/ace.png" }, "clan_id": { "type": "string", "example": "clan-1782000-1" }, "id": { "type": "string", "example": "driver-1782000-123" }, "name": { "type": "string", "example": "Alice" }, "nickname": { "type": "string", "example": "ACE" } } }, "transport.DriverListResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.Driver" } } } }, "transport.DriverUpdateRequest": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/u/ace.png" }, "clan_id": { "type": "string", "example": "clan-1782000-1" }, "name": { "type": "string", "example": "Alice" } } }, "transport.Envelope": { "type": "object", "properties": { "payload": {}, "seq": { "type": "integer" }, "ts_ms": { "type": "integer" }, "type": { "$ref": "#/definitions/transport.MessageType" } } }, "transport.LeaderboardEntry": { "type": "object", "properties": { "avatar_url": { "type": "string", "example": "https://cdn.example.com/u/ace.png" }, "best_pos": { "description": "Only populated for track-specific leaderboard", "type": "integer", "example": 1 }, "best_time_ms": { "type": "integer", "example": 15000 }, "clan_id": { "type": "string", "example": "clan-seed-001" }, "clan_tag": { "type": "string", "example": "ACE" }, "driver_id": { "type": "string", "example": "driver-seed-001" }, "name": { "type": "string", "example": "Alice" }, "nickname": { "type": "string", "example": "ACE" }, "points": { "type": "integer", "example": 25 }, "rank": { "type": "integer", "example": 1 } } }, "transport.LeaderboardResponse": { "type": "object", "properties": { "current_driver": { "$ref": "#/definitions/transport.LeaderboardEntry" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.LeaderboardEntry" } }, "limit": { "type": "integer", "example": 50 }, "offset": { "type": "integer", "example": 0 }, "total": { "type": "integer", "example": 8 } } }, "transport.LobbyRace": { "type": "object", "properties": { "created_ms": { "type": "integer" }, "driver_ids": { "type": "array", "items": { "type": "string" } }, "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.", "type": "array", "items": { "$ref": "#/definitions/transport.RacePodiumEntry" } }, "started_ms": { "type": "integer" }, "status": { "type": "string" }, "time_limit_s": { "type": "integer" }, "track_id": { "type": "string" } } }, "transport.MessageType": { "type": "string", "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" ], "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": { "type": "object", "properties": { "class": { "type": "string" }, "kind": { "type": "string" }, "kv": { "type": "integer" }, "power_w": { "type": "number" } } }, "transport.RaceListResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.LobbyRace" } }, "next_cursor": { "type": "string", "example": "MTcwMDAwMDAwMDAwMDpyYWNlLWFiYzpmaW5pc2hlZA" } } }, "transport.RacePlan": { "type": "object", "properties": { "count": { "type": "integer", "example": 0 }, "created_ms": { "type": "integer" }, "enabled": { "type": "boolean", "example": true }, "fires_done": { "type": "integer" }, "id": { "type": "string", "example": "plan-1730000-1" }, "interval_s": { "type": "integer", "example": 3600 }, "laps": { "type": "integer", "example": 10 }, "max_cars": { "type": "integer", "example": 4 }, "name": { "type": "string", "example": "Daily Monza" }, "next_fire_ms": { "type": "integer" }, "start_at_ms": { "type": "integer", "example": 1730000000000 }, "time_limit_s": { "type": "integer" }, "track_id": { "type": "string", "example": "default" }, "updated_ms": { "type": "integer" } } }, "transport.RacePlanCreateRequest": { "type": "object", "properties": { "count": { "type": "integer", "example": 0 }, "enabled": { "type": "boolean", "example": true }, "id": { "type": "string", "example": "plan-1730000-1" }, "interval_s": { "type": "integer", "example": 3600 }, "laps": { "type": "integer", "example": 10 }, "max_cars": { "type": "integer", "example": 4 }, "name": { "type": "string", "example": "Daily Monza" }, "start_at_ms": { "type": "integer", "example": 1730000000000 }, "time_limit_s": { "type": "integer" }, "track_id": { "type": "string", "example": "default" } } }, "transport.RacePlanListResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.RacePlan" } }, "next_cursor": { "type": "string" } } }, "transport.RacePodiumEntry": { "type": "object", "properties": { "avatar_url": { "type": "string" }, "clan_id": { "type": "string", "example": "clan-bob" }, "driver_id": { "type": "string", "example": "driver-alice" }, "name": { "type": "string", "example": "driver-alice" }, "nickname": { "type": "string", "example": "driver-alice" }, "position": { "type": "integer", "example": 1 }, "total_time_ms": { "type": "integer", "example": 123456 } } }, "transport.RaceQueueEntry": { "type": "object", "properties": { "driver_id": { "type": "string", "example": "driver-42" }, "enqueued_ms": { "type": "integer", "example": 1730000000000 }, "plan_id": { "type": "string", "example": "plan-1730000-1" }, "race_id": { "type": "string", "example": "race-1730000-7" } } }, "transport.RaceQueueJoinRequest": { "type": "object", "properties": { "driver_id": { "type": "string", "example": "driver-42" }, "limit": { "type": "integer", "example": 3 } } }, "transport.RaceQueueJoinResponse": { "type": "object", "properties": { "joined": { "type": "array", "items": { "$ref": "#/definitions/transport.RaceQueueEntry" } }, "skipped": { "type": "array", "items": { "type": "string" }, "example": [ "race-1", "race-2" ] } } }, "transport.RaceQueueListResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.RaceQueueEntry" } } } }, "transport.RaceUpcomingItem": { "type": "object", "properties": { "meta": { "$ref": "#/definitions/transport.LobbyRace" }, "plan_id": { "type": "string" }, "queue_len": { "type": "integer" }, "start_at_ms": { "type": "integer" } } }, "transport.RaceUpcomingResponse": { "type": "object", "properties": { "count": { "type": "integer" }, "items": { "type": "array", "items": { "$ref": "#/definitions/transport.RaceUpcomingItem" } } } }, "transport.TrackAck": { "type": "object", "properties": { "error": { "type": "string" }, "id": { "type": "string" }, "ok": { "type": "boolean" }, "track": { "$ref": "#/definitions/transport.TrackWire" } } }, "transport.TrackBounds": { "type": "object", "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" } } }, "transport.TrackCalendarCreateRequest": { "type": "object", "properties": { "end_at": { "type": "string", "example": "2026-07-30T00:00:00Z" }, "start_at": { "type": "string", "example": "2026-07-15T00:00:00Z" }, "track_id": { "type": "string", "example": "monaco" } } }, "transport.TrackCalendarEntry": { "type": "object", "properties": { "end_at": { "type": "string", "example": "2026-07-30T00:00:00Z" }, "id": { "type": "integer", "example": 1 }, "start_at": { "type": "string", "example": "2026-07-15T00:00:00Z" }, "track_id": { "type": "string", "example": "monaco" } } }, "transport.TrackCalendarLeaderboard": { "type": "object", "properties": { "current_driver": { "$ref": "#/definitions/transport.LeaderboardEntry" }, "end_at": { "type": "string", "example": "2026-07-30T00:00:00Z" }, "podium": { "type": "array", "items": { "$ref": "#/definitions/transport.LeaderboardEntry" } }, "start_at": { "type": "string", "example": "2026-07-15T00:00:00Z" }, "status": { "type": "string", "example": "active" }, "track_id": { "type": "string", "example": "monaco" }, "track_name": { "type": "string", "example": "Monaco" } } }, "transport.TrackCalendarLeaderboardResponse": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/definitions/transport.TrackCalendarLeaderboard" } } } }, "transport.TrackCalendarResponse": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/definitions/transport.TrackCalendarEntry" } } } }, "transport.TrackCreateRequest": { "type": "object", "properties": { "track": { "$ref": "#/definitions/transport.TrackWire" } } }, "transport.TrackPatch": { "type": "object", "properties": { "centerline": { "type": "array", "items": { "$ref": "#/definitions/transport.TrackWaypoint" } }, "description": { "type": "string" }, "lane_width_m": { "type": "number" }, "length_m": { "type": "number" }, "name": { "type": "string" }, "scale": { "type": "integer" }, "surface": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "visibility": { "type": "string" }, "width_m": { "type": "number" } } }, "transport.TrackUpdateRequest": { "type": "object", "properties": { "id": { "type": "string" }, "patch": { "$ref": "#/definitions/transport.TrackPatch" } } }, "transport.TrackWaypoint": { "type": "object", "properties": { "curvature": { "type": "number" }, "heading_rad": { "type": "number" }, "speed_ms": { "type": "number" }, "x": { "type": "number" }, "y": { "type": "number" } } }, "transport.TrackWire": { "type": "object", "properties": { "author_id": { "type": "string" }, "best_lap_holder": { "type": "string" }, "best_lap_ms": { "type": "integer" }, "bounds": { "$ref": "#/definitions/transport.TrackBounds" }, "centerline": { "type": "array", "items": { "$ref": "#/definitions/transport.TrackWaypoint" } }, "created_ms": { "type": "integer" }, "description": { "type": "string" }, "id": { "type": "string" }, "is_active": { "type": "boolean", "example": true }, "lane_width_m": { "type": "number" }, "length_m": { "type": "number" }, "name": { "type": "string" }, "scale": { "type": "integer" }, "surface": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "updated_ms": { "type": "integer" }, "visibility": { "type": "string" }, "width_m": { "type": "number" } } }, "transport.VersionResponse": { "type": "object", "properties": { "version": { "type": "string", "example": "0.1.0-poc" } } }, "transport.WebRTCConnectRequest": { "type": "object", "properties": { "driver_id": { "type": "string", "example": "driver-alice" }, "sdp": { "type": "string" }, "type": { "type": "string", "example": "offer" } } }, "transport.WebRTCConnectResponse": { "type": "object", "properties": { "sdp": { "type": "string" }, "type": { "type": "string", "example": "answer" } } } } }