Skip to content

Parties

Parties are authenticated, local account groups used to queue an indivisible matchmaker ticket. A party has one leader and up to eight members. The leader invites accounts; an invited account accepts before it becomes a member. A party ticket represents every connected member, so it either forms as a whole or not at all. Each member later receives and accepts their own matchmaker handoff.

All methods below use the generic realtime RPC envelope. Guests receive "authentication required". Clustered PostgreSQL and CockroachDB deployments use durable party ownership; clustered SQLite is rejected rather than claiming durable cross-node behavior.

Successful create, invite, accept, promote, remove, and status calls return:

{
"party_id": "opaque-id",
"leader_user_id": "alice",
"members": ["alice", "bob"],
"invitations": []
}

All operations have the signature rpc(method, JSON payload). Use the examples below for any method in the reference table.

Client->Send(CitadelWire::KindRpcRequest,
CitadelWire::EncodeRpcRequest(81, TEXT("party.create"), TEXT("{}")), true);
// Decode the correlated KIND_RPC_RESPONSE body as JSON.

Payload: {}. Returns: the response shape above with the authenticated caller as sole leader/member. An account may belong to one active party only.

Payload: {"party_id":"…","target_user_id":"bob"}.

Returns: the updated party snapshot. Only the leader may invite; the target must not already be a member of another party. The maximum of eight includes outstanding invitations.

Payload: {"party_id":"…"}.

Returns: the updated party snapshot. Only the invited authenticated account can accept. A random party id alone is not enough without an outstanding invite.

Payload: {"party_id":"…"}. Returns: {"left":true}.

A normal member leaves immediately. When the leader leaves, the local party is closed rather than silently transferring leadership; use party.promote first when another member should own it.

Payload: {"party_id":"…","target_user_id":"bob"}.

Returns: the updated party snapshot. Only the current leader can promote an existing member.

Payload: {"party_id":"…","target_user_id":"bob"}.

Returns: the updated party snapshot. Only the leader can remove a non-leader member. A leader cannot remove itself; use party.close or promote first.

Payload: {"party_id":"…"}. Returns: {"closed":true}.

Only the leader can close a party. Closing clears its invitations and lets every former member create or join another party.

Payload: {"party_id":"…"}.

Returns: the response shape above, including the authoritative revision. Only a member or an invited account can read a party snapshot.

On a clustered PostgreSQL or CockroachDB deployment, party RPCs can arrive at any gateway. Citadel routes each mutation to a durably leased party owner and fences it by owner generation and snapshot revision. Repeating the same client request id replays the original result, including after an owner restart; a stale owner response is rejected and the client should retry from a fresh party.status snapshot.

After an expired owner is replaced, the new owner reloads the last committed snapshot and emits one reliable party.resync_required transition followed by that snapshot before it accepts a new mutation. Clients should discard any stale local party view, apply the supplied snapshot, then resume normal RPCs; the transition is fenced by generation and may be safely deduplicated. SQLite and MongoDB clustered configurations are rejected at startup rather than being treated as distributed durable storage.

Citadel advertises party presence between nodes as a fenced, expiring (party_id, node_id) lease. The directory never contains account, participant, socket, invitation, or member-online data. A joining or reconnecting local member renews its node lease; final local departure withdraws it. If a node is lost, its destination expires; a delayed renewal or withdrawal cannot override a newer lease tombstone.

Cross-node member snapshots travel only over the authenticated node-control channel, one command per current destination node. The receiving gateway checks both source and destination lease fences, reloads durable membership, and then fans out solely to its currently authorized local members. Invitees receive no member-online detail, and nonmembers receive neither presence nor a party-existence signal. Multiple devices for one account resolve to one visible member. Client transitions are monotonic by (party revision, presence sequence): duplicates and reordered updates are ignored. If a bounded local delivery queue drops an update, Citadel sends a reliable party.presence.resync barrier followed by a fresh authorized snapshot before resuming live deltas.

The leader adds party_id to matchmaker.add:

{
"party_id": "opaque-id",
"min_count": 4,
"max_count": 4,
"ttl_ms": 30000
}

Every member must be currently connected. While the ticket is queued, party membership and invitations are frozen; cancel the ticket before changing them. The matchmaker counts all members toward the cohort size and never forms a subset of the party. Each member gets a distinct, account-bound KIND_MATCHMAKER_MATCHED token and may recover it through matchmaker.status after reconnecting.

For clustered party authority, matchmaker.add admits the committed fenced party snapshot rather than a gateway-local membership list. If the party changes before admission, the request is rejected as retryable instead of queuing a partial or stale party.