{"openapi":"3.1.0","info":{"title":"PlayFlow API","version":"3.0.0","description":"PlayFlow is a game server hosting and orchestration platform. Deploy, manage, and scale multiplayer game servers globally with built-in matchmaking, lobby management, and billing.\n\n## Authentication\n\nAll API endpoints (except Health) require the `api-key` header:\n\n```\napi-key: pf_your_project_api_key\n```\n\nFind your API key in the PlayFlow dashboard under **Project Settings**. Each project has two keys, both sent in the `api-key` header:\n\n- **Server key** (`pf_...`) — full access. Keep it secret (server-side only).\n- **Client key** (`pfclient_...`) — safe to embed in shipped game clients. It has access to all player-facing lobby, matchmaking, and read endpoints, but is **rejected with `403`** from admin/privileged routes: admin lobby get/force-delete (`GET`/`DELETE /v3/lobbies/{config}/{id}`), build deletion (`DELETE /v3/builds/{id}`), and project settings (`POST /v3/projects/settings`). Server lifecycle mutations (start/stop/restart/update) are allowed for client keys by default but can be locked to server-key-only per project via the `client_key_server_control` setting.\n\n## Quick Start\n\n1. **Upload a build**: `POST /v3/builds/upload-url` → get presigned URL → `PUT` your ZIP → processing starts automatically\n2. **Start a server**: `POST /v3/servers/start` → get connection info in `network_ports`\n3. **Connect your game client**: Use `host:external_port` from the response\n\n## Key Concepts\n\n- **Builds**: Your game server binary packaged as a ZIP or Docker image. Versioned per name per project.\n- **Servers**: Running game server instances. Each gets dedicated compute and network ports with automatic health monitoring.\n- **Pool Servers**: Pre-provisioned servers for instant startup (~5s vs ~30s cold start).\n- **Regions**: 13 global regions (us-east, us-south, us-west, eu-north, eu-west, eu-uk, ap-south, sea, ap-north, ap-southeast, south-africa, south-america-brazil, south-america-chile).\n- **Compute Sizes**: From micro (512MB) to dedicated-xlarge (16GB dedicated CPU).\n\n## Server Lifecycle\n\n`launching` → `running` (game ports open) → `stopped` (game exits, TTL expires, or manual stop)\n\n## Build Lifecycle\n\n`uploading` → `processing` (build pipeline creates deployable image) → `ready` | `failed`\n\n## Rate Limits\n\n- General reads: 100 req/sec per API key\n- Write operations (start server, upload build): 10 req/sec per API key\n- Player-facing lobby writes (join, matchmaking, start): 10 req/sec **per player** (keyed by `x-player-id`), so a game's whole player base doesn't share one bucket\n- Headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`\n\n## Plan Limits\n\n- **Free**: 1 server max, forced 1hr TTL, small size only\n- **Pro**: Unlimited servers, all sizes, custom TTL, pool servers\n\n## Error Format\n\nAll errors return: `{ \"error\": \"message\", \"detail\": \"context\", \"status\": 404 }`\n"},"servers":[{"url":"https://api.computeflow.cloud"}],"tags":[{"name":"Servers","description":"Create, manage, and monitor game server instances. Each server gets dedicated compute, network ports, and automatic health monitoring with TTL enforcement. Servers start in `launching` status and transition to `running` when the game opens its ports."},{"name":"Builds","description":"Upload and manage game server builds. Supports ZIP uploads (automatically processed into deployable images) and direct Docker image references. Builds are versioned per name per project — uploading a new build with the same name auto-increments the version. Servers always use the latest `ready` build unless a specific version is pinned."},{"name":"Projects","description":"Configure project-level settings including network ports, player authentication, environment variables, server pooling, and lobby/matchmaking rules. Settings apply as defaults to all servers in the project and can be overridden per-server at start time."},{"name":"Lobbies","description":"Player-centric lobby and matchmaking system. Every request identifies the player via the `x-player-id` header. Use `/me` endpoints to interact with your current lobby — the API resolves which lobby you're in automatically.\n\n**Quick start:** `POST /v3/lobbies/default` creates a lobby with sensible defaults (no dashboard config needed). Share the invite `code` with friends, or use `POST /v3/lobbies/default/me/matchmaking` to find opponents automatically.\n\n**Matchmaking** supports symmetric teams (2v2, 5v5), asymmetric roles (1 monster vs 5 hunters), FFA, battle royale duos, and skill-based matching with auto-expanding MMR buckets."}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"api-key"}},"schemas":{},"parameters":{}},"paths":{"/api/v3/servers/{instance_id}/roster":{"get":{"operationId":"getServerRoster","tags":["Servers"],"summary":"Get the authoritative match roster","description":"Returns the authoritative roster of players admitted to the match backing this server. Game servers call this (with their server API key and their own `INSTANCE_ID`) to know exactly which players to admit — this is the source of truth for admission and fixes reconnect / \"denied by proxy\" edge cases.\n\nThe response includes per-team slots, total `capacity`, `current_players`, backfill state, and a monotonically increasing `roster_version`. Poll `roster_version` cheaply to detect roster changes (e.g., when a backfill joiner is admitted).\n\nOnly available for servers that back a match (created through matchmaking/lobbies).","parameters":[{"schema":{"type":"string","description":"Server instance ID (the game server's own INSTANCE_ID)."},"required":true,"description":"Server instance ID (the game server's own INSTANCE_ID).","name":"instance_id","in":"path"}],"responses":{"200":{"description":"Authoritative roster for the match backing this server.","content":{"application/json":{"schema":{"type":"object","properties":{"match_id":{"type":"string","description":"Identifier of the match backing this server."},"instance_id":{"type":"string","description":"Server instance backing this match."},"status":{"type":"string","enum":["in_game","locked","completed"],"description":"Match lifecycle status. \"in_game\": active. \"locked\": no further joiners. \"completed\": ended."},"capacity":{"type":"integer","description":"Total admissible players across all teams (teams × playersPerTeam)."},"current_players":{"type":"integer","description":"Number of players currently on the roster."},"backfill_open":{"type":"boolean","description":"Whether the match is currently accepting backfill joiners."},"backfill_deadline":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp after which backfill closes. Null if no window is active."},"roster_version":{"type":"integer","description":"Monotonically increasing version bumped on every roster change. Poll this to detect updates cheaply."},"teams":{"type":"object","additionalProperties":{"type":"object","properties":{"capacity":{"type":"integer","description":"Maximum players this team admits."},"players":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string","description":"Player identifier."},"lobbyId":{"type":"string","description":"Lobby the player joined through."}},"required":["playerId","lobbyId"]},"description":"Players currently occupying slots on this team."}},"required":["capacity","players"],"description":"Per-team capacity and occupancy for a running match."},"description":"Per-team slot map keyed by team name."},"roster":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string","description":"Player identifier admitted to this match."},"team":{"type":"string","description":"Team the player is assigned to (e.g., \"team_1\", \"hunters\")."},"lobbyId":{"type":"string","description":"Lobby the player joined through."},"joinedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the player joined the match roster."}},"required":["playerId","team","lobbyId","joinedAt"],"description":"A single authoritative roster entry for a player in a running match."},"description":"Authoritative flat list of admitted players. The game server uses this to admit connecting players."}},"required":["match_id","instance_id","status","capacity","current_players","backfill_open","backfill_deadline","roster_version","teams","roster"],"description":"Authoritative roster for a running match. Game servers poll this (by instance_id, with a server API key) to know which players to admit."}}}},"404":{"description":"No active match found for this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/backfill":{"patch":{"operationId":"updateServerBackfill","tags":["Servers"],"summary":"Override backfill state (server-authoritative)","description":"Lets an advanced game server take control of its match's backfill. Set `accepting` to open or close backfill joiners, and optionally replace the `skillBand` that gates joiners.\n\nThis is an optional override — the engine manages backfill automatically for modes with backfill enabled. Use this only when the game server has better knowledge of when it can accept joiners (e.g., between rounds).\n\nRequires a server API key (`pf_...`).","parameters":[{"schema":{"type":"string","description":"Server instance ID (the game server's own INSTANCE_ID)."},"required":true,"description":"Server instance ID (the game server's own INSTANCE_ID).","name":"instance_id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accepting":{"type":"boolean","description":"Whether the match should accept backfill joiners. Set false to close backfill (server-authoritative override)."},"skillBand":{"type":["object","null"],"properties":{"field":{"type":"string","description":"Player-state field the skill band gates on."},"min":{"type":"number","description":"Inclusive lower bound of the admissible skill band."},"max":{"type":"number","description":"Inclusive upper bound of the admissible skill band."}},"required":["field","min","max"],"description":"Replace the match's skill band for joiners. Omit to leave the current band unchanged; null is treated as no change."}},"required":["accepting"],"description":"Server-authoritative override of a match's backfill state."}}}},"responses":{"200":{"description":"Backfill state updated. The roster mirror and roster_version reflect the change.","content":{"application/json":{"schema":{"type":"object","properties":{"match_id":{"type":"string","description":"Identifier of the match that was updated."},"instance_id":{"type":["string","null"],"description":"Server instance backing this match."},"status":{"type":"string","enum":["in_game","locked","completed"],"description":"Match lifecycle status after the update."},"backfill_open":{"type":"boolean","description":"Whether the match is now accepting backfill joiners."},"backfill_deadline":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp after which backfill closes. Null if no window is active."},"skill_band":{"type":["object","null"],"properties":{"field":{"type":"string"},"min":{"type":"number"},"max":{"type":"number"}},"required":["field","min","max"],"description":"Current skill band applied to joiners, or null if none."},"roster_version":{"type":"integer","description":"Monotonically increasing roster version after the update."}},"required":["match_id","instance_id","status","backfill_open","backfill_deadline","skill_band","roster_version"],"description":"Result of a server-authoritative backfill state change."}}}},"404":{"description":"No active match found for this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/backfill/lock":{"post":{"operationId":"lockServerBackfill","tags":["Servers"],"summary":"Close backfill (server-authoritative)","description":"Closes backfill for the match backing this server — no further joiners are admitted. Convenience shorthand for `PATCH /{instance_id}/backfill` with `accepting: false`.\n\nRequires a server API key (`pf_...`).","parameters":[{"schema":{"type":"string","description":"Server instance ID (the game server's own INSTANCE_ID)."},"required":true,"description":"Server instance ID (the game server's own INSTANCE_ID).","name":"instance_id","in":"path"}],"responses":{"200":{"description":"Backfill closed. The roster mirror and roster_version reflect the change.","content":{"application/json":{"schema":{"type":"object","properties":{"match_id":{"type":"string","description":"Identifier of the match that was updated."},"instance_id":{"type":["string","null"],"description":"Server instance backing this match."},"status":{"type":"string","enum":["in_game","locked","completed"],"description":"Match lifecycle status after the update."},"backfill_open":{"type":"boolean","description":"Whether the match is now accepting backfill joiners."},"backfill_deadline":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp after which backfill closes. Null if no window is active."},"skill_band":{"type":["object","null"],"properties":{"field":{"type":"string"},"min":{"type":"number"},"max":{"type":"number"}},"required":["field","min","max"],"description":"Current skill band applied to joiners, or null if none."},"roster_version":{"type":"integer","description":"Monotonically increasing roster version after the update."}},"required":["match_id","instance_id","status","backfill_open","backfill_deadline","skill_band","roster_version"],"description":"Result of a server-authoritative backfill state change."}}}},"404":{"description":"No active match found for this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers":{"get":{"operationId":"listServers","tags":["Servers"],"summary":"List servers","description":"Returns a paginated list of game servers for your project. By default, only servers in `running` status are returned. Use `include_launching=true` to also see servers that are still starting up, or `include_pool=true` to include pre-provisioned pool machines.","parameters":[{"schema":{"type":"string","enum":["true","false"],"default":"false","description":"Include servers in \"launching\" status. By default only \"running\" servers are returned."},"required":false,"description":"Include servers in \"launching\" status. By default only \"running\" servers are returned.","name":"include_launching","in":"query"},{"schema":{"type":"string","enum":["true","false"],"default":"false","description":"Include pre-provisioned pool servers (stopped machines waiting to be claimed)."},"required":false,"description":"Include pre-provisioned pool servers (stopped machines waiting to be claimed).","name":"include_pool","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Maximum number of servers per page (1–100). Defaults to 50."},"required":false,"description":"Maximum number of servers per page (1–100). Defaults to 50.","name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Number of servers to skip for pagination. Defaults to 0."},"required":false,"description":"Number of servers to skip for pagination. Defaults to 0.","name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated list of servers matching the query filters.","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number","description":"Total number of servers matching the query filters."},"servers":{"type":"array","items":{"type":"object","properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Complete details of a game server instance, including connection info, status, and configuration."},"description":"Array of server instances for the current page."},"limit":{"type":"number","description":"Maximum number of items per page (as requested)."},"offset":{"type":"number","description":"Number of items skipped (for pagination)."},"has_more":{"type":"boolean","description":"True if there are more servers beyond this page. Use offset + limit to fetch the next page."}},"required":["total","servers","limit","offset","has_more"],"description":"Paginated list of game server instances."}}}}}}},"/api/v3/servers/start":{"post":{"operationId":"startServer","tags":["Servers"],"summary":"Start a new server","description":"Creates and launches a new game server instance. The server starts in `launching` status and transitions to `running` when the game process opens its configured ports (typically 10–30 seconds, or ~5 seconds if a pool server is available).\n\nThe response includes `network_ports` with the connection details (host + external_port) that game clients need to connect.\n\n**Plan limits:** Free plan allows 1 active server with a 1-hour TTL and `small` compute. Pro plan unlocks unlimited servers with all compute sizes.\n\n**Build resolution:** Uses the latest `ready` build matching `version_tag` (defaults to `\"default\"`). Pin to a specific build with the `version` parameter.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"Display name for this server instance. Visible in the dashboard and API responses. Example: \"ranked-match-1\", \"lobby-eu-west\"."},"region":{"type":"string","minLength":1,"description":"Deployment region. Determines where the server runs physically. Case-insensitive. Available regions: us-east, us-south, us-west, eu-north, eu-west, eu-uk, ap-south, sea, ap-north, ap-southeast, south-america-brazil, south-america-chile, south-america, south-africa, ea. Note: south-america and ea are legacy aliases for south-america-brazil and ap-north."},"startup_args":{"type":"string","description":"Command-line arguments passed to your game executable. Accessible in your game via PLAYFLOW_STARTUP_ARGS env var or the config API. Example: \"-map forest -maxplayers 16 -tickrate 64\"."},"compute_size":{"type":"string","enum":["micro","small","medium","large","xlarge","dedicated-small","dedicated-medium","dedicated-large","dedicated-xlarge","persistent-small","persistent-medium","persistent-large","persistent-xlarge"],"default":"small","description":"Machine size for the server. Shared CPU: micro (512MB), small (1GB), medium (2GB), large (4GB), xlarge (8GB). Dedicated CPU: dedicated-small (2GB), dedicated-medium (4GB), dedicated-large (8GB), dedicated-xlarge (16GB). Free plan is limited to \"small\"."},"version_tag":{"type":"string","description":"Build name to deploy (e.g., \"default\", \"beta\", \"staging\"). Uses the latest ready build with this name. If omitted, defaults to \"default\"."},"version":{"type":"integer","description":"Specific build version number to deploy. Overrides version_tag to pin to an exact build version. Useful for rollbacks or testing specific builds."},"ttl":{"type":"integer","minimum":60,"maximum":86400,"description":"Time-to-live in seconds (60–86400). The server automatically stops after this duration. Free plan servers are forced to 3600 (1 hour). Omit for no auto-shutdown."},"auto_restart":{"type":"boolean","default":false,"description":"Automatically restart the game process if it crashes or exits unexpectedly. Uses on-failure restart policy with max 10 retries. After 10 failures, the machine is destroyed."},"custom_data":{"type":"object","additionalProperties":{},"description":"Arbitrary key-value data attached to this server. Accessible in your game via PLAYFLOW_CUSTOM_DATA env var or the config API at localhost:9090/v1/config. Example: {\"map\": \"forest\", \"mode\": \"ranked\"}."},"match_id":{"type":"string","description":"Match identifier for matchmaking integration. Auto-generated if not provided. Use this to link servers to your matchmaking system."},"environment_variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Additional environment variables injected into the game container. Merged with project-level defaults (server-level values override project defaults). Example: {\"LOG_LEVEL\": \"debug\"}."},"port_configs":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique name for this port (e.g., \"game_udp\", \"voice_tcp\"). Used to identify the port in network_ports responses."},"internal_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port number your game server listens on inside the container (1–65535). This is the port your game code binds to."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol. Use \"udp\" for real-time game traffic (FPS, racing) or \"tcp\" for reliable connections (turn-based, WebSocket)."},"tls_enabled":{"type":"boolean","default":false,"description":"Enable automatic TLS termination for this port. Only valid for TCP ports. Clients connect with TLS and PlayFlow terminates it before forwarding to your server."}},"required":["name","internal_port","protocol"],"description":"Override the project-level port configuration for a specific server. Allows customizing ports on a per-server basis at start time."},"description":"Override the project-level port configuration for this specific server. If omitted, the project's default port_configs are used."}},"required":["name","region"],"description":"Configuration for starting a new game server instance."}}}},"responses":{"201":{"description":"Server created and launching. The `network_ports` array contains connection details for game clients. Poll `GET /servers/{instance_id}` until status is `running`.","content":{"application/json":{"schema":{"type":"object","properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Complete details of a game server instance, including connection info, status, and configuration."}}}}}}},"/api/v3/servers/{instance_id}":{"get":{"operationId":"getServer","tags":["Servers"],"summary":"Get server details","description":"Returns the full details of a specific server instance, including its current status, network ports, configuration, and metadata. Use this to poll for status changes (e.g., wait for `launching` → `running`) or to get connection info for game clients.","parameters":[{"schema":{"type":"string","description":"Unique server instance ID (UUID) returned by the start endpoint."},"required":true,"description":"Unique server instance ID (UUID) returned by the start endpoint.","name":"instance_id","in":"path"}],"responses":{"200":{"description":"Full server details including status, connection info, and configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Complete details of a game server instance, including connection info, status, and configuration."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"delete":{"operationId":"stopServer","tags":["Servers"],"summary":"Stop a server","description":"Stops a running server. The status is updated to `stopped` immediately, and infrastructure cleanup happens in the background.\n\nOptionally provide a `shutdown_reason` for analytics (e.g., `PROCESS_EXITED`, `TTL_EXPIRED`). PlayFlow also calls this automatically when the game process exits or TTL expires.","parameters":[{"schema":{"type":"string","description":"Server instance ID to stop."},"required":true,"description":"Server instance ID to stop.","name":"instance_id","in":"path"},{"schema":{"type":"string","description":"Reason for stopping the server. Used for analytics and debugging. Common values: PROCESS_EXITED, TTL_EXPIRED, SHUTDOWN_SIGNAL, STARTUP_FAILURE_URL, STARTUP_FAILURE_DOWNLOAD, STARTUP_FAILURE_EXECUTABLE."},"required":false,"description":"Reason for stopping the server. Used for analytics and debugging. Common values: PROCESS_EXITED, TTL_EXPIRED, SHUTDOWN_SIGNAL, STARTUP_FAILURE_URL, STARTUP_FAILURE_DOWNLOAD, STARTUP_FAILURE_EXECUTABLE.","name":"shutdown_reason","in":"query"}],"responses":{"200":{"description":"Server stop initiated. Status is updated immediately; machine cleanup happens in the background.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Confirmation message, e.g., \"Server stopped successfully\"."}},"required":["status"],"description":"Response returned after successfully stopping a server."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/update":{"post":{"operationId":"updateServer","tags":["Servers"],"summary":"Update server custom_data","description":"Updates the `custom_data` field on a running server. Use this to store match state, player counts, map info, or any metadata that your game server or matchmaking system needs to expose via the API. The data is immediately visible in list and get responses.","parameters":[{"schema":{"type":"string","description":"Server instance ID to update."},"required":true,"description":"Server instance ID to update.","name":"instance_id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"custom_data":{"type":"object","additionalProperties":{},"description":"Replace the server's custom_data with this value. Useful for updating match state, player counts, or any metadata your game needs to expose via the API."}},"description":"Fields that can be updated on a running server."}}}},"responses":{"200":{"description":"Server updated with new custom_data.","content":{"application/json":{"schema":{"type":"object","properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Complete details of a game server instance, including connection info, status, and configuration."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/restart":{"post":{"operationId":"restartServer","tags":["Servers"],"summary":"Restart a server","description":"Restarts a server with a fresh instance. The old machine is replaced and new network ports are allocated. You can optionally change the build version, compute size, TTL, startup args, and other settings during restart.\n\nAll fields in the request body are optional — omitted fields keep their current values. The server returns to `launching` status.\n\n**Note:** Pool servers cannot be restarted.","parameters":[{"schema":{"type":"string","description":"Server instance ID to restart."},"required":true,"description":"Server instance ID to restart.","name":"instance_id","in":"path"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"New display name for the restarted server. Keeps the current name if omitted."},"startup_args":{"type":"string","description":"New command-line arguments for the game executable. Keeps current args if omitted."},"version_tag":{"type":"string","description":"Switch to a different build name on restart. Example: upgrade from \"default\" to \"beta\"."},"version":{"type":"integer","description":"Pin to a specific build version number on restart. Useful for rollbacks."},"ttl":{"type":"integer","minimum":60,"maximum":86400,"description":"New time-to-live in seconds (60–86400). Resets the TTL countdown from the restart time."},"auto_restart":{"type":"boolean","description":"Change auto-restart behavior on restart."},"custom_data":{"type":"object","additionalProperties":{},"description":"Replace custom_data on restart."},"match_id":{"type":"string","description":"New match identifier for the restarted server."},"environment_variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Replace environment variables on restart. Merged with project defaults."},"port_configs":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique name for this port (e.g., \"game_udp\", \"voice_tcp\"). Used to identify the port in network_ports responses."},"internal_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port number your game server listens on inside the container (1–65535). This is the port your game code binds to."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol. Use \"udp\" for real-time game traffic (FPS, racing) or \"tcp\" for reliable connections (turn-based, WebSocket)."},"tls_enabled":{"type":"boolean","default":false,"description":"Enable automatic TLS termination for this port. Only valid for TCP ports. Clients connect with TLS and PlayFlow terminates it before forwarding to your server."}},"required":["name","internal_port","protocol"],"description":"Override the project-level port configuration for a specific server. Allows customizing ports on a per-server basis at start time."},"description":"Override port configuration on restart. New ports are allocated."}},"description":"Configuration overrides applied during a server restart. All fields are optional — omitted fields keep their current values. The server gets a new machine with the same instance_id."}}}},"responses":{"200":{"description":"Server restarting with `launching` status. New network_ports are allocated.","content":{"application/json":{"schema":{"type":"object","properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Complete details of a game server instance, including connection info, status, and configuration."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/logs":{"get":{"operationId":"getServerLogs","tags":["Servers"],"summary":"Get server runtime logs","description":"Retrieves runtime logs (stdout/stderr) from a game server, including timestamps, log levels, and region info.\n\n**Pagination:** Use the `next_token` from the response as `start_time` in the next request to fetch subsequent logs. Returns an empty logs array if the server has never launched.","parameters":[{"schema":{"type":"string","description":"Server instance ID."},"required":true,"description":"Server instance ID.","name":"instance_id","in":"path"},{"schema":{"type":"string","description":"Window start. Accepts ISO 8601 format (e.g., \"2026-03-15T12:00:00Z\") or relative time (e.g., \"-1h\", \"-30m\"). Omit to use the default 1-hour window (matching the dashboard), NOT the whole retention period."},"required":false,"description":"Window start. Accepts ISO 8601 format (e.g., \"2026-03-15T12:00:00Z\") or relative time (e.g., \"-1h\", \"-30m\"). Omit to use the default 1-hour window (matching the dashboard), NOT the whole retention period.","name":"start_time","in":"query"},{"schema":{"type":"string","description":"Window end. Same format as start_time. Defaults to now if omitted. Pass the response's older_token here (keeping start_time) to page backward through the window."},"required":false,"description":"Window end. Same format as start_time. Defaults to now if omitted. Pass the response's older_token here (keeping start_time) to page backward through the window.","name":"end_time","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000,"description":"Maximum number of log entries to return (1–1000). Defaults to 1000. Returns the most recent entries in the window."},"required":false,"description":"Maximum number of log entries to return (1–1000). Defaults to 1000. Returns the most recent entries in the window.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":256,"description":"Case-insensitive substring to match within log messages. Applied server-side across the ENTIRE window, not just the returned page."},"required":false,"description":"Case-insensitive substring to match within log messages. Applied server-side across the ENTIRE window, not just the returned page.","name":"search","in":"query"},{"schema":{"type":"string","enum":["error","warn","info"],"description":"Restrict to a single inferred severity. Levels are inferred from message content (Fly forwards raw stdout without parsed levels)."},"required":false,"description":"Restrict to a single inferred severity. Levels are inferred from message content (Fly forwards raw stdout without parsed levels).","name":"level","in":"query"},{"schema":{"type":"string","enum":["newest","oldest"],"default":"newest","description":"Which end of the window to return when it holds more than `limit` rows. \"newest\" (default) returns the most-recent entries (live-tail). \"oldest\" returns the EARLIEST entries ascending from start_time — use it to reach a server's genuine first log lines. In \"oldest\" mode older_token is null (you are at the start) and next_token pages FORWARD toward now (pass it as start_time, keeping order=oldest)."},"required":false,"description":"Which end of the window to return when it holds more than `limit` rows. \"newest\" (default) returns the most-recent entries (live-tail). \"oldest\" returns the EARLIEST entries ascending from start_time — use it to reach a server's genuine first log lines. In \"oldest\" mode older_token is null (you are at the start) and next_token pages FORWARD toward now (pass it as start_time, keeping order=oldest).","name":"order","in":"query"}],"responses":{"200":{"description":"Server runtime logs with pagination cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","description":"ISO 8601 timestamp of the log entry."},"message":{"type":"string","description":"Log message content from the game server's stdout/stderr."},"level":{"type":"string","description":"Log severity: \"info\", \"warn\", or \"error\"."},"instance":{"type":"string","description":"Fly.io machine ID that produced this log entry."},"region":{"type":"string","description":"Fly.io region code where the server is running (e.g., \"ewr\", \"fra\")."}},"required":["timestamp","message","level","instance","region"],"description":"A single runtime log entry from a game server."},"description":"The most recent log entries for the server within the requested window (after any search/level filter), ordered oldest-to-newest."},"instance_id":{"type":"string","description":"Server instance ID these logs belong to."},"machine_id":{"type":["string","null"],"description":"Fly.io machine ID that produced these logs. Null if no machine is assigned (server never launched)."},"next_token":{"type":["string","null"],"description":"Live-tail cursor pointing just after the newest entry returned. Pass as start_time in the next request to fetch only newer logs. Null if no logs were returned."},"older_token":{"type":["string","null"],"description":"Backward cursor equal to the oldest entry returned. Pass as end_time (keeping the same start_time) to page further back through the window. Null if no logs were returned."}},"required":["logs","instance_id","machine_id","next_token","older_token"],"description":"Server runtime logs with forward (live-tail) and backward pagination support."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/metrics":{"get":{"operationId":"getServerMetrics","tags":["Servers"],"summary":"Get server resource metrics","description":"Returns CPU, memory, network, load, and connection metrics for a running server over a configurable time window. Each metric is a time series of `{t, v}` data points where `t` is a Unix timestamp and `v` is the metric value.\n\nUse `period` to control the time window (e.g., `1h` for last hour) and `step` for data point resolution (e.g., `60s` for one point per minute).\n\nOnly available for servers that are currently running.","parameters":[{"schema":{"type":"string","description":"Server instance ID."},"required":true,"description":"Server instance ID.","name":"instance_id","in":"path"},{"schema":{"type":"string","enum":["5m","15m","1h","6h","24h"],"default":"1h","description":"Relative time window for metrics (used when start_time is omitted). \"5m\": last 5 minutes, \"15m\": last 15 minutes, \"1h\": last hour, \"6h\": last 6 hours, \"24h\": last 24 hours."},"required":false,"description":"Relative time window for metrics (used when start_time is omitted). \"5m\": last 5 minutes, \"15m\": last 15 minutes, \"1h\": last hour, \"6h\": last 6 hours, \"24h\": last 24 hours.","name":"period","in":"query"},{"schema":{"type":"string","enum":["15s","30s","60s","300s","600s","1800s"],"default":"60s","description":"Data point interval. Smaller steps give higher resolution but more data. \"15s\": every 15 seconds … \"1800s\": every 30 minutes. Scale it to the window (a wide window needs a coarse step)."},"required":false,"description":"Data point interval. Smaller steps give higher resolution but more data. \"15s\": every 15 seconds … \"1800s\": every 30 minutes. Scale it to the window (a wide window needs a coarse step).","name":"step","in":"query"},{"schema":{"type":"string","description":"Absolute window start — ISO 8601 (e.g., \"2026-07-12T03:00:00Z\") or relative (e.g., \"-6h\"). When set it OVERRIDES period, so you can inspect a specific interval (pair with end_time)."},"required":false,"description":"Absolute window start — ISO 8601 (e.g., \"2026-07-12T03:00:00Z\") or relative (e.g., \"-6h\"). When set it OVERRIDES period, so you can inspect a specific interval (pair with end_time).","name":"start_time","in":"query"},{"schema":{"type":"string","description":"Absolute window end — ISO 8601. Defaults to now. Use with start_time for a fixed interval (e.g., a past incident window)."},"required":false,"description":"Absolute window end — ISO 8601. Defaults to now. Use with start_time for a fixed interval (e.g., a past incident window).","name":"end_time","in":"query"}],"responses":{"200":{"description":"Server resource metrics over the requested time window.","content":{"application/json":{"schema":{"type":"object","properties":{"instance_id":{"type":"string","description":"Server instance ID."},"machine_id":{"type":"string","description":"Fly.io machine ID providing these metrics."},"region":{"type":"string","description":"Fly.io region code (e.g., \"ewr\", \"fra\")."},"compute_size":{"type":["string","null"],"description":"PlayFlow compute size of the machine (e.g. \"small\", \"large\")."},"vcpus":{"type":["number","null"],"description":"Number of vCPUs allocated to the machine, derived from compute_size. Threshold the 1-minute load average against this: at/above vcpus is a warning, at/above 2× is an overload likely to freeze the game loop. Null when the size is unknown."},"lifecycle":{"type":"object","properties":{"started_at":{"type":["string","null"],"description":"ISO 8601 time the current run started (last launch/restart). Overlay as a reference line so restarts are visible on the metric charts."},"stopped_at":{"type":["string","null"],"description":"ISO 8601 time the server stopped, or null if still running."},"shutdown_reason":{"type":["string","null"],"description":"Why the server stopped (e.g. \"oom\", \"ttl\", \"crash\", \"user\"). Pair with stopped_at to mark an OOM/crash line on the timeline."}},"required":["started_at","stopped_at","shutdown_reason"],"description":"Restart/stop/OOM markers for the charts."},"period":{"type":"object","properties":{"start":{"type":"number","description":"Unix timestamp for the start of the metrics window."},"end":{"type":"number","description":"Unix timestamp for the end of the metrics window."},"step":{"type":"string","description":"Data point interval used (e.g., \"60s\")."}},"required":["start","end","step"],"description":"Time window and resolution for the returned metrics."},"cpu":{"type":"object","properties":{"usage_percent":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"CPU usage percentage over time (0–100+). Values above 100% indicate multiple cores being used."},"iowait_percent":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Percentage of CPU time spent waiting on I/O. Sustained high iowait is a common cause of game-loop stalls/freezes (the process is blocked on disk or network)."},"steal_percent":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Percentage of CPU time \"stolen\" by the hypervisor for other tenants. Elevated steal indicates noisy-neighbor contention on the host and can cause hitches the game cannot control."}},"required":["usage_percent"],"description":"CPU usage metrics, broken down to surface I/O-wait and steal which are typical freeze culprits."},"memory":{"type":"object","properties":{"used_mb":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Memory usage in megabytes over time."},"total_mb":{"type":"number","description":"Total memory available to this machine in megabytes."}},"required":["used_mb","total_mb"],"description":"Memory usage metrics."},"network":{"type":"object","properties":{"rx_bytes_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Inbound network throughput in bytes per second."},"tx_bytes_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Outbound network throughput in bytes per second."},"rx_packets_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Inbound packets per second. Packet rate (vs. byte rate) better reflects per-tick networking pressure for multiplayer servers sending many small updates."},"tx_packets_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Outbound packets per second."},"rx_dropped_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Inbound packets DROPPED per second. Sustained drops mean the server can't keep up with incoming traffic — a direct network cause of stalls/rubber-banding."},"tx_dropped_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Outbound packets dropped per second."},"rx_errors_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Inbound packet errors per second (malformed/failed frames). Elevated errors point at a link or driver problem rather than application load."},"tx_errors_per_sec":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Outbound packet errors per second."}},"required":["rx_bytes_per_sec","tx_bytes_per_sec"],"description":"Network I/O metrics (bytes, packets, and — for freeze diagnosis — dropped/error rates)."},"load":{"type":"object","properties":{"average_1m":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"1-minute load average over time. Values above the CPU count indicate the machine is overloaded."},"average_5m":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"5-minute load average. Sustained load above the CPU count (more than a brief 1m spike) indicates a persistent overload likely to cause freezing."},"average_15m":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"15-minute load average, for spotting slow-building saturation trends."}},"required":["average_1m"],"description":"System load average metrics over 1/5/15-minute windows."},"connections":{"type":"object","properties":{"tcp":{"type":"array","items":{"type":"object","properties":{"t":{"type":"number","description":"Unix timestamp (seconds since epoch) for this data point."},"v":{"type":"number","description":"Metric value at this timestamp. Units depend on the metric: percent for CPU, megabytes for memory, bytes/sec for network, count for connections."}},"required":["t","v"],"description":"A single time-series data point (timestamp + value)."},"description":"Number of active TCP connections over time."}},"required":["tcp"],"description":"Network connection metrics."}},"required":["instance_id","machine_id","region","compute_size","vcpus","lifecycle","period","cpu","memory","network","load","connections"],"description":"Server resource metrics including CPU, memory, network, load, and connections over a configurable time window."}}}},"404":{"description":"Server not found or no machine assigned.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/servers/{instance_id}/log-histogram":{"get":{"operationId":"getServerLogHistogram","tags":["Servers"],"summary":"Get server log-volume histogram","description":"Returns a stacked info/warn/error log-volume time-series for a single server over the same window as the logs. Use it to correlate a spike in error volume with a freeze: overlay these buckets on the CPU/memory/network charts on a shared time axis.\n\nBins are `step` seconds wide (default 60). Empty bins may be omitted — treat gaps as zero.","parameters":[{"schema":{"type":"string","description":"Server instance ID."},"required":true,"description":"Server instance ID.","name":"instance_id","in":"path"},{"schema":{"type":"string","description":"Window start. ISO 8601 or relative (e.g. \"-1h\"). Omit to use the default 1-hour window (matches the log query so the histogram and the log table share one time axis)."},"required":false,"description":"Window start. ISO 8601 or relative (e.g. \"-1h\"). Omit to use the default 1-hour window (matches the log query so the histogram and the log table share one time axis).","name":"start_time","in":"query"},{"schema":{"type":"string","description":"Window end. Same format as start_time. Defaults to now."},"required":false,"description":"Window end. Same format as start_time. Defaults to now.","name":"end_time","in":"query"},{"schema":{"type":"integer","minimum":5,"maximum":3600,"default":60,"description":"Bin width in SECONDS (5–3600). Each bucket counts logs whose _time falls in [bin, bin+step). Use a small step (e.g. 15) for short windows to resolve a freeze spike."},"required":false,"description":"Bin width in SECONDS (5–3600). Each bucket counts logs whose _time falls in [bin, bin+step). Use a small step (e.g. 15) for short windows to resolve a freeze spike.","name":"step","in":"query"},{"schema":{"type":"string","maxLength":256,"description":"Case-insensitive substring to match within log messages, applied server-side across the whole window (same semantics as the log query)."},"required":false,"description":"Case-insensitive substring to match within log messages, applied server-side across the whole window (same semantics as the log query).","name":"search","in":"query"}],"responses":{"200":{"description":"Log-volume histogram over the requested window.","content":{"application/json":{"schema":{"type":"object","properties":{"buckets":{"type":"array","items":{"type":"object","properties":{"t":{"type":"string","description":"ISO 8601 timestamp of the bin start."},"info":{"type":"integer","description":"Count of info-level entries in this bin."},"warn":{"type":"integer","description":"Count of warn-level entries in this bin."},"error":{"type":"integer","description":"Count of error-level entries in this bin."}},"required":["t","info","warn","error"],"description":"One time bin of the log-volume histogram with per-severity counts."},"description":"Ordered (oldest→newest) time bins. A bin with no logs may be omitted — treat gaps as zero when rendering."},"step":{"type":"integer","description":"Bin width in seconds actually used."},"window":{"type":"object","properties":{"start":{"type":"string","description":"ISO 8601 resolved window start."},"end":{"type":"string","description":"ISO 8601 resolved window end."}},"required":["start","end"],"description":"The resolved time window these buckets cover (after applying the default window)."}},"required":["buckets","step","window"],"description":"Log-volume histogram: per-severity counts bucketed over time, for correlating log spikes with resource metrics during a freeze."}}}},"404":{"description":"No server found with this instance_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/projects/log-histogram":{"get":{"operationId":"getProjectLogHistogram","tags":["Projects"],"summary":"Get project-wide log-volume histogram","description":"Returns a stacked info/warn/error log-volume time-series across ALL servers in the project over the same window as the project logs. Overlay it on fleet resource charts to spot which time bucket a freeze/error spike falls in.\n\nBins are `step` seconds wide (default 60). Empty bins may be omitted — treat gaps as zero.","parameters":[{"schema":{"type":"string","description":"Window start. ISO 8601 or relative (e.g. \"-1h\"). Omit to use the default 1-hour window (matches the log query so the histogram and the log table share one time axis)."},"required":false,"description":"Window start. ISO 8601 or relative (e.g. \"-1h\"). Omit to use the default 1-hour window (matches the log query so the histogram and the log table share one time axis).","name":"start_time","in":"query"},{"schema":{"type":"string","description":"Window end. Same format as start_time. Defaults to now."},"required":false,"description":"Window end. Same format as start_time. Defaults to now.","name":"end_time","in":"query"},{"schema":{"type":"integer","minimum":5,"maximum":3600,"default":60,"description":"Bin width in SECONDS (5–3600). Each bucket counts logs whose _time falls in [bin, bin+step). Use a small step (e.g. 15) for short windows to resolve a freeze spike."},"required":false,"description":"Bin width in SECONDS (5–3600). Each bucket counts logs whose _time falls in [bin, bin+step). Use a small step (e.g. 15) for short windows to resolve a freeze spike.","name":"step","in":"query"},{"schema":{"type":"string","maxLength":256,"description":"Case-insensitive substring to match within log messages, applied server-side across the whole window (same semantics as the log query)."},"required":false,"description":"Case-insensitive substring to match within log messages, applied server-side across the whole window (same semantics as the log query).","name":"search","in":"query"}],"responses":{"200":{"description":"Project-wide log-volume histogram over the requested window.","content":{"application/json":{"schema":{"type":"object","properties":{"buckets":{"type":"array","items":{"type":"object","properties":{"t":{"type":"string","description":"ISO 8601 timestamp of the bin start."},"info":{"type":"integer","description":"Count of info-level entries in this bin."},"warn":{"type":"integer","description":"Count of warn-level entries in this bin."},"error":{"type":"integer","description":"Count of error-level entries in this bin."}},"required":["t","info","warn","error"],"description":"One time bin of the log-volume histogram with per-severity counts."},"description":"Ordered (oldest→newest) time bins. A bin with no logs may be omitted — treat gaps as zero when rendering."},"step":{"type":"integer","description":"Bin width in seconds actually used."},"window":{"type":"object","properties":{"start":{"type":"string","description":"ISO 8601 resolved window start."},"end":{"type":"string","description":"ISO 8601 resolved window end."}},"required":["start","end"],"description":"The resolved time window these buckets cover (after applying the default window)."}},"required":["buckets","step","window"],"description":"Log-volume histogram: per-severity counts bucketed over time, for correlating log spikes with resource metrics during a freeze."}}}}}}},"/api/v3/projects/logs":{"get":{"operationId":"getProjectLogs","tags":["Projects"],"summary":"Get project-wide server logs","description":"Retrieves aggregated runtime logs from all servers in the project. Each log entry includes the server name, making it easy to filter and search across your entire fleet.\n\nUse `start_time` and optionally `end_time` to define the time range. Returns the **most recent** entries within that window (up to `limit`), ordered oldest-to-newest.\n\nNarrow results server-side with `search` (case-insensitive substring) and/or `level` (error/warn/info) — both apply across the ENTIRE window, not just the returned page, so you can find a specific error or session ID anywhere in the range. Set `include_counts=true` to also get per-level totals for the window.\n\n**Paging back:** if the window holds more than `limit` entries, pass the response's `older_token` as `end_time` (keeping the same `start_time`) to fetch the next older page. **Live tail:** poll with `next_token` as the next `start_time`; it points just after the newest entry returned, so each poll fetches only newer logs.","parameters":[{"schema":{"type":"string","description":"Window start. Accepts ISO 8601 format (e.g., \"2026-03-15T12:00:00Z\") or relative time (e.g., \"-1h\"). Omit to use the default 1-hour window (symmetric with the per-server log query)."},"required":false,"description":"Window start. Accepts ISO 8601 format (e.g., \"2026-03-15T12:00:00Z\") or relative time (e.g., \"-1h\"). Omit to use the default 1-hour window (symmetric with the per-server log query).","name":"start_time","in":"query"},{"schema":{"type":"string","description":"Window end. Same format as start_time. Defaults to current time if omitted. Pass the response's older_token here to page backward through the window."},"required":false,"description":"Window end. Same format as start_time. Defaults to current time if omitted. Pass the response's older_token here to page backward through the window.","name":"end_time","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000,"description":"Maximum number of log entries to return (1–1000). Defaults to 1000."},"required":false,"description":"Maximum number of log entries to return (1–1000). Defaults to 1000.","name":"limit","in":"query"},{"schema":{"type":"string","maxLength":256,"description":"Case-insensitive substring to match within log messages. Applied server-side across the ENTIRE window, not just the returned page."},"required":false,"description":"Case-insensitive substring to match within log messages. Applied server-side across the ENTIRE window, not just the returned page.","name":"search","in":"query"},{"schema":{"type":"string","enum":["error","warn","info"],"description":"Restrict to a single inferred severity. Levels are inferred from message content (Fly forwards raw stdout without parsed levels)."},"required":false,"description":"Restrict to a single inferred severity. Levels are inferred from message content (Fly forwards raw stdout without parsed levels).","name":"level","in":"query"},{"schema":{"type":["boolean","null"],"default":false,"description":"When true, also return per-level totals for the window (an extra aggregation query). Counts respect search but ignore the level filter."},"required":false,"description":"When true, also return per-level totals for the window (an extra aggregation query). Counts respect search but ignore the level filter.","name":"include_counts","in":"query"}],"responses":{"200":{"description":"Aggregated logs from all servers in the project with pagination cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","description":"ISO 8601 timestamp of the log entry."},"message":{"type":"string","description":"Log message content from the game server's stdout/stderr."},"level":{"type":"string","description":"Log severity: \"info\", \"warn\", or \"error\"."},"instance":{"type":"string","description":"Fly.io machine ID that produced this log entry."},"region":{"type":"string","description":"Fly.io region code where the server is running (e.g., \"ewr\", \"fra\")."},"server_name":{"type":"string","description":"Display name of the server that produced this log entry."}},"required":["timestamp","message","level","instance","region","server_name"],"description":"A runtime log entry enriched with the server name for project-wide log views."},"description":"The most recent log entries from all servers in the project within the requested window (after any search/level filter), ordered oldest-to-newest."},"next_token":{"type":["string","null"],"description":"Live-tail cursor pointing just after the newest entry returned. Pass as start_time in the next request to fetch only newer logs. Null if no logs were returned."},"older_token":{"type":["string","null"],"description":"Backward cursor equal to the oldest entry returned. Pass as end_time (keeping the same start_time) to page further back through the window. Null if no logs were returned."},"counts":{"type":["object","null"],"properties":{"all":{"type":"integer","description":"Total matching log entries across all severities."},"error":{"type":"integer","description":"Entries inferred as errors."},"warn":{"type":"integer","description":"Entries inferred as warnings."},"info":{"type":"integer","description":"Entries inferred as info."}},"required":["all","error","warn","info"],"description":"Per-level totals for the window when include_counts was set; otherwise null."}},"required":["logs","next_token","older_token","counts"],"description":"Aggregated runtime logs from all servers in the project."}}}}}}},"/api/v3/projects/settings":{"get":{"operationId":"getProjectSettings","tags":["Projects"],"summary":"Get project settings","description":"Returns the complete project configuration including network ports, player authentication, default environment variables, server pool settings, and lobby/matchmaking rules.\n\nThese settings are applied as defaults to every server started in this project. Individual settings can be overridden per-server at start time.","responses":{"200":{"description":"Complete current project settings.","content":{"application/json":{"schema":{"type":"object","properties":{"port_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Auto-generated unique identifier for this port config entry. Omit when creating new configs."},"name":{"type":"string","minLength":1,"maxLength":30,"description":"Human-readable name for this port (1–30 chars). Example: \"game_udp\", \"query_tcp\", \"voice\"."},"internal_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port number your game server listens on inside the container (1–65535). Must be unique per protocol within the project."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol. \"udp\" for real-time game traffic, \"tcp\" for reliable connections and WebSocket."},"tls_enabled":{"type":"boolean","description":"Enable automatic TLS termination. Only valid for TCP ports. When enabled, clients connect via TLS and PlayFlow handles certificate management."}},"required":["name","internal_port","protocol","tls_enabled"],"description":"Defines a network port that PlayFlow allocates for every server in this project."},"description":"Current network port configuration for the project."},"auth_config":{"oneOf":[{"type":"object","properties":{"provider":{"type":"string","enum":["none"],"description":"No player authentication. Any token (or no token) is accepted. Use only for development or trusted environments."}},"required":["provider"],"description":"Disable player authentication verification. Not recommended for production."},{"type":"object","properties":{"provider":{"type":"string","enum":["custom_jwt"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Works with Auth0, Firebase Auth, Supabase Auth, Clerk, and any OIDC-compliant provider."},"jwks_url":{"type":"string","format":"uri","description":"JWKS endpoint URL for verifying JWT signatures. Example: \"https://your-domain.auth0.com/.well-known/jwks.json\"."},"issuer":{"type":"string","description":"Expected JWT issuer (iss claim). Tokens with a different issuer are rejected. Example: \"https://your-domain.auth0.com/\"."},"audience":{"type":"string","description":"Expected JWT audience (aud claim). Example: \"your-api-identifier\"."}},"required":["provider","jwks_url"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Compatible with Auth0, Firebase, Supabase Auth, Clerk, and any OIDC-compliant provider."},{"type":"object","properties":{"provider":{"type":"string","enum":["playfab"],"description":"Verify player tokens using PlayFab entity tokens."},"title_id":{"type":"string","minLength":1,"description":"Your PlayFab Title ID. Found in the PlayFab dashboard under Settings > API Keys."}},"required":["provider","title_id"],"description":"Verify player tokens using PlayFab's entity token system."},{"type":"object","properties":{"provider":{"type":"string","enum":["steam"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."},"app_id":{"type":"string","minLength":1,"description":"Your Steam App ID. Found in the Steamworks dashboard."},"web_api_key":{"type":"string","minLength":1,"description":"Your Steamworks Web API key for server-side ticket verification."}},"required":["provider","app_id","web_api_key"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."}],"description":"Current player authentication configuration."},"environment_variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Current default environment variables for all servers."},"pool_config":{"type":["object","null"],"properties":{"enabled":{"type":"boolean","description":"Whether server pooling is active. When enabled, PlayFlow maintains pre-provisioned stopped machines that can be claimed instantly (~5s vs ~30s cold start)."},"regions":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"integer","minimum":0,"maximum":10}},"description":"Pool size configuration per region and compute size. Structure: { \"region\": { \"compute_size\": count } }. Example: { \"us-east\": { \"small\": 3, \"medium\": 1 }, \"eu-west\": { \"small\": 2 } }. Max 10 machines per region/size combination."},"build_name":{"type":"string","description":"Build name to pre-warm pool machines with. When set, pool-sweep provisions machines using the latest ready version of this build. When omitted, uses the latest ready build regardless of name."},"build_version":{"type":"integer","minimum":1,"description":"Specific build version to pin pool machines to. Only used when build_name is also set. When omitted, the latest version of the named build is used."}},"required":["enabled","regions"],"description":"Current server pool configuration. Null if pooling has never been configured."},"lobby_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Unique identifier for this lobby configuration (e.g., \"ranked-2v2\", \"casual-ffa\")."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"Human-readable lobby name displayed to players (e.g., \"Ranked 2v2\", \"Casual Free-for-All\")."},"enabled":{"type":"boolean","description":"Whether this lobby is active and accepting players."},"timeout":{"type":"integer","minimum":1,"description":"Lobby timeout in seconds. Lobbies are cleaned up after this duration of inactivity."},"heartbeat":{"type":"boolean","description":"Whether lobbies require periodic heartbeat signals to stay alive."},"heartbeatTimeout":{"type":"integer","description":"Seconds of missed heartbeats before a lobby is considered dead."},"hostLeaveBehavior":{"type":"string","enum":["promote","delete"],"default":"promote","description":"What happens when the host leaves a lobby. `promote` (default) transfers host duty to the next player; `delete` destroys the lobby and evicts all remaining players. Use `delete` for one-shot private rooms where the host's session IS the room."},"customProperties":{"type":"object","additionalProperties":{},"default":{},"description":"Arbitrary properties attached to lobbies of this type. Accessible by game clients for filtering."},"inviteCodeConfig":{"type":"object","properties":{"type":{"type":"string","enum":["numeric","alphanumeric","alphabetic"],"description":"Character set for generated invite codes."},"length":{"type":"integer","minimum":1,"maximum":16,"description":"Length of generated invite codes (1–16 characters)."},"prefix":{"type":"string","description":"Optional prefix prepended to all invite codes (e.g., \"PF-\" produces codes like \"PF-A3B7\")."}},"required":["type","length"],"description":"Configuration for auto-generated invite codes. Omit to disable invite codes for this lobby type."},"serverSettings":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"default":{},"description":"Default server configuration for servers created by this lobby type."},"matchmaking":{"type":"object","properties":{"modes":{"type":"object","additionalProperties":{"type":"object","properties":{"teams":{"type":"integer","minimum":1,"description":"Number of teams in a match (e.g., 2 for team deathmatch, 1 for FFA)."},"playersPerTeam":{"type":"integer","minimum":1,"description":"Maximum players per team."},"minPlayersPerTeam":{"type":"integer","minimum":1,"description":"Minimum players per team required to start the match."},"minTeams":{"type":"integer","minimum":1,"description":"Minimum number of teams required to start. Defaults to the value of teams."},"teamComposition":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"number"}},"description":"Custom team composition. Structure: { teamName: { roleName: count } }. Omit or leave empty for standard teams × playersPerTeam matches. Supports asymmetric teams (e.g. { monsters: { boss: 1 }, hunters: { medic: 1, assault: 2 } })."},"excludedFromQueue":{"type":"array","items":{"type":"string"},"description":"List of player attribute field names that exclude players from this queue (e.g., [\"banned\", \"cooldown\"])."},"allowBackfill":{"type":"boolean","description":"DEPRECATED alias for `backfill: { enabled: true }`. When true, newly-queued players are routed into an already-running match with open slots instead of always launching a new server. Prefer the structured `backfill` object; this flag is kept for backwards compatibility and maps to `{ enabled: true }`."},"backfill":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Master switch. When true, newly-queued lobbies can join an already-running match with open slots (real backfill) before a fresh server is launched."},"windowSeconds":{"type":"integer","minimum":1,"description":"How long after match formation the match stays open to joiners. Omit for the engine default."},"closeWhenFull":{"type":"boolean","description":"Close backfill automatically once the match reaches capacity. Defaults to true."},"skill":{"type":"object","properties":{"field":{"type":"string","description":"Player-state field to gate joiners on (e.g., \"mmr\")."},"maxDifference":{"type":"number","description":"A joining lobby's average value on `field` must be within this distance of the running match's band to be admitted."}},"required":["field","maxDifference"],"description":"Optional skill gate for backfill joiners."}},"required":["enabled"],"description":"Backfill configuration. Omit or set enabled:false to disable — non-backfill modes behave exactly as before. Takes precedence over the deprecated `allowBackfill` flag."},"timeout":{"type":"integer","minimum":1,"description":"Matchmaking timeout in seconds. Players are removed from the queue if not matched within this time."},"maxPartySize":{"type":"integer","minimum":1,"description":"Maximum number of players that can queue together as a party."},"rules":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["difference"]},"field":{"type":"string","minLength":1,"description":"Numeric player attribute field to compare (e.g., \"mmr\", \"level\")."},"maxDifference":{"type":"number","minimum":0,"description":"Max |A.field - B.field|. Auto-expands with wait time."}},"required":["type","field","maxDifference"],"description":"Match only if both lobbies' averaged numeric field values are within tolerance."},{"type":"object","properties":{"type":{"type":"string","enum":["equals"]},"field":{"type":"string","minLength":1,"description":"Field whose value must match on both sides (e.g., \"gameVersion\")."}},"required":["type","field"],"description":"Match only if both lobbies have the same value for a field (strings/bools; unanimous across party)."},{"type":"object","properties":{"type":{"type":"string","enum":["not_equals"]},"field":{"type":"string","minLength":1,"description":"Field whose values must differ (e.g., \"lastOpponent\" — anti-rematch)."}},"required":["type","field"],"description":"Match only if both lobbies differ on a field value."},{"type":"object","properties":{"type":{"type":"string","enum":["region"]},"minOverlap":{"type":"integer","minimum":1,"description":"Minimum shared regions. Default 1."}},"required":["type"],"description":"Match only if acceptable regions overlap by at least `minOverlap` entries."},{"type":"object","properties":{"type":{"type":"string","enum":["expression"]},"cel":{"type":"string","minLength":1,"description":"CEL expression. Tickets `a` and `b` plus `ctx` are in scope. e.g., `a.state.mmr > 1000 && b.state.mmr > 1000`."}},"required":["type","cel"],"description":"CEL expression escape hatch for custom match logic."}],"description":"One of five rule primitives (difference, equals, not_equals, region, expression). See docs."},"description":"Declarative match rules. All must pass for two lobbies to match. See docs for full primitive grammar."},"useVersionFromState":{"type":"string","description":"When set, the server's build name (`version_tag`) is taken from this field in the matched players' state. Lets you run multiple game versions simultaneously with no rolling updates. REQUIRED: an `equals` rule on the same field, so matched players always share the version."},"matchConfirmation":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, matches enter `match_found` and await player acceptance."},"timeoutSeconds":{"type":"integer","minimum":1,"description":"Seconds players have to confirm before the match cancels."},"requireAllPlayers":{"type":"boolean","description":"When true (default), every participating lobby must confirm. When false, partial confirmations can proceed."}},"required":["enabled","timeoutSeconds"],"description":"Optional 'Accept Match' flow. Matches pause at `match_found` for player confirmation before launching."},"serverOverrides":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"description":"Override the lobby's default server settings for this specific mode."}},"required":["teams","playersPerTeam","minPlayersPerTeam","timeout","maxPartySize"],"description":"Configuration for a matchmaking mode within a lobby. Defines team structure, player limits, and matching rules."},"description":"Named matchmaking modes. Keys are mode identifiers (e.g., \"ranked\", \"casual\"). Values define team structure and rules."}},"required":["modes"],"description":"Matchmaking configuration. Omit to create a lobby without automated matchmaking."}},"required":["id","name","enabled","timeout"],"description":"Configuration for a lobby type. Defines player limits, matchmaking rules, and server settings."},"description":"Current lobby and matchmaking configurations."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last settings update."}},"required":["port_configs","auth_config","environment_variables","pool_config","lobby_configs","updated_at"],"description":"Complete current project settings including ports, auth, environment, pool, and lobby configurations."}}}},"500":{"description":"Internal server error while fetching settings.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"post":{"operationId":"updateProjectSettings","tags":["Projects"],"summary":"Update project settings","description":"Updates project-level configuration. All fields are optional — only include what you want to change. Omitted fields remain unchanged. **Requires a server key (pf_...); client keys (pfclient_*) are rejected with 403.**\n\n**port_configs:** Network ports allocated for every server (max 10). Define game ports, query ports, etc.\n\n**auth_config:** Player authentication provider (none, custom_jwt, playfab, steam).\n\n**environment_variables:** Default env vars injected into every game server.\n\n**pool_config:** Pre-provisioned server pool sizes per region and compute size.\n\n**lobby_configs:** Lobby types with matchmaking rules, team structures, and invite codes (max 20).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"port_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Auto-generated unique identifier for this port config entry. Omit when creating new configs."},"name":{"type":"string","minLength":1,"maxLength":30,"description":"Human-readable name for this port (1–30 chars). Example: \"game_udp\", \"query_tcp\", \"voice\"."},"internal_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port number your game server listens on inside the container (1–65535). Must be unique per protocol within the project."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol. \"udp\" for real-time game traffic, \"tcp\" for reliable connections and WebSocket."},"tls_enabled":{"type":"boolean","description":"Enable automatic TLS termination. Only valid for TCP ports. When enabled, clients connect via TLS and PlayFlow handles certificate management."}},"required":["name","internal_port","protocol","tls_enabled"],"description":"Defines a network port that PlayFlow allocates for every server in this project."},"maxItems":10,"description":"Network port configuration for all servers in this project (max 10 ports). Defines which ports PlayFlow allocates for each server."},"auth_config":{"oneOf":[{"type":"object","properties":{"provider":{"type":"string","enum":["none"],"description":"No player authentication. Any token (or no token) is accepted. Use only for development or trusted environments."}},"required":["provider"],"description":"Disable player authentication verification. Not recommended for production."},{"type":"object","properties":{"provider":{"type":"string","enum":["custom_jwt"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Works with Auth0, Firebase Auth, Supabase Auth, Clerk, and any OIDC-compliant provider."},"jwks_url":{"type":"string","format":"uri","description":"JWKS endpoint URL for verifying JWT signatures. Example: \"https://your-domain.auth0.com/.well-known/jwks.json\"."},"issuer":{"type":"string","description":"Expected JWT issuer (iss claim). Tokens with a different issuer are rejected. Example: \"https://your-domain.auth0.com/\"."},"audience":{"type":"string","description":"Expected JWT audience (aud claim). Example: \"your-api-identifier\"."}},"required":["provider","jwks_url"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Compatible with Auth0, Firebase, Supabase Auth, Clerk, and any OIDC-compliant provider."},{"type":"object","properties":{"provider":{"type":"string","enum":["playfab"],"description":"Verify player tokens using PlayFab entity tokens."},"title_id":{"type":"string","minLength":1,"description":"Your PlayFab Title ID. Found in the PlayFab dashboard under Settings > API Keys."}},"required":["provider","title_id"],"description":"Verify player tokens using PlayFab's entity token system."},{"type":"object","properties":{"provider":{"type":"string","enum":["steam"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."},"app_id":{"type":"string","minLength":1,"description":"Your Steam App ID. Found in the Steamworks dashboard."},"web_api_key":{"type":"string","minLength":1,"description":"Your Steamworks Web API key for server-side ticket verification."}},"required":["provider","app_id","web_api_key"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."}],"description":"Player authentication provider configuration. Determines how player tokens are verified."},"environment_variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Default environment variables injected into every game server. Can be overridden per-server at start time."},"pool_config":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether server pooling is active. When enabled, PlayFlow maintains pre-provisioned stopped machines that can be claimed instantly (~5s vs ~30s cold start)."},"regions":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"integer","minimum":0,"maximum":10}},"description":"Pool size configuration per region and compute size. Structure: { \"region\": { \"compute_size\": count } }. Example: { \"us-east\": { \"small\": 3, \"medium\": 1 }, \"eu-west\": { \"small\": 2 } }. Max 10 machines per region/size combination."},"build_name":{"type":"string","description":"Build name to pre-warm pool machines with. When set, pool-sweep provisions machines using the latest ready version of this build. When omitted, uses the latest ready build regardless of name."},"build_version":{"type":"integer","minimum":1,"description":"Specific build version to pin pool machines to. Only used when build_name is also set. When omitted, the latest version of the named build is used."}},"required":["enabled","regions"],"description":"Server pool configuration for reducing cold-start latency. Set to { enabled: false, regions: {} } to disable pooling."},"lobby_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Unique identifier for this lobby configuration (e.g., \"ranked-2v2\", \"casual-ffa\")."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"Human-readable lobby name displayed to players (e.g., \"Ranked 2v2\", \"Casual Free-for-All\")."},"enabled":{"type":"boolean","description":"Whether this lobby is active and accepting players."},"timeout":{"type":"integer","minimum":1,"description":"Lobby timeout in seconds. Lobbies are cleaned up after this duration of inactivity."},"heartbeat":{"type":"boolean","description":"Whether lobbies require periodic heartbeat signals to stay alive."},"heartbeatTimeout":{"type":"integer","description":"Seconds of missed heartbeats before a lobby is considered dead."},"hostLeaveBehavior":{"type":"string","enum":["promote","delete"],"default":"promote","description":"What happens when the host leaves a lobby. `promote` (default) transfers host duty to the next player; `delete` destroys the lobby and evicts all remaining players. Use `delete` for one-shot private rooms where the host's session IS the room."},"customProperties":{"type":"object","additionalProperties":{},"default":{},"description":"Arbitrary properties attached to lobbies of this type. Accessible by game clients for filtering."},"inviteCodeConfig":{"type":"object","properties":{"type":{"type":"string","enum":["numeric","alphanumeric","alphabetic"],"description":"Character set for generated invite codes."},"length":{"type":"integer","minimum":1,"maximum":16,"description":"Length of generated invite codes (1–16 characters)."},"prefix":{"type":"string","description":"Optional prefix prepended to all invite codes (e.g., \"PF-\" produces codes like \"PF-A3B7\")."}},"required":["type","length"],"description":"Configuration for auto-generated invite codes. Omit to disable invite codes for this lobby type."},"serverSettings":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"default":{},"description":"Default server configuration for servers created by this lobby type."},"matchmaking":{"type":"object","properties":{"modes":{"type":"object","additionalProperties":{"type":"object","properties":{"teams":{"type":"integer","minimum":1,"description":"Number of teams in a match (e.g., 2 for team deathmatch, 1 for FFA)."},"playersPerTeam":{"type":"integer","minimum":1,"description":"Maximum players per team."},"minPlayersPerTeam":{"type":"integer","minimum":1,"description":"Minimum players per team required to start the match."},"minTeams":{"type":"integer","minimum":1,"description":"Minimum number of teams required to start. Defaults to the value of teams."},"teamComposition":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"number"}},"description":"Custom team composition. Structure: { teamName: { roleName: count } }. Omit or leave empty for standard teams × playersPerTeam matches. Supports asymmetric teams (e.g. { monsters: { boss: 1 }, hunters: { medic: 1, assault: 2 } })."},"excludedFromQueue":{"type":"array","items":{"type":"string"},"description":"List of player attribute field names that exclude players from this queue (e.g., [\"banned\", \"cooldown\"])."},"allowBackfill":{"type":"boolean","description":"DEPRECATED alias for `backfill: { enabled: true }`. When true, newly-queued players are routed into an already-running match with open slots instead of always launching a new server. Prefer the structured `backfill` object; this flag is kept for backwards compatibility and maps to `{ enabled: true }`."},"backfill":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Master switch. When true, newly-queued lobbies can join an already-running match with open slots (real backfill) before a fresh server is launched."},"windowSeconds":{"type":"integer","minimum":1,"description":"How long after match formation the match stays open to joiners. Omit for the engine default."},"closeWhenFull":{"type":"boolean","description":"Close backfill automatically once the match reaches capacity. Defaults to true."},"skill":{"type":"object","properties":{"field":{"type":"string","description":"Player-state field to gate joiners on (e.g., \"mmr\")."},"maxDifference":{"type":"number","description":"A joining lobby's average value on `field` must be within this distance of the running match's band to be admitted."}},"required":["field","maxDifference"],"description":"Optional skill gate for backfill joiners."}},"required":["enabled"],"description":"Backfill configuration. Omit or set enabled:false to disable — non-backfill modes behave exactly as before. Takes precedence over the deprecated `allowBackfill` flag."},"timeout":{"type":"integer","minimum":1,"description":"Matchmaking timeout in seconds. Players are removed from the queue if not matched within this time."},"maxPartySize":{"type":"integer","minimum":1,"description":"Maximum number of players that can queue together as a party."},"rules":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["difference"]},"field":{"type":"string","minLength":1,"description":"Numeric player attribute field to compare (e.g., \"mmr\", \"level\")."},"maxDifference":{"type":"number","minimum":0,"description":"Max |A.field - B.field|. Auto-expands with wait time."}},"required":["type","field","maxDifference"],"description":"Match only if both lobbies' averaged numeric field values are within tolerance."},{"type":"object","properties":{"type":{"type":"string","enum":["equals"]},"field":{"type":"string","minLength":1,"description":"Field whose value must match on both sides (e.g., \"gameVersion\")."}},"required":["type","field"],"description":"Match only if both lobbies have the same value for a field (strings/bools; unanimous across party)."},{"type":"object","properties":{"type":{"type":"string","enum":["not_equals"]},"field":{"type":"string","minLength":1,"description":"Field whose values must differ (e.g., \"lastOpponent\" — anti-rematch)."}},"required":["type","field"],"description":"Match only if both lobbies differ on a field value."},{"type":"object","properties":{"type":{"type":"string","enum":["region"]},"minOverlap":{"type":"integer","minimum":1,"description":"Minimum shared regions. Default 1."}},"required":["type"],"description":"Match only if acceptable regions overlap by at least `minOverlap` entries."},{"type":"object","properties":{"type":{"type":"string","enum":["expression"]},"cel":{"type":"string","minLength":1,"description":"CEL expression. Tickets `a` and `b` plus `ctx` are in scope. e.g., `a.state.mmr > 1000 && b.state.mmr > 1000`."}},"required":["type","cel"],"description":"CEL expression escape hatch for custom match logic."}],"description":"One of five rule primitives (difference, equals, not_equals, region, expression). See docs."},"description":"Declarative match rules. All must pass for two lobbies to match. See docs for full primitive grammar."},"useVersionFromState":{"type":"string","description":"When set, the server's build name (`version_tag`) is taken from this field in the matched players' state. Lets you run multiple game versions simultaneously with no rolling updates. REQUIRED: an `equals` rule on the same field, so matched players always share the version."},"matchConfirmation":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, matches enter `match_found` and await player acceptance."},"timeoutSeconds":{"type":"integer","minimum":1,"description":"Seconds players have to confirm before the match cancels."},"requireAllPlayers":{"type":"boolean","description":"When true (default), every participating lobby must confirm. When false, partial confirmations can proceed."}},"required":["enabled","timeoutSeconds"],"description":"Optional 'Accept Match' flow. Matches pause at `match_found` for player confirmation before launching."},"serverOverrides":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"description":"Override the lobby's default server settings for this specific mode."}},"required":["teams","playersPerTeam","minPlayersPerTeam","timeout","maxPartySize"],"description":"Configuration for a matchmaking mode within a lobby. Defines team structure, player limits, and matching rules."},"description":"Named matchmaking modes. Keys are mode identifiers (e.g., \"ranked\", \"casual\"). Values define team structure and rules."}},"required":["modes"],"description":"Matchmaking configuration. Omit to create a lobby without automated matchmaking."}},"required":["id","name","enabled","timeout"],"description":"Configuration for a lobby type. Defines player limits, matchmaking rules, and server settings."},"maxItems":20,"description":"Lobby and matchmaking configuration. Defines lobby types, team structures, and matching rules (max 20)."}},"description":"Project settings to update. All fields are optional — only include what you want to change. Omitted fields remain unchanged."}}}},"responses":{"200":{"description":"Updated project settings (returns all fields, including unchanged ones).","content":{"application/json":{"schema":{"type":"object","properties":{"port_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Auto-generated unique identifier for this port config entry. Omit when creating new configs."},"name":{"type":"string","minLength":1,"maxLength":30,"description":"Human-readable name for this port (1–30 chars). Example: \"game_udp\", \"query_tcp\", \"voice\"."},"internal_port":{"type":"integer","minimum":1,"maximum":65535,"description":"Port number your game server listens on inside the container (1–65535). Must be unique per protocol within the project."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol. \"udp\" for real-time game traffic, \"tcp\" for reliable connections and WebSocket."},"tls_enabled":{"type":"boolean","description":"Enable automatic TLS termination. Only valid for TCP ports. When enabled, clients connect via TLS and PlayFlow handles certificate management."}},"required":["name","internal_port","protocol","tls_enabled"],"description":"Defines a network port that PlayFlow allocates for every server in this project."},"description":"Current network port configuration for the project."},"auth_config":{"oneOf":[{"type":"object","properties":{"provider":{"type":"string","enum":["none"],"description":"No player authentication. Any token (or no token) is accepted. Use only for development or trusted environments."}},"required":["provider"],"description":"Disable player authentication verification. Not recommended for production."},{"type":"object","properties":{"provider":{"type":"string","enum":["custom_jwt"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Works with Auth0, Firebase Auth, Supabase Auth, Clerk, and any OIDC-compliant provider."},"jwks_url":{"type":"string","format":"uri","description":"JWKS endpoint URL for verifying JWT signatures. Example: \"https://your-domain.auth0.com/.well-known/jwks.json\"."},"issuer":{"type":"string","description":"Expected JWT issuer (iss claim). Tokens with a different issuer are rejected. Example: \"https://your-domain.auth0.com/\"."},"audience":{"type":"string","description":"Expected JWT audience (aud claim). Example: \"your-api-identifier\"."}},"required":["provider","jwks_url"],"description":"Verify player tokens as JWTs using a JWKS endpoint. Compatible with Auth0, Firebase, Supabase Auth, Clerk, and any OIDC-compliant provider."},{"type":"object","properties":{"provider":{"type":"string","enum":["playfab"],"description":"Verify player tokens using PlayFab entity tokens."},"title_id":{"type":"string","minLength":1,"description":"Your PlayFab Title ID. Found in the PlayFab dashboard under Settings > API Keys."}},"required":["provider","title_id"],"description":"Verify player tokens using PlayFab's entity token system."},{"type":"object","properties":{"provider":{"type":"string","enum":["steam"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."},"app_id":{"type":"string","minLength":1,"description":"Your Steam App ID. Found in the Steamworks dashboard."},"web_api_key":{"type":"string","minLength":1,"description":"Your Steamworks Web API key for server-side ticket verification."}},"required":["provider","app_id","web_api_key"],"description":"Verify player tokens using Steam session tickets via the Steamworks Web API."}],"description":"Current player authentication configuration."},"environment_variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Current default environment variables for all servers."},"pool_config":{"type":["object","null"],"properties":{"enabled":{"type":"boolean","description":"Whether server pooling is active. When enabled, PlayFlow maintains pre-provisioned stopped machines that can be claimed instantly (~5s vs ~30s cold start)."},"regions":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"integer","minimum":0,"maximum":10}},"description":"Pool size configuration per region and compute size. Structure: { \"region\": { \"compute_size\": count } }. Example: { \"us-east\": { \"small\": 3, \"medium\": 1 }, \"eu-west\": { \"small\": 2 } }. Max 10 machines per region/size combination."},"build_name":{"type":"string","description":"Build name to pre-warm pool machines with. When set, pool-sweep provisions machines using the latest ready version of this build. When omitted, uses the latest ready build regardless of name."},"build_version":{"type":"integer","minimum":1,"description":"Specific build version to pin pool machines to. Only used when build_name is also set. When omitted, the latest version of the named build is used."}},"required":["enabled","regions"],"description":"Current server pool configuration. Null if pooling has never been configured."},"lobby_configs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Unique identifier for this lobby configuration (e.g., \"ranked-2v2\", \"casual-ffa\")."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"Human-readable lobby name displayed to players (e.g., \"Ranked 2v2\", \"Casual Free-for-All\")."},"enabled":{"type":"boolean","description":"Whether this lobby is active and accepting players."},"timeout":{"type":"integer","minimum":1,"description":"Lobby timeout in seconds. Lobbies are cleaned up after this duration of inactivity."},"heartbeat":{"type":"boolean","description":"Whether lobbies require periodic heartbeat signals to stay alive."},"heartbeatTimeout":{"type":"integer","description":"Seconds of missed heartbeats before a lobby is considered dead."},"hostLeaveBehavior":{"type":"string","enum":["promote","delete"],"default":"promote","description":"What happens when the host leaves a lobby. `promote` (default) transfers host duty to the next player; `delete` destroys the lobby and evicts all remaining players. Use `delete` for one-shot private rooms where the host's session IS the room."},"customProperties":{"type":"object","additionalProperties":{},"default":{},"description":"Arbitrary properties attached to lobbies of this type. Accessible by game clients for filtering."},"inviteCodeConfig":{"type":"object","properties":{"type":{"type":"string","enum":["numeric","alphanumeric","alphabetic"],"description":"Character set for generated invite codes."},"length":{"type":"integer","minimum":1,"maximum":16,"description":"Length of generated invite codes (1–16 characters)."},"prefix":{"type":"string","description":"Optional prefix prepended to all invite codes (e.g., \"PF-\" produces codes like \"PF-A3B7\")."}},"required":["type","length"],"description":"Configuration for auto-generated invite codes. Omit to disable invite codes for this lobby type."},"serverSettings":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"default":{},"description":"Default server configuration for servers created by this lobby type."},"matchmaking":{"type":"object","properties":{"modes":{"type":"object","additionalProperties":{"type":"object","properties":{"teams":{"type":"integer","minimum":1,"description":"Number of teams in a match (e.g., 2 for team deathmatch, 1 for FFA)."},"playersPerTeam":{"type":"integer","minimum":1,"description":"Maximum players per team."},"minPlayersPerTeam":{"type":"integer","minimum":1,"description":"Minimum players per team required to start the match."},"minTeams":{"type":"integer","minimum":1,"description":"Minimum number of teams required to start. Defaults to the value of teams."},"teamComposition":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"number"}},"description":"Custom team composition. Structure: { teamName: { roleName: count } }. Omit or leave empty for standard teams × playersPerTeam matches. Supports asymmetric teams (e.g. { monsters: { boss: 1 }, hunters: { medic: 1, assault: 2 } })."},"excludedFromQueue":{"type":"array","items":{"type":"string"},"description":"List of player attribute field names that exclude players from this queue (e.g., [\"banned\", \"cooldown\"])."},"allowBackfill":{"type":"boolean","description":"DEPRECATED alias for `backfill: { enabled: true }`. When true, newly-queued players are routed into an already-running match with open slots instead of always launching a new server. Prefer the structured `backfill` object; this flag is kept for backwards compatibility and maps to `{ enabled: true }`."},"backfill":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Master switch. When true, newly-queued lobbies can join an already-running match with open slots (real backfill) before a fresh server is launched."},"windowSeconds":{"type":"integer","minimum":1,"description":"How long after match formation the match stays open to joiners. Omit for the engine default."},"closeWhenFull":{"type":"boolean","description":"Close backfill automatically once the match reaches capacity. Defaults to true."},"skill":{"type":"object","properties":{"field":{"type":"string","description":"Player-state field to gate joiners on (e.g., \"mmr\")."},"maxDifference":{"type":"number","description":"A joining lobby's average value on `field` must be within this distance of the running match's band to be admitted."}},"required":["field","maxDifference"],"description":"Optional skill gate for backfill joiners."}},"required":["enabled"],"description":"Backfill configuration. Omit or set enabled:false to disable — non-backfill modes behave exactly as before. Takes precedence over the deprecated `allowBackfill` flag."},"timeout":{"type":"integer","minimum":1,"description":"Matchmaking timeout in seconds. Players are removed from the queue if not matched within this time."},"maxPartySize":{"type":"integer","minimum":1,"description":"Maximum number of players that can queue together as a party."},"rules":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["difference"]},"field":{"type":"string","minLength":1,"description":"Numeric player attribute field to compare (e.g., \"mmr\", \"level\")."},"maxDifference":{"type":"number","minimum":0,"description":"Max |A.field - B.field|. Auto-expands with wait time."}},"required":["type","field","maxDifference"],"description":"Match only if both lobbies' averaged numeric field values are within tolerance."},{"type":"object","properties":{"type":{"type":"string","enum":["equals"]},"field":{"type":"string","minLength":1,"description":"Field whose value must match on both sides (e.g., \"gameVersion\")."}},"required":["type","field"],"description":"Match only if both lobbies have the same value for a field (strings/bools; unanimous across party)."},{"type":"object","properties":{"type":{"type":"string","enum":["not_equals"]},"field":{"type":"string","minLength":1,"description":"Field whose values must differ (e.g., \"lastOpponent\" — anti-rematch)."}},"required":["type","field"],"description":"Match only if both lobbies differ on a field value."},{"type":"object","properties":{"type":{"type":"string","enum":["region"]},"minOverlap":{"type":"integer","minimum":1,"description":"Minimum shared regions. Default 1."}},"required":["type"],"description":"Match only if acceptable regions overlap by at least `minOverlap` entries."},{"type":"object","properties":{"type":{"type":"string","enum":["expression"]},"cel":{"type":"string","minLength":1,"description":"CEL expression. Tickets `a` and `b` plus `ctx` are in scope. e.g., `a.state.mmr > 1000 && b.state.mmr > 1000`."}},"required":["type","cel"],"description":"CEL expression escape hatch for custom match logic."}],"description":"One of five rule primitives (difference, equals, not_equals, region, expression). See docs."},"description":"Declarative match rules. All must pass for two lobbies to match. See docs for full primitive grammar."},"useVersionFromState":{"type":"string","description":"When set, the server's build name (`version_tag`) is taken from this field in the matched players' state. Lets you run multiple game versions simultaneously with no rolling updates. REQUIRED: an `equals` rule on the same field, so matched players always share the version."},"matchConfirmation":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, matches enter `match_found` and await player acceptance."},"timeoutSeconds":{"type":"integer","minimum":1,"description":"Seconds players have to confirm before the match cancels."},"requireAllPlayers":{"type":"boolean","description":"When true (default), every participating lobby must confirm. When false, partial confirmations can proceed."}},"required":["enabled","timeoutSeconds"],"description":"Optional 'Accept Match' flow. Matches pause at `match_found` for player confirmation before launching."},"serverOverrides":{"type":"object","properties":{"gameBuild":{"type":"string","description":"Build name to use for servers created by this lobby/mode (e.g., \"default\", \"ranked\")."},"gameBuildVersion":{"type":"integer","description":"Specific build version number. Pin to an exact version for consistency."},"serverSize":{"type":"string","description":"Compute size for servers created by this lobby (e.g., \"small\", \"medium\", \"large\")."},"maxGameTime":{"type":"integer","description":"Maximum game duration in seconds. Server auto-stops after this time."}},"description":"Override the lobby's default server settings for this specific mode."}},"required":["teams","playersPerTeam","minPlayersPerTeam","timeout","maxPartySize"],"description":"Configuration for a matchmaking mode within a lobby. Defines team structure, player limits, and matching rules."},"description":"Named matchmaking modes. Keys are mode identifiers (e.g., \"ranked\", \"casual\"). Values define team structure and rules."}},"required":["modes"],"description":"Matchmaking configuration. Omit to create a lobby without automated matchmaking."}},"required":["id","name","enabled","timeout"],"description":"Configuration for a lobby type. Defines player limits, matchmaking rules, and server settings."},"description":"Current lobby and matchmaking configurations."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last settings update."}},"required":["port_configs","auth_config","environment_variables","pool_config","lobby_configs","updated_at"],"description":"Complete current project settings including ports, auth, environment, pool, and lobby configurations."}}}},"400":{"description":"Invalid input (e.g., duplicate port config, TLS on UDP port, invalid auth provider).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"A client key (pfclient_*) was used. Updating project settings requires a server key (pf_...).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"500":{"description":"Internal server error while updating settings.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/builds":{"get":{"operationId":"listBuilds","tags":["Builds"],"summary":"List builds","description":"Returns a paginated list of builds for your project, sorted by creation date (newest first). Soft-deleted builds are excluded. Use the `name` filter to list versions of a specific build name.","parameters":[{"schema":{"type":"string","description":"Filter builds by name (e.g., \"default\", \"beta\"). Returns all names if omitted."},"required":false,"description":"Filter builds by name (e.g., \"default\", \"beta\"). Returns all names if omitted.","name":"name","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Maximum builds per page (1–100). Defaults to 50."},"required":false,"description":"Maximum builds per page (1–100). Defaults to 50.","name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Number of builds to skip for pagination. Defaults to 0."},"required":false,"description":"Number of builds to skip for pagination. Defaults to 0.","name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated list of builds.","content":{"application/json":{"schema":{"type":"object","properties":{"builds":{"type":"array","items":{"type":"object","properties":{"build_id":{"type":"string","description":"Unique identifier for this build (UUID)."},"name":{"type":"string","description":"Build name (e.g., \"default\", \"beta\"). Multiple versions can share the same name."},"version":{"type":"number","description":"Auto-incrementing version number within this build name. The first build named \"default\" is version 1, the next is version 2, etc."},"status":{"type":"string","enum":["uploading","processing","ready","failed","deleted"],"description":"Build lifecycle status. \"uploading\": ZIP uploaded, awaiting processing. \"processing\": GitHub Actions is building the Docker image. \"ready\": build is deployable. \"failed\": processing failed (check build logs). \"deleted\": soft-deleted, no longer usable."},"build_type":{"type":"string","enum":["zip","docker_image"],"default":"zip","description":"How this build was created. \"zip\": uploaded as a ZIP archive. \"docker_image\": created from an existing Docker image URL."},"executable_path":{"type":["string","null"],"description":"Path to the game executable inside the build. Defaults to \"Server.x86_64\" if not specified during upload."},"image_url":{"type":["string","null"],"description":"Source Docker image URL (only for docker_image build type). Null for ZIP builds."},"file_manifest":{"type":["array","null"],"items":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"number"}},"required":["path","size"]},"description":"List of files in the build with their sizes in bytes. Available after processing completes."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the build was created."},"updated_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp of the last status update. Null if never updated after creation."}},"required":["build_id","name","version","status","created_at","updated_at"],"description":"Details of a game server build artifact."},"description":"Array of builds for the current page."},"total":{"type":"number","description":"Total number of builds matching the query filters."},"limit":{"type":"number","description":"Maximum number of items per page."},"offset":{"type":"number","description":"Number of items skipped (for pagination)."},"has_more":{"type":"boolean","description":"True if there are more builds beyond this page."}},"required":["builds","total","limit","offset","has_more"],"description":"Paginated list of game server builds."}}}}}}},"/api/v3/builds/upload-url":{"post":{"operationId":"createUploadUrl","tags":["Builds"],"summary":"Get presigned upload URL","description":"Generates a presigned upload URL for uploading your game server as a ZIP file. Upload flow:\n\n1. **Call this endpoint** to get a `build_id` and `upload_url`\n2. **PUT your ZIP** directly to the `upload_url` (expires in 5 minutes)\n3. **Done!** Processing starts automatically when the upload completes\n4. **Poll GET /builds/{build_id}** until status is `ready`\n\nVersions auto-increment per build name: first \"default\" build is version 1, next is version 2, etc.","parameters":[{"schema":{"type":"string","default":"default","description":"Build name (e.g., \"default\", \"beta\", \"staging\"). Versions auto-increment per name per project. Most projects use \"default\"."},"required":false,"description":"Build name (e.g., \"default\", \"beta\", \"staging\"). Versions auto-increment per name per project. Most projects use \"default\".","name":"name","in":"query"},{"schema":{"type":"string","description":"Path to your game executable inside the ZIP archive. Defaults to \"Server.x86_64\" if not specified. Examples: \"MyGame.x86_64\", \"bin/DedicatedServer\"."},"required":false,"description":"Path to your game executable inside the ZIP archive. Defaults to \"Server.x86_64\" if not specified. Examples: \"MyGame.x86_64\", \"bin/DedicatedServer\".","name":"executable_path","in":"query"}],"responses":{"201":{"description":"Presigned upload URL generated. Upload your ZIP with a PUT request to the `upload_url` within 5 minutes.","content":{"application/json":{"schema":{"type":"object","properties":{"build_id":{"type":"string","description":"Unique identifier for the newly created build. Use this ID in subsequent API calls (process, get, delete)."},"name":{"type":"string","description":"Build name as provided in the request."},"upload_url":{"type":"string","description":"Presigned R2 URL for uploading your ZIP file. Send a PUT request with your ZIP as the body. This URL expires in 5 minutes."},"message":{"type":"string","description":"Confirmation message."}},"required":["build_id","name","upload_url","message"],"description":"Presigned upload URL and build metadata. Upload your ZIP file to the upload_url with a PUT request."}}}}}}},"/api/v3/builds/docker-image":{"post":{"operationId":"createDockerImageBuild","tags":["Builds"],"summary":"Create build from Docker image","description":"Creates a build from an existing Docker image instead of uploading a ZIP. PlayFlow pulls the image and makes it available for deployment.\n\nProcessing is triggered automatically — no need to call `/process` separately. Poll `GET /builds/{build_id}` until status is `ready`.\n\nFor private registries, provide `registry_credentials` with username and password/token. Public images (Docker Hub, ghcr.io public repos) don't need credentials.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","default":"default","description":"Build name for versioning (e.g., \"default\", \"beta\"). Versions auto-increment per name per project."},"image_url":{"type":"string","minLength":1,"description":"Full Docker image URL including tag. Examples: \"ghcr.io/myorg/gameserver:v2.1.0\", \"registry.example.com/game:latest\", \"docker.io/myname/server:1.0\"."},"executable_path":{"type":"string","description":"Path to the game executable inside the Docker image. Required if your executable is not at the default location (Server.x86_64)."},"registry_credentials":{"type":"object","properties":{"username":{"type":"string","minLength":1,"description":"Registry username or access token name."},"password":{"type":"string","minLength":1,"description":"Registry password, access token, or personal access token (PAT)."}},"required":["username","password"],"description":"Credentials for private Docker registries. Not needed for public images (Docker Hub public, ghcr.io public). Required for private repos."}},"required":["image_url"],"description":"Create a build from an existing Docker image instead of uploading a ZIP. PlayFlow pulls the image and prepares it for deployment."}}}},"responses":{"202":{"description":"Docker image build created and processing started. Poll GET /builds/{build_id} for status updates.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Processing status, typically \"accepted\"."},"build_id":{"type":"string","description":"Build identifier being processed."},"message":{"type":"string","description":"Human-readable status message."}},"required":["status","build_id","message"],"description":"Confirmation that build processing has been triggered. Processing runs asynchronously via GitHub Actions (typically 1–5 minutes)."}}}},"400":{"description":"Invalid request (e.g., missing image_url, malformed registry credentials).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/builds/{build_id}":{"get":{"operationId":"getBuild","tags":["Builds"],"summary":"Get build details","description":"Returns the full details of a specific build including its status, version, and type. Use this to poll for status changes during processing (`uploading` → `processing` → `ready` or `failed`).","parameters":[{"schema":{"type":"string","description":"Unique build ID (UUID)."},"required":true,"description":"Unique build ID (UUID).","name":"build_id","in":"path"}],"responses":{"200":{"description":"Full build details.","content":{"application/json":{"schema":{"type":"object","properties":{"build_id":{"type":"string","description":"Unique identifier for this build (UUID)."},"name":{"type":"string","description":"Build name (e.g., \"default\", \"beta\"). Multiple versions can share the same name."},"version":{"type":"number","description":"Auto-incrementing version number within this build name. The first build named \"default\" is version 1, the next is version 2, etc."},"status":{"type":"string","enum":["uploading","processing","ready","failed","deleted"],"description":"Build lifecycle status. \"uploading\": ZIP uploaded, awaiting processing. \"processing\": GitHub Actions is building the Docker image. \"ready\": build is deployable. \"failed\": processing failed (check build logs). \"deleted\": soft-deleted, no longer usable."},"build_type":{"type":"string","enum":["zip","docker_image"],"default":"zip","description":"How this build was created. \"zip\": uploaded as a ZIP archive. \"docker_image\": created from an existing Docker image URL."},"executable_path":{"type":["string","null"],"description":"Path to the game executable inside the build. Defaults to \"Server.x86_64\" if not specified during upload."},"image_url":{"type":["string","null"],"description":"Source Docker image URL (only for docker_image build type). Null for ZIP builds."},"file_manifest":{"type":["array","null"],"items":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"number"}},"required":["path","size"]},"description":"List of files in the build with their sizes in bytes. Available after processing completes."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the build was created."},"updated_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp of the last status update. Null if never updated after creation."}},"required":["build_id","name","version","status","created_at","updated_at"],"description":"Details of a game server build artifact."}}}},"404":{"description":"No build found with this build_id in your project, or the build has been deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"delete":{"operationId":"deleteBuild","tags":["Builds"],"summary":"Delete a build","description":"Soft-deletes a build and cleans up its stored artifacts. The build status is set to `deleted` and it will no longer appear in list responses or be usable for starting new servers. **Requires a server key (pf_...); client keys (pfclient_*) are rejected with 403.**\n\n**Note:** Servers already running with this build are unaffected — deletion only prevents new servers from using it.","parameters":[{"schema":{"type":"string","description":"Build ID to delete."},"required":true,"description":"Build ID to delete.","name":"build_id","in":"path"}],"responses":{"200":{"description":"Build soft-deleted and artifacts cleaned up.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Result status, typically \"success\"."},"message":{"type":"string","description":"Human-readable confirmation, e.g., \"Build deleted successfully\"."}},"required":["status","message"],"description":"Confirmation that the build was soft-deleted."}}}},"403":{"description":"A client key (pfclient_*) was used. Build deletion requires a server key (pf_...).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"No build found with this build_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/builds/{build_id}/logs":{"get":{"operationId":"getBuildLogs","tags":["Builds"],"summary":"Get build processing logs","description":"Returns paginated build processing logs in chronological order. Each log entry includes a phase (e.g., download, extract, compress, upload), a message, and a severity level.\n\nUseful for debugging failed builds — check for `error` level entries. Build logs are also available for live streaming in the PlayFlow dashboard.","parameters":[{"schema":{"type":"string","description":"Build ID to fetch logs for."},"required":true,"description":"Build ID to fetch logs for.","name":"build_id","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum log entries per page (1–500). Defaults to 100."},"required":false,"description":"Maximum log entries per page (1–500). Defaults to 100.","name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Number of entries to skip for pagination. Defaults to 0."},"required":false,"description":"Number of entries to skip for pagination. Defaults to 0.","name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated build processing logs in chronological order.","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Auto-incrementing log entry ID."},"build_id":{"type":"string","description":"Build this log entry belongs to."},"phase":{"type":"string","description":"Processing phase when this log was emitted. Common phases: init, download, extract, compress, upload, pull, container, docker_build, complete, error, finalize."},"message":{"type":"string","description":"Log message content."},"level":{"type":"string","description":"Log severity: \"info\" for normal progress, \"warn\" for warnings, \"error\" for failures."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when this log entry was created."}},"required":["id","build_id","phase","message","level","created_at"],"description":"A single log entry from the build processing pipeline."},"description":"Array of build log entries in chronological order."},"total":{"type":"number","description":"Total number of log entries for this build."},"limit":{"type":"number","description":"Maximum number of entries per page."},"offset":{"type":"number","description":"Number of entries skipped (for pagination)."},"has_more":{"type":"boolean","description":"True if there are more log entries beyond this page."}},"required":["logs","total","limit","offset","has_more"],"description":"Paginated list of build processing logs. Subscribe via Supabase Realtime for live streaming."}}}},"404":{"description":"No build found with this build_id in your project.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}":{"post":{"operationId":"createLobby","tags":["Lobbies"],"summary":"Create a lobby","description":"Creates a new lobby and makes the caller the host. The caller is identified by the `x-player-id` header.\n\nThe lobby starts in `waiting` status. Other players can join via `POST /{config}/join` using the lobby ID or invite code (if the lobby is private).\n\n**Lobby config:** The `{config}` path parameter maps to a lobby configuration defined in your project settings. Each config controls timeout, server size, matchmaking modes, and custom properties. If no matching config exists, sensible defaults are used.\n\n**Player state:** Optionally pass `state` to set initial player state (ready status, team, loadout, MMR, etc.). Other players see this in the `players` array.\n\n**Private lobbies:** Set `isPrivate: true` to hide the lobby from browsing and generate an invite code.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID from project settings (e.g. \"casual\", \"ranked_2v2\")."},"required":true,"description":"Lobby configuration name or ID from project settings (e.g. \"casual\", \"ranked_2v2\").","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. This becomes the lobby host."},"required":true,"description":"Unique player identifier. This becomes the lobby host.","name":"x-player-id","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name for the lobby. Shown to other players when browsing."},"region":{"type":"string","default":"us-east","description":"Preferred region for the game server. Available: us-east, us-south, us-west, eu-west, eu-north, ap-south, etc."},"maxPlayers":{"type":"integer","minimum":1,"maximum":100,"default":2,"description":"Maximum number of players (1–100). Defaults to 2."},"isPrivate":{"type":"boolean","default":false,"description":"If true, the lobby is hidden from browse and an invite code is generated."},"allowLateJoin":{"type":"boolean","default":true,"description":"If true, players can join after the game has started."},"settings":{"type":"object","additionalProperties":{},"default":{},"description":"Custom game settings (map, mode, difficulty, etc.). Passed to the game server as custom_data."},"state":{"type":"object","additionalProperties":{},"description":"Initial player state for the host (ready status, team, loadout, etc.)."},"forceFresh":{"type":"boolean","description":"When true, delete any existing lobby this player owns in this config before creating the new one. Useful for \"start a fresh session\" flows where the host may be reconnecting from a prior session."}},"required":["name"],"description":"Create a new lobby. The caller (x-player-id) becomes the host."}}}},"responses":{"201":{"description":"Lobby created. The caller is the host. Share the `id` or `code` for others to join.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header or invalid request body.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Player is already in a lobby for this config.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"get":{"operationId":"browseLobbies","tags":["Lobbies"],"summary":"Browse lobbies","description":"Returns a paginated list of public lobbies for the given config. Private (invite-only) lobbies are excluded.\n\nUse `status` to filter by lobby state (`waiting`, `in_queue`, `in_game`). Use `region` to filter by region.\n\n**`includePrivate`:** Set to `true` to also return private lobbies. Honored ONLY when authenticated with a server key (`pf_*`) — the project owner's own dashboard/admin view. Client keys (`pfclient_*`) always get public-only results, so invite codes stay meaningful.\n\n**No x-player-id required.** This endpoint is designed for lobby browsers and server-side matchmaking systems.\n\nResults are ordered by creation time (newest first).","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Filter by region."},"required":false,"description":"Filter by region.","name":"region","in":"query"},{"schema":{"type":"string","description":"Filter by status (waiting, in_queue, in_game)."},"required":false,"description":"Filter by status (waiting, in_queue, in_game).","name":"status","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Max results per page (1–100). Defaults to 50."},"required":false,"description":"Max results per page (1–100). Defaults to 50.","name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Pagination offset. Defaults to 0."},"required":false,"description":"Pagination offset. Defaults to 0.","name":"offset","in":"query"},{"schema":{"type":"string","default":false,"description":"Include private (invite-only) lobbies. Honored ONLY for server keys (pf_*) — the project owner's own admin/dashboard view. Ignored for client keys, so public browse never leaks private lobbies."},"required":false,"description":"Include private (invite-only) lobbies. Honored ONLY for server keys (pf_*) — the project owner's own admin/dashboard view. Ignored for client keys, so public browse never leaks private lobbies.","name":"includePrivate","in":"query"}],"responses":{"200":{"description":"Paginated list of public lobbies matching the filters.","content":{"application/json":{"schema":{"type":"object","properties":{"lobbies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"config":{"type":"string","description":"Lobby config name."},"name":{"type":"string","description":"Display name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"hasServer":{"type":"boolean","description":"True if a game server is associated with this lobby. Use `GET /{config}/me` for details."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","config","name","status","host","maxPlayers","currentPlayers","region","allowLateJoin","hasServer","createdAt","updatedAt"],"description":"Summary shape for lobby browse. Join a lobby to see full state."},"description":"Lobbies matching the query."},"total":{"type":"number","description":"Total matching lobbies."},"limit":{"type":"number","description":"Page size."},"offset":{"type":"number","description":"Page offset."},"hasMore":{"type":"boolean","description":"True if more results exist beyond this page."}},"required":["lobbies","total","limit","offset","hasMore"],"description":"Paginated list of public lobbies (summary projection)."}}}}}}},"/api/v3/lobbies/{config}/join":{"post":{"operationId":"joinLobby","tags":["Lobbies"],"summary":"Join a lobby","description":"Joins an existing lobby by its ID or invite code. The caller is identified by `x-player-id`.\n\n**By ID:** Set `lobbyId` to the UUID from the browse or create response.\n\n**By invite code:** Set `code` to the invite code shared by the host (e.g. `\"MEOW-42\"`).\n\nExactly one of `lobbyId` or `code` must be provided.\n\n**Player state:** Optionally pass `state` to set initial player state on join.\n\n**Errors:**\n- `404` — Lobby not found or invalid invite code\n- `400` — Lobby is full or not accepting players\n- `409` — Player is already in a lobby for this config","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier for the joining player."},"required":true,"description":"Unique player identifier for the joining player.","name":"x-player-id","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lobbyId":{"type":"string","format":"uuid","description":"Join a specific lobby by its ID."},"code":{"type":"string","description":"Join a lobby by its invite code (e.g. \"MEOW-42\")."},"state":{"type":"object","additionalProperties":{},"description":"Initial player state on join (team, loadout, etc.)."}},"description":"Join an existing lobby by ID or invite code."}}}},"responses":{"200":{"description":"Successfully joined the lobby. The response includes the full lobby state with all players.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Lobby is full, not accepting players, or missing x-player-id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"Lobby not found or invalid invite code.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Player is already in a lobby for this config.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me":{"get":{"operationId":"getMyLobby","tags":["Lobbies"],"summary":"Get my lobby","description":"Returns the full state of the lobby the caller is currently in. The caller is identified by `x-player-id`.\n\nIf the lobby is in `in_queue` status (matchmaking active), the response includes live `matchmaking.queueStats` with the number of players searching, lobbies in queue, and average wait time.\n\n**Use this to poll lobby state.** For real-time updates, use `GET /{config}/me/events` (SSE) instead.\n\nReturns `404` if the player is not currently in a lobby.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Full lobby state including players, server info, and matchmaking status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby. Create one or join an existing lobby first.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"patch":{"operationId":"updateMyState","tags":["Lobbies"],"summary":"Update my player state","description":"Updates the caller's player state within the lobby. State is merged with existing state (existing keys are preserved unless explicitly overwritten).\n\nUse this to signal ready status, select a team, set a loadout, report MMR, or store any per-player data that the host or game server needs.\n\n**Example states:**\n- `{ \"ready\": true }`\n- `{ \"team\": \"blue\", \"role\": \"healer\" }`\n- `{ \"mmr\": 1500, \"loadout\": \"sniper\" }`\n\nAll players in the lobby see state changes in real-time via the SSE stream (`GET /{config}/me/events`).","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"object","additionalProperties":{},"description":"Merge into your player state. Existing keys are preserved unless overwritten."}},"required":["state"],"description":"Update your player state in the lobby (ready status, team, loadout, MMR, etc.)."}}}},"responses":{"200":{"description":"Player state updated. Returns the full lobby state.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header or invalid request body.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"delete":{"operationId":"leaveLobby","tags":["Lobbies"],"summary":"Leave a lobby","description":"Removes the caller from their current lobby. If the caller is the host, host duty is transferred to the next player. If the caller is the last player, the lobby is deleted.\n\n**Response:**\n- If the lobby still has players: returns the updated lobby state.\n- If the lobby was deleted (last player left): returns `{ \"status\": \"lobby_deleted\" }`.\n\nPlayers who leave while matchmaking is active are removed from the queue.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Left the lobby. Returns updated lobby state, or `{ status: \"lobby_deleted\" }` if you were the last player.","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."},{"type":"object","properties":{"status":{"type":"string","enum":["lobby_deleted"],"description":"Returned when the last player leaves and the lobby is cleaned up."}},"required":["status"]}]}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/settings":{"patch":{"operationId":"updateLobbySettings","tags":["Lobbies"],"summary":"Update lobby settings","description":"**Host only.** Updates the lobby's shared settings (name, max players, region, game settings, etc.).\n\nAll fields are optional. Omitted fields keep their current values. The `settings` object is merged with the existing settings (existing keys are preserved unless overwritten).\n\n**Example:** Change the map and toggle private mode:\n```json\n{ \"isPrivate\": true, \"settings\": { \"map\": \"desert\" } }\n```\n\nAll players see setting changes in real-time via the SSE stream.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"New lobby name."},"maxPlayers":{"type":"integer","minimum":1,"maximum":100,"description":"New max players."},"isPrivate":{"type":"boolean","description":"Toggle private mode."},"allowLateJoin":{"type":"boolean","description":"Toggle late join."},"region":{"type":"string","description":"Change preferred region."},"settings":{"type":"object","additionalProperties":{},"description":"Merge into lobby settings."}},"description":"Update lobby settings. Only the host can do this."}}}},"responses":{"200":{"description":"Settings updated. Returns the full lobby state.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header or invalid request body.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"Only the host can update lobby settings.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/players/{playerId}":{"delete":{"operationId":"kickPlayer","tags":["Lobbies"],"summary":"Kick a player","description":"**Host only.** Removes a player from the lobby by their player ID.\n\nThe kicked player receives a `lobby_updated` event via SSE showing they are no longer in the lobby. The host cannot kick themselves (use `DELETE /{config}/me` to leave instead).\n\nIf the kicked player is the last non-host player, the lobby continues with just the host.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Player ID of the player to kick."},"required":true,"description":"Player ID of the player to kick.","name":"playerId","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Player kicked. Returns the updated lobby state.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"Only the host can kick players.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby, or the target player is not in the lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/start":{"post":{"operationId":"startGame","tags":["Lobbies"],"summary":"Start the game","description":"**Host only.** Starts the game for the lobby. This triggers a three-step process:\n\n1. Lobby status transitions to `starting`\n2. A game server is provisioned (uses the build and compute size from the lobby config)\n3. Lobby status transitions to `in_game` with server connection details\n\nThe response includes `server.ports` with host and port for game client connections. All players receive a `lobby_updated` event via SSE with the server details.\n\n**Server provisioning** typically takes 10-30 seconds (or ~5 seconds with pool servers). The lobby stays in `starting` status until the server is ready.\n\nIf server provisioning fails, the lobby rolls back to `waiting` status.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Game started. The `server` field contains connection details (instanceId, ports). Status is `in_game`.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header or invalid lobby state for starting.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"Only the host can start the game.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Game is already running or lobby is in an incompatible state.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/matchmaking":{"post":{"operationId":"startMatchmaking","tags":["Lobbies"],"summary":"Start matchmaking","description":"**Host only.** Enters the lobby into the matchmaking queue for the specified mode.\n\nThe `mode` must be a matchmaking mode defined in the lobby config (e.g. `\"ranked_2v2\"`, `\"casual_ffa\"`).\n\n**How it works:**\n1. Lobby status transitions to `in_queue`\n2. The matchmaking system immediately attempts to find compatible opponents\n3. If no immediate match, the lobby waits in the queue\n4. When matched, status transitions to `match_found` and a game server is started\n\n**Queue stats:** The response includes `matchmaking.queueStats` with the number of players searching and average wait time. Players receive live updates via SSE (`queue_stats` events every 10 seconds).\n\n**Cancel:** Use `DELETE /{config}/me/matchmaking` to leave the queue and return to `waiting`.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","minLength":1,"description":"Matchmaking mode name from the lobby config (e.g. \"ranked_2v2\", \"casual_ffa\")."}},"required":["mode"],"description":"Start matchmaking. The lobby enters the queue and is matched with compatible opponents."}}}},"responses":{"200":{"description":"Matchmaking started. Status is `in_queue`. The `matchmaking` field includes mode, startedAt, and queueStats.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id, invalid mode, or lobby is not in a valid state for matchmaking.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"Only the host can start matchmaking.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Lobby is already in matchmaking or a game is running.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"delete":{"operationId":"cancelMatchmaking","tags":["Lobbies"],"summary":"Cancel matchmaking","description":"**Host only.** Removes the lobby from the matchmaking queue and returns to `waiting` status.\n\nThis clears the `matchmaking` field and stops queue stat updates via SSE.\n\n**Note:** If a match has already been found (`match_found` status), cancellation may not be possible and the game will proceed.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Matchmaking cancelled. Lobby returns to `waiting` status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Lobby is not in matchmaking queue or you are not the host.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/end-match":{"post":{"operationId":"endMatch","tags":["Lobbies"],"summary":"End the current match","description":"**Host only.** Ends the current in-game match and returns the lobby to `waiting` status so the same players can start another round (or queue again).\n\n**What it does:**\n1. Atomically transitions lobby status `in_game` → `waiting`\n2. Clears `server`, `matchId`, and `matchmaking` fields\n3. Stops the associated game server best-effort\n4. Preserves players, settings, and lobby metadata\n\nAll players receive a `lobby_updated` SSE event with the reset state.\n\n**When to use:** Rematch flows, party-persistent games, or any scenario where the host wants to reuse the lobby without forcing players to rejoin.\n\n**Natural server exits:** If your game server shuts down on its own, the lobby transitions to `completed` and is cleaned up. Use `end-match` only when the host explicitly wants to rematch with the same group.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier. Must be the lobby host."},"required":true,"description":"Unique player identifier. Must be the lobby host.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Match ended. Lobby is back to `waiting` status with the same players. `server` and `matchmaking` fields are null.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"403":{"description":"Only the host can end the match.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Lobby is not currently in a game.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/confirm-match":{"post":{"operationId":"confirmMatch","tags":["Lobbies"],"summary":"Confirm a found match","description":"Accepts a match while the lobby is in `match_found` state (CS2-style 'Accept Match' flow).\n\n**Who can call:** Any player in the lobby. Confirmation is per-lobby (parties confirm together).\n\n**Behavior:** Records the confirmation. When every lobby in the match has confirmed, the engine automatically launches the game server and transitions every lobby to `in_game`.\n\n**Match-confirmation requires the mode to be configured with `matchConfirmation.enabled: true`.** If a mode does NOT enable confirmation, matches transition directly from queue to `in_game` and this endpoint returns `409`.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Confirmation recorded. If this was the last required confirmation, `status` will be `in_game`. Otherwise `status` remains `match_found` with `matchmaking.confirmation.confirmed: true`.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Lobby is not in `match_found` state.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"delete":{"operationId":"declineMatch","tags":["Lobbies"],"summary":"Decline a found match","description":"Declines a match. **Cancels the match for every participating lobby** — all of them return to `waiting`. Players explicitly re-queue when ready.\n\n**Who can call:** Any player in any lobby of the match.\n\n**Compare to `cancel-matchmaking`:** Cancel-matchmaking exits the queue entirely (returns to `waiting`). Decline-match also returns to `waiting` but cancels the match for every participating lobby, not just yours.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Match declined. Lobby is back in `waiting`.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"409":{"description":"Lobby is not in `match_found` state.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/heartbeat":{"post":{"operationId":"heartbeat","tags":["Lobbies"],"summary":"Send a player heartbeat","description":"Updates the caller's `lastHeartbeat` timestamp. Only needed if the lobby config has `heartbeat: true` enabled — in that case, players whose heartbeat is older than `heartbeatTimeout` seconds are evicted by the `lobby-sweep` cron.\n\nUse this for polling clients where the SSE connection (which acts as an implicit heartbeat) isn't available. Send every few seconds (well under `heartbeatTimeout`).","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"Heartbeat recorded.","content":{"application/json":{"schema":{"type":"object","properties":{"lastHeartbeat":{"type":"string","description":"ISO 8601 timestamp of this heartbeat."}},"required":["lastHeartbeat"]}}}},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/me/events":{"get":{"operationId":"lobbyEvents","tags":["Lobbies"],"summary":"Subscribe to lobby events (SSE)","description":"Opens a Server-Sent Events (SSE) stream for real-time lobby updates. The caller is identified by `x-player-id`.\n\n**Event types:**\n\n| Event | Description | Frequency |\n|-|-|-|\n| `connected` | Initial event with full lobby state | Once on connect |\n| `lobby_updated` | Lobby state changed (player joined/left, settings changed, game started, etc.) | On every change |\n| `lobby_deleted` | Lobby was deleted (all players left or timeout) | Once, then stream closes |\n| `queue_stats` | Live matchmaking queue statistics | Every 10s while `in_queue` |\n| `ping` | Keep-alive heartbeat | Every 30s |\n\n**Usage (JavaScript):**\n```javascript\nconst es = new EventSource('/api/v3/lobbies/casual/me/events', {\n  headers: { 'api-key': 'pf_...', 'x-player-id': 'player123' }\n});\nes.addEventListener('lobby_updated', (e) => {\n  const lobby = JSON.parse(e.data);\n  updateUI(lobby);\n});\n```\n\n**Connection lifecycle:** The stream stays open until the client disconnects, the lobby is deleted, or the player is kicked. Reconnect automatically on disconnect.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","description":"Unique player identifier."},"required":true,"description":"Unique player identifier.","name":"x-player-id","in":"header"}],"responses":{"200":{"description":"SSE stream opened. Events are sent as `text/event-stream`."},"400":{"description":"Missing x-player-id header.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"You are not in a lobby.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}},"/api/v3/lobbies/{config}/{id}":{"delete":{"operationId":"adminDeleteLobby","tags":["Lobbies"],"summary":"Delete a lobby by ID (admin)","description":"Force-deletes a lobby by ID. Removes all players and cancels any active matchmaking. Unlike `DELETE /{config}/me` (which makes the caller leave), this bypasses ownership checks — use from server-side admin tools only. **Requires a server key (pf_...); client keys (pfclient_*) are rejected with 403.**\n\n**Behavior:**\n- Stops any running game server best-effort\n- Deletes the lobby row (cascades all player and matchmaking state stored on it)\n- Connected SSE clients receive a `lobby_deleted` event and disconnect cleanly\n\nReturns `404` if no lobby with the given ID exists in the caller's project for the specified config.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","format":"uuid","description":"Lobby ID (UUID)."},"required":true,"description":"Lobby ID (UUID).","name":"id","in":"path"}],"responses":{"200":{"description":"Lobby was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["lobby_deleted"],"description":"Confirmation that the lobby was deleted."},"id":{"type":"string","format":"uuid","description":"The ID of the deleted lobby."}},"required":["status","id"]}}}},"403":{"description":"A client key (pfclient_*) was used. This admin endpoint requires a server key (pf_...).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"No lobby found with this ID for the given config.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}},"get":{"operationId":"getLobbyById","tags":["Lobbies"],"summary":"Get lobby by ID","description":"Returns the full state of a specific lobby by its ID. No `x-player-id` required. **Requires a server key (pf_...); client keys (pfclient_*) are rejected with 403.**\n\nUse this for admin dashboards, debugging, and server-side systems that need to inspect lobby state without being a participant.\n\n**Note:** This endpoint matches any `{id}` path segment under `{config}`, so it must be registered last to avoid shadowing other routes.","parameters":[{"schema":{"type":"string","description":"Lobby configuration name or ID."},"required":true,"description":"Lobby configuration name or ID.","name":"config","in":"path"},{"schema":{"type":"string","format":"uuid","description":"Lobby ID (UUID)."},"required":true,"description":"Lobby ID (UUID).","name":"id","in":"path"}],"responses":{"200":{"description":"Full lobby state.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Lobby ID."},"code":{"type":["string","null"],"description":"Invite code for joining (null if private mode is disabled)."},"config":{"type":"string","description":"Lobby config name."},"status":{"type":"string","enum":["waiting","in_queue","starting","matched","match_found","in_game"],"description":"Current lobby status."},"host":{"type":"string","description":"Player ID of the host."},"maxPlayers":{"type":"number","description":"Maximum player capacity."},"currentPlayers":{"type":"number","description":"Current number of players."},"region":{"type":["string","null"],"description":"Preferred game server region."},"isPrivate":{"type":"boolean","description":"Whether the lobby is hidden from browsing."},"allowLateJoin":{"type":"boolean","description":"Whether players can join mid-game."},"settings":{"type":"object","additionalProperties":{},"description":"Custom game settings."},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Player's unique identifier."},"state":{"type":"object","additionalProperties":{},"description":"Player's custom state (ready, team, loadout, MMR, etc.)."},"isHost":{"type":"boolean","description":"True if this player is the lobby host."}},"required":["id","state","isHost"],"description":"A player in the lobby with their current state."},"description":"Players in the lobby with their state."},"server":{"type":["object","null"],"properties":{"instance_id":{"type":"string","description":"Unique identifier for this server instance (UUID). Use this to reference the server in all other API calls."},"name":{"type":"string","description":"Display name of the server, as provided when starting."},"status":{"type":"string","enum":["launching","running","stopped"],"description":"Current server status. \"launching\": machine is being created and game is starting up. \"running\": game is ready and accepting connections. \"stopped\": server has been shut down."},"network_ports":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Port name matching the port_configs entry (e.g., \"game_udp\")."},"internal_port":{"type":"number","description":"Port number the game server listens on inside the container."},"external_port":{"type":"number","description":"Public port number that players connect to from outside."},"protocol":{"type":"string","enum":["udp","tcp"],"description":"Network protocol for this port."},"host":{"type":"string","description":"Hostname or IP address players connect to. For UDP: a proxy IPv4 address. For TCP: \"connect.computeflow.cloud\" (Fly.io L7 routing)."},"tls_enabled":{"type":"boolean","description":"Whether TLS termination is enabled for this port (TCP only)."}},"required":["name","internal_port","external_port","protocol","host","tls_enabled"],"description":"A network port mapping showing how external traffic reaches your game server. Use host:external_port as the connection address for game clients."},"description":"List of allocated network ports with connection details. Give host:external_port to game clients for connecting. Empty if the server has stopped."},"startup_args":{"type":["string","null"],"description":"Command-line arguments passed to the game executable. Null if none were provided."},"service_type":{"type":"string","enum":["match_based","persistent_world"],"description":"Server type. \"match_based\" (default): short-lived servers for individual matches. \"persistent_world\": long-running servers for persistent game worlds."},"compute_size":{"type":"string","description":"Machine size this server is running on (e.g., \"small\", \"medium\", \"dedicated-large\")."},"region":{"type":"string","description":"Region where this server is deployed (e.g., \"us-east\", \"eu-west\")."},"version_tag":{"type":"string","description":"Build name this server was started with (e.g., \"default\", \"beta\")."},"version":{"type":["number","null"],"description":"Build version number this server is running. Null if no specific version was resolved."},"started_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server transitioned to \"running\" status (game ports opened). Null if still launching or never reached running state."},"stopped_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when the server was stopped. Null if the server is still active."},"auto_restart":{"type":"boolean","description":"Whether the game process auto-restarts on crash (max 10 retries)."},"custom_data":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary metadata attached to this server. Null if no custom data was provided."},"ttl":{"type":["number","null"],"description":"Time-to-live in seconds. The server auto-stops after this duration from start. Null if no TTL was set (server runs until explicitly stopped)."},"is_pool_server":{"type":"boolean","description":"Whether this server was claimed from the pre-provisioned pool. Pool servers have faster startup times (~5s vs ~30s)."},"pool_claimed_at":{"type":["string","null"],"format":"date-time","description":"ISO 8601 timestamp when this pool server was claimed for use. Null for non-pool servers or unclaimed pool machines."},"match_id":{"type":["string","null"],"description":"Match identifier for matchmaking integration. Auto-generated if not provided at start time."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the server instance was created in the database."},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last update to this server record."}},"required":["instance_id","name","status","network_ports","startup_args","service_type","compute_size","region","version_tag","version","started_at","stopped_at","auto_restart","custom_data","ttl","is_pool_server","pool_claimed_at","match_id","created_at","updated_at"],"description":"Game server info (null when no game running)."},"matchmaking":{"type":["object","null"],"properties":{"mode":{"type":"string","description":"Active matchmaking mode name."},"startedAt":{"type":"string","description":"ISO 8601 timestamp when matchmaking started."},"queueStats":{"type":["object","null"],"properties":{"playersSearching":{"type":"number","description":"Total players currently in the matchmaking queue for this mode."},"lobbiesInQueue":{"type":"number","description":"Number of lobbies waiting for a match."},"avgWaitSeconds":{"type":"number","description":"Average wait time in seconds for lobbies currently in queue."}},"required":["playersSearching","lobbiesInQueue","avgWaitSeconds"],"description":"Live queue stats. Updated via SSE while searching."},"confirmation":{"type":["object","null"],"properties":{"deadline":{"type":"string","description":"ISO 8601 deadline. The match cancels if any lobby hasn't confirmed by then."},"confirmed":{"type":"boolean","description":"True if THIS lobby has confirmed."}},"required":["deadline","confirmed"],"description":"Set when status=match_found and players are accepting/declining."}},"required":["mode","startedAt","queueStats","confirmation"],"description":"Matchmaking info (null when not searching)."},"matchmakingError":{"type":["string","null"],"description":"Set when a queue entry was failed after repeated server-create failures. Explains why the match never formed; the lobby is returned to `waiting` so the player can retry."},"createdAt":{"type":"string","description":"ISO 8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 last update timestamp."}},"required":["id","code","config","status","host","maxPlayers","currentPlayers","region","isPrivate","allowLateJoin","settings","players","server","matchmaking","createdAt","updatedAt"],"description":"Full lobby state including players, server, and matchmaking info."}}}},"403":{"description":"A client key (pfclient_*) was used. This admin endpoint requires a server key (pf_...).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}},"404":{"description":"No lobby found with this ID.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message describing what went wrong."},"detail":{"type":"string","description":"Additional error context. May contain the same text as error or more specific technical details."},"status":{"type":"number","description":"HTTP status code (matches the response status code)."}},"required":["error","status"],"description":"Standard error response returned by all API endpoints on failure."}}}}}}}},"webhooks":{}}