Skip to content

Friends (pairwise relationships & invites)

A friend is a pairwise relationship between two accounts (invites → mutual friendship, blocking is one-sided). A client calls four RPC methods to manage relationships: friends.add (invite or accept), friends.remove (unblock and delete), friends.block (one-sided), and friends.list (read your relations). These are built-in server RPC methods (reserved, not overridable by game script), reachable over the existing generic RPC transport — no new wire kinds, no SDK binding changes.

From state Action Result
(none) friends.add invited_sent on the caller; invited_received on the other
invited_received friends.add (matching) Both sides become friend
friend friends.add (re-invite) No-op; state stays friend
Any friends.block Caller-side becomes blocked (one-sided; blocks re-invites)
blocked friends.remove Clears the block; the relation is deleted
Any friends.remove Both-sided deletion; unblocked if either side was blocked

Attempting to add when either side has blocked returns 409 conflict. A user cannot befriend or block themselves — returns 400 invalid request.

Purpose: Invite another user to be friends, or complete a mutual friendship if they already invited the caller (how “accept” happens).

Request body (JSON):

{ "other": "<user_id>" }
Field Type Required Meaning
other string yes The account to invite.

Success response (status=0, JSON):

{ "state": "<friend_state>" }
Field Type Meaning
state string The caller’s new state: invited_sent, invited_received (unchanged, caller sent first), friend (mutual or re-invite), or blocked (rejected).

Errors (status != 0, UTF-8 message body)

Case Message
Unauthenticated (guest) "authentication required"
Invalid JSON body "invalid JSON body"
Missing or empty other field "missing string field: other"
Caller == other (self-friendship) "validation: cannot befriend yourself"
Either side is blocked "relationship is blocked"

Examples

// Send the friends.add RPC with a JSON body.
auto* Client = GetGameInstance->GetSubsystem<UCitadelClientSubsystem>;
FString payload = FString::Printf(TEXT(R"({"other":"%s"})"), *OtherId);
auto status = Client->Send(
CitadelWire::KindRpcRequest,
FStringToBinary(payload),
true // reliable
);
if (status != ECitadelStatus::Ok && status != ECitadelStatus::Again) {
UE_LOG(LogTemp, Error, TEXT("RPC send failed: %s"), *Client->LastError);
return;
}
// Poll until the correlated KIND_RPC_RESPONSE arrives.
uint16 kind;
TArray<uint8> body;
while (Client->Poll(kind, body) == ECitadelStatus::Ok) {
if (kind == CitadelWire::KindRpcResponse) {
uint64 requestId;
uint8 status;
TArray<uint8> payload;
if (CitadelWire::DecodeRpcResponse(body, requestId, status, payload)) {
if (status == CitadelWire::RpcStatusOk) {
// Parse the JSON: { "state": "invited_sent"|"friend"|... }
FString stateStr = BinaryToFString(payload);
// Extract and use the state.
UE_LOG(LogTemp, Warning, TEXT("Add succeeded; new state: %s"), *stateStr);
} else {
FString errorMsg = BinaryToFString(payload);
UE_LOG(LogTemp, Error, TEXT("Add failed: %s"), *errorMsg);
}
return;
}
}
}

Purpose: Delete a friend relation in both directions. If either side was blocked, this also clears the block.

Request body (JSON):

{ "other": "<user_id>" }
Field Type Required Meaning
other string yes The account to remove.

Success response (status=0, JSON):

{ "removed": true|false }
Field Type Meaning
removed bool true if a relation existed and was deleted; false if no relation existed (no-op success).

Errors (status != 0, UTF-8 message body)

Case Message
Unauthenticated (guest) "authentication required"
Invalid JSON body "invalid JSON body"
Missing or empty other field "missing string field: other"

Examples

auto* Client = GetGameInstance->GetSubsystem<UCitadelClientSubsystem>;
FString payload = FString::Printf(TEXT(R"({"other":"%s"})"), *OtherId);
Client->Send(CitadelWire::KindRpcRequest, FStringToBinary(payload), true);
// Poll for response (see friends.add for the polling pattern).

Purpose: One-sidedly block another user, preventing them from re-inviting the caller.

Request body (JSON):

{ "other": "<user_id>" }
Field Type Required Meaning
other string yes The account to block.

Success response (status=0, JSON):

{}

An empty object (no fields).

Errors (status != 0, UTF-8 message body)

Case Message
Unauthenticated (guest) "authentication required"
Invalid JSON body "invalid JSON body"
Missing or empty other field "missing string field: other"
Caller == other (self-block) "validation: cannot befriend yourself"

Examples

auto* Client = GetGameInstance->GetSubsystem<UCitadelClientSubsystem>;
FString payload = FString::Printf(TEXT(R"({"other":"%s"})"), *OtherId);
Client->Send(CitadelWire::KindRpcRequest, FStringToBinary(payload), true);
// Poll for response (see friends.add for the polling pattern).

Purpose: Retrieve all friend relations for the caller, ordered by the other user’s id.

Request body (JSON):

{}

(Empty object; no fields required.)

Success response (status=0, JSON):

{
"friends": [
{
"user_id": "<id>",
"state": "<friend_state>",
"updated_unix_ms": <u64>
}
]
}
Field Type Meaning
friends array List of relation rows.
friends[].user_id string The other account.
friends[].state string The caller’s state: invited_sent, invited_received, friend, or blocked.
friends[].updated_unix_ms number Unix milliseconds when this relation last changed.

Errors (status != 0, UTF-8 message body)

Case Message
Unauthenticated (guest) "authentication required"
Invalid JSON body "invalid JSON body"

Examples

auto* Client = GetGameInstance->GetSubsystem<UCitadelClientSubsystem>;
FString payload = TEXT("{}");
Client->Send(CitadelWire::KindRpcRequest, FStringToBinary(payload), true);
// Poll for response (see friends.add for the polling pattern).
  • Operator console (admin API): Friends can also be managed via the admin-console friends endpoints for operator-side tooling and batch operations.
  • Server-side scripting (parity host API): A server-side Lua, Python, or JavaScript script manages friends through the citadel.friends.* parity host API, which exposes the same friend management (add/remove/block/list) semantics as the game-client RPC surface.