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.
Response shape
Section titled “Response shape”Successful create, invite, accept, promote, remove, and status calls return:
{ "party_id": "opaque-id", "leader_user_id": "alice", "members": ["alice", "bob"], "invitations": []}Calling an operation
Section titled “Calling an operation”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.- Build the method payload as a Make Json Object.
- Call Encode RPC Request with the method shown below and a new request id.
- Send it reliably with RPC Request kind
3. - On RPC Response kind
4, decode the correlated JSON body.
rpcClient.CallRpc("party.create", "{}", result => Debug.Log(result.Payload));citadel.call_rpc("party.create", "{}", func(result): print(result.payload))let reply = client.call_rpc("party.create", br#"{}"#).await?;assert!(reply.is_ok);const reply = JSON.parse(new TextDecoder.decode(await client.callRpc("party.create")));console.log(reply.party_id);party.create
Section titled “party.create”Payload: {}. Returns: the response shape above with the authenticated
caller as sole leader/member. An account may belong to one active party only.
party.invite
Section titled “party.invite”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.
party.accept
Section titled “party.accept”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.
party.leave
Section titled “party.leave”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.
party.promote
Section titled “party.promote”Payload: {"party_id":"…","target_user_id":"bob"}.
Returns: the updated party snapshot. Only the current leader can promote an existing member.
party.remove
Section titled “party.remove”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.
party.close
Section titled “party.close”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.
party.status
Section titled “party.status”Payload: {"party_id":"…"}.
Returns: the response shape above, including the authoritative revision.
Only a member or an invited account can read a party snapshot.
Clustered party authority
Section titled “Clustered party authority”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.
Clustered presence and privacy
Section titled “Clustered presence and privacy”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.
Queueing the party
Section titled “Queueing the party”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.