Files
x0gp/server/docs/swagger.json
T

2894 lines
102 KiB
JSON

{
"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/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/{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"
}
}
}
}
},
"/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.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.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"
},
"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"
}
}
}
}
}