Documentation Index
Fetch the complete documentation index at: https://docs.playflowcloud.com/llms.txt
Use this file to discover all available pages before exploring further.
A lobby progresses through several states during its lifetime. Understanding the lifecycle helps you design your game’s UI and handle every edge case cleanly.
Status Flow
Lobbies never dead-end. They either stay alive (rematch-ready) or get deleted entirely. There’s no “completed” terminal state.
waiting ─────→ in_queue ─────→ matched ─────→ in_game ─────→ waiting
│ │ │ ↑ (rematch-ready)
│ │ │ │
│ │ └──→ match_found (if matchConfirmation enabled)
│ │ │
│ │ └──→ in_game (all confirm)
│ │ └──→ waiting (decline / timeout)
│ │
│ └──→ waiting (timeout / cancel / no opponents)
│
└──→ starting ─────→ in_game (host started game directly)
│
└──→ waiting (server create failed)
| Status | Meaning |
|---|
waiting | Open lobby, accepting players. The default resting state. |
in_queue | Matchmaking active, searching for opponents |
starting | Host triggered direct start, server provisioning |
matched | Claimed for a match, server being created |
match_found | Match proposed, waiting for player POST /me/confirm-match (only if matchConfirmation.enabled) |
in_game | Game server running, players connected |
Self-healing lobbies. When the game server stops — whether the host clicks “End Match”, the server crashes, or its TTL expires — the lobby automatically returns to waiting with all players retained. There is no completed status. Players can queue or start another match immediately.
Creating a Lobby
POST /v3/lobbies/{config}
x-player-id: host_player
Content-Type: application/json
{
"name": "Friday Night CTF",
"maxPlayers": 8,
"region": "us-east",
"isPrivate": false,
"allowLateJoin": true,
"settings": { "map": "forest", "mode": "ctf" },
"state": { "ready": true }
}
The creator becomes the host. An invite code is generated automatically (even for public lobbies, so friends can share it).
| Field | Required | Default | Notes |
|---|
name | Yes | - | Display name |
maxPlayers | No | 2 | 1–100 |
region | No | us-east | Game server region |
isPrivate | No | false | Hide from GET /v3/lobbies/{config} browse |
allowLateJoin | No | true | Can players join after game starts? |
settings | No | {} | Game-specific data passed to the server |
state | No | null | Host’s initial player state |
The host can only be in one lobby at a time per config. Trying to create a second returns 409 Already in another lobby.
Fresh lobby on reconnect
Set forceFresh: true on create to atomically replace a lobby the caller already hosts in this config. The backend deletes the old lobby (stopping its game server best-effort) and then creates the new one — all in a single call. It’s a no-op when the player has no prior lobby, so it’s safe to always pass on reconnect. This replaces the old 3-call workaround (TryReconnect → detect stale host lobby → DeleteLobby → CreateLobby) with one request.
POST /v3/lobbies/{config}
x-player-id: host_player
Content-Type: application/json
{
"name": "Friday Night CTF",
"maxPlayers": 8,
"forceFresh": true
}
Only lobbies the caller hosts are force-deleted. If the caller is merely a guest in someone else’s lobby, the create still returns 409 Already in another lobby — they need to leave that one first.
Joining a Lobby
Two ways: by invite code or by lobby ID.
By Invite Code
By Lobby ID
POST /v3/lobbies/{config}/join
x-player-id: new_player
{
"code": "MEOW-42",
"state": { "team": "blue" }
}
Best for “friend shares a code” flow. Works for both public and private lobbies.POST /v3/lobbies/{config}/join
x-player-id: new_player
{
"lobbyId": "a1b2c3d4-...",
"state": { "team": "blue" }
}
Best when the player browsed public lobbies and clicked one.
Join Errors
| Status | Error | Cause |
|---|
404 | Lobby not found | Wrong ID/code |
400 | Lobby is full | currentPlayers >= maxPlayers |
400 | Lobby is not accepting players | status: in_game and allowLateJoin: false |
409 | Already in another lobby | Player is in a different lobby in the same config |
Joining is idempotent — if the player is already in the lobby, it returns the lobby unchanged (no error).
Updating Player State
Every player can update their own state. Use PATCH /me:
PATCH /v3/lobbies/{config}/me
x-player-id: my_player
{
"state": {
"ready": true,
"character": "wizard",
"mmr": 1450
}
}
State merges, not replaces. Existing keys are preserved unless your update overwrites them. To clear a key, set it to null.
State flows automatically into matchmaking_data when the lobby queues, so fields like mmr are used by matchmaking rules.
Host Actions
Only the host can perform these actions. Non-hosts get 403 Not host.
Update Lobby Settings
PATCH /v3/lobbies/{config}/me/settings
x-player-id: host_player
{
"name": "New Name",
"maxPlayers": 16,
"isPrivate": true,
"region": "eu-west",
"settings": { "map": "desert" }
}
Every field is optional — only include what you want to change.
Kick a Player
DELETE /v3/lobbies/{config}/me/players/{playerId}
x-player-id: host_player
The kicker is the host (from x-player-id). The {playerId} in the path is the target.
Hosts can’t kick themselves. Use DELETE /me to leave (which auto-transfers host).
Start the Game Directly
Skip matchmaking and launch a server immediately with the current players:
POST /v3/lobbies/{config}/me/start
x-player-id: host_player
The response has server.status: "launching". Within ~10-30s (or ~5s with pool servers), status transitions to running and clients can connect.
End the Match (Rematch)
POST /v3/lobbies/{config}/me/end-match
x-player-id: host_player
The server stops, the lobby returns to waiting, and all players stay in the lobby with the same invite code and settings. Great for “Play Again” buttons.
If the game server stops on its own (crashes, hits TTL, or your game logic exits cleanly), the lobby auto-heals to waiting with the same effect — no call needed. POST /me/end-match is just the explicit, host-triggered version.
Leaving
Any player can leave at any time:
DELETE /v3/lobbies/{config}/me
x-player-id: my_player
What happens depends on who leaves:
| Scenario | Behavior |
|---|
| Regular player leaves | Removed from players, currentPlayers decrements |
| Host leaves (with others remaining) | Host auto-transfers to next player (default) |
| Last player leaves | Lobby is deleted, returns {"status": "lobby_deleted"} |
| Player leaves during matchmaking | Matchmaking auto-cancels, lobby returns to waiting |
Host leave behavior
What happens when the host leaves is controlled per lobby config via hostLeaveBehavior:
| Value | Behavior |
|---|
"promote" (default) | The next player in the lobby becomes the new host. Remaining players stay put with the same invite code, settings, and state. |
"delete" | The entire lobby is destroyed. Every remaining player receives a lobby_deleted SSE event on /me/events and must create or join a new lobby. |
When to use each:
"promote" — persistent rooms, squad play, party-based games where the lobby should survive individual churn. This is the default for every lobby config.
"delete" — one-shot private rooms, pickup games, host-authored sessions where the host’s presence IS the room. Pairs well with isPrivate: true invite-only lobbies.
Non-host players leaving never triggers delete — only the host walking out can collapse the lobby under "delete".
Example config JSON:
{
"id": "private-squad",
"name": "Private Squad Room",
"enabled": true,
"timeout": 300,
"hostLeaveBehavior": "delete",
"inviteCodeConfig": {
"type": "alphanumeric",
"length": 6,
"prefix": "SQD-"
},
"serverSettings": { "serverSize": "small" }
}
When hostLeaveBehavior: "delete" fires, any game server associated with the lobby is stopped best-effort — same cleanup path as the admin delete endpoint.
Browsing Lobbies
Anyone (even without a player-id) can browse public lobbies in a config:
GET /v3/lobbies/{config}?region=us-east&status=waiting&limit=20
Returns a paginated list. Private lobbies (isPrivate: true) are excluded automatically.
Query params:
| Param | Purpose |
|---|
region | Filter by region |
status | Filter by lobby status |
limit | Page size (default 50, max 100) |
offset | Pagination offset |
Cleanup & Timeouts
Lobbies don’t stick around forever. The PlayFlow lobby-sweep cron runs continuously and handles:
- Expired waiting lobbies — deleted after
timeout seconds (default 5 min) with no activity
- Stale in-game lobbies — in-game lobbies past 2 hours are cleaned up
- Heartbeat expiry — if
heartbeat: true is enabled in config, players that stop sending heartbeats are removed
- Matchmaking timeout — lobbies in
in_queue past the mode’s timeout drop back to waiting
For most games, you don’t need to send heartbeats. The SSE connection itself is the heartbeat — as long as the player is connected to /me/events, they’re alive. Only enable heartbeat in config if your players use HTTP polling instead of SSE.
Heartbeat
Set heartbeat: true on a lobby config to require connected players to ping the server periodically. Players who go silent get evicted by the lobby-sweep cron.
{
"heartbeat": true,
"heartbeatTimeout": 30
}
heartbeat — when true, every player must send heartbeats
heartbeatTimeout — seconds before a player is considered stale. Any player whose last heartbeat is older than this is removed on the next sweep.
When you need it: HTTP-polling clients where the SSE connection at /me/events isn’t available. Mobile background states, low-resource IoT clients, or environments that block long-lived connections.
SSE is the better alternative. If your client can open /me/events, the stream itself is the liveness signal — see Real-time Events — and you can leave heartbeat off.
Admin Delete
Force-delete any lobby by ID. Removes all players and cancels any active matchmaking — regardless of who created the lobby.
curl -X DELETE https://api.computeflow.cloud/api/v3/lobbies/{config}/{id} \
-H "api-key: pf_your_server_key"
- No
x-player-id required. This is a server-side admin operation.
{config} — the lobby config name (e.g. "ranked_2v2").
{id} — the lobby UUID to delete.
Response (200):
{ "status": "lobby_deleted", "id": "a3f1…-e8c2" }
404 if no lobby with that ID exists in your project.
Connected SDK clients in that lobby receive a lobby_deleted SSE event and disconnect cleanly. In contrast, DELETE /{config}/me is “leave lobby” — it only removes the caller, and when the caller is the host a new host is promoted automatically. Use admin delete when you actually mean destroy.
Good uses: dashboard admin actions, anti-cheat or moderation tooling, periodic cleanup for lobbies your app determines are abandoned.
Invite Codes
inviteCodeConfig on a lobby config controls the codes PlayFlow generates when a lobby is created. Every new lobby (public and private) gets a code so friends can join by sharing it.
{
"inviteCodeConfig": {
"type": "alphanumeric",
"length": 6,
"prefix": "PLAY-"
}
}
| Field | Values | Notes |
|---|
type | numeric, alphanumeric, alphabetic | Character set for the generated suffix |
length | 1–16 | Length of the generated suffix (excluding prefix) |
prefix | any string | Optional. Prepended verbatim to the generated code. |
With the example above a lobby might get the code PLAY-K7Q2F1. Players then join with:
POST /v3/lobbies/{config}/join
{ "inviteCode": "PLAY-K7Q2F1" }
Error Codes
| HTTP | Code | Meaning |
|---|
400 | LOBBY_FULL | Capacity exceeded |
400 | LOBBY_NOT_ACCEPTING | In-game with no late-join |
400 | INVALID_ACTION | E.g., host trying to self-kick |
403 | NOT_HOST | Non-host tried a host-only action |
404 | LOBBY_NOT_FOUND | Lobby doesn’t exist |
404 | PLAYER_NOT_FOUND | Player isn’t in the lobby |
409 | ALREADY_IN_LOBBY | Player is already in another lobby |
409 | GAME_ALREADY_RUNNING | Started a game that’s already in progress |
409 | INVALID_STATE | Wrong status for this operation |
All errors follow the same format:
{
"error": "Lobby is full",
"detail": "Lobby is full (4 of 4)",
"status": 400
}