Skip to main content

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)
StatusMeaning
waitingOpen lobby, accepting players. The default resting state.
in_queueMatchmaking active, searching for opponents
startingHost triggered direct start, server provisioning
matchedClaimed for a match, server being created
match_foundMatch proposed, waiting for player POST /me/confirm-match (only if matchConfirmation.enabled)
in_gameGame 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).
FieldRequiredDefaultNotes
nameYes-Display name
maxPlayersNo21–100
regionNous-eastGame server region
isPrivateNofalseHide from GET /v3/lobbies/{config} browse
allowLateJoinNotrueCan players join after game starts?
settingsNo{}Game-specific data passed to the server
stateNonullHost’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 → DeleteLobbyCreateLobby) 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.
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.

Join Errors

StatusErrorCause
404Lobby not foundWrong ID/code
400Lobby is fullcurrentPlayers >= maxPlayers
400Lobby is not accepting playersstatus: in_game and allowLateJoin: false
409Already in another lobbyPlayer 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:
ScenarioBehavior
Regular player leavesRemoved from players, currentPlayers decrements
Host leaves (with others remaining)Host auto-transfers to next player (default)
Last player leavesLobby is deleted, returns {"status": "lobby_deleted"}
Player leaves during matchmakingMatchmaking auto-cancels, lobby returns to waiting

Host leave behavior

What happens when the host leaves is controlled per lobby config via hostLeaveBehavior:
ValueBehavior
"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:
ParamPurpose
regionFilter by region
statusFilter by lobby status
limitPage size (default 50, max 100)
offsetPagination 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-"
  }
}
FieldValuesNotes
typenumeric, alphanumeric, alphabeticCharacter set for the generated suffix
length1–16Length of the generated suffix (excluding prefix)
prefixany stringOptional. 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

HTTPCodeMeaning
400LOBBY_FULLCapacity exceeded
400LOBBY_NOT_ACCEPTINGIn-game with no late-join
400INVALID_ACTIONE.g., host trying to self-kick
403NOT_HOSTNon-host tried a host-only action
404LOBBY_NOT_FOUNDLobby doesn’t exist
404PLAYER_NOT_FOUNDPlayer isn’t in the lobby
409ALREADY_IN_LOBBYPlayer is already in another lobby
409GAME_ALREADY_RUNNINGStarted a game that’s already in progress
409INVALID_STATEWrong status for this operation
All errors follow the same format:
{
  "error": "Lobby is full",
  "detail": "Lobby is full (4 of 4)",
  "status": 400
}