Social Integration
Introduction
Buddo provides built-in social features that every operator app shares. Friends, presence, and chat are platform services — users maintain a single social graph, one online status, and one message history across every app on the platform.
As an operator, you don’t need to build your own friends list, presence system, or chat server. Instead, your app consumes the Buddo social API and the platform handles the rest. A user who adds a friend in your game can see that friend in every other Buddo app, and vice versa.
Architecture
All social data lives at the platform level. There is one friend list per
user, one presence record per user, and one set of chat channels that any
app can access. The social API is authenticated with JWT bearer
tokens (the same token returned by
POST /api/auth/login), not OAuth tokens.
Key principles
- One social graph. A friendship formed in App A is visible in App B. There are no per-app friend lists.
- One presence. A user is either online or offline on the platform. The presence record can include which game and lobby they are currently in, so other apps can show context.
-
Shared chat channels. Chat channels are identified by
type (
lobbyortable) and an optionalref_id. Any app that knows the channel can read and write messages. - Platform-managed blocking. When a user blocks someone, the block applies everywhere — friend requests, search results, and chat are all filtered.
Friends System
The friends system supports searching for users, sending and responding to friend requests, listing friends, removing friendships, and blocking or unblocking users. All endpoints require a JWT bearer token.
Search for users
Before sending a friend request, your app needs to find the target user. The search endpoint matches against usernames (minimum 2 characters) and returns up to 20 results. Blocked users and the current user are automatically excluded.
/api/social/users/search?q={query}
GET /api/social/users/search?q=alice
Authorization: Bearer <jwt>
// 200 OK
{
"users": [
{ "id": "uuid", "username": "alice42" },
{ "id": "uuid", "username": "alice_wonder" }
]
}
Send a friend request
Once you have a user’s ID, send a friend request. The request
stays in pending state until the recipient accepts or
rejects it.
/api/social/friends
POST /api/social/friends
Authorization: Bearer <jwt>
Content-Type: application/json
{ "friend_id": "target-user-uuid" }
// 201 Created
{
"friendship_id": "uuid",
"status": "pending"
}
| Status | Meaning |
|---|---|
403 | Target user has blocked you |
404 | User not found |
409 | Already friends or request already pending |
List pending requests
Retrieve incoming friend requests that the current user has not yet responded to.
/api/social/friends/pending
GET /api/social/friends/pending
Authorization: Bearer <jwt>
// 200 OK
[
{
"id": "sender-user-uuid",
"friendship_id": "uuid",
"username": "bob99",
"sent_at": "2026-05-20T14:30:00Z"
}
]
Accept or reject a request
Use the friendship_id from the pending list to accept or
reject. The action field must be "accept" or
"reject".
/api/social/friends/{friendship_id}
PUT /api/social/friends/<friendship_id>
Authorization: Bearer <jwt>
Content-Type: application/json
{ "action": "accept" }
// 200 OK
{ "status": "accepted" }
List friends
Returns all accepted friends for the current user. Each entry includes
the friend’s id, username, and
email.
/api/social/friends
GET /api/social/friends
Authorization: Bearer <jwt>
// 200 OK
[
{ "id": "uuid", "username": "alice42", "email": "alice@example.com" },
{ "id": "uuid", "username": "bob99", "email": "bob@example.com" }
]
Remove a friendship
Either party can remove an accepted friendship. This is a mutual removal — neither user will see the other in their friend list afterward.
/api/social/friends/{friendship_id}
DELETE /api/social/friends/<friendship_id>
Authorization: Bearer <jwt>
// 200 OK
{ "status": "removed" }
Block and unblock
Blocking a user prevents them from sending friend requests, appearing in search results, and interacting via chat. Blocks are platform-wide and apply across all apps.
/api/social/block
POST /api/social/block
Authorization: Bearer <jwt>
Content-Type: application/json
{ "user_id": "target-user-uuid" }
// 200 OK
{ "status": "blocked" }
To unblock:
/api/social/block/{user_id}
DELETE /api/social/block/<user_id>
Authorization: Bearer <jwt>
// 200 OK
{ "status": "unblocked" }
Presence
The presence system tracks which users are online and what they are
currently doing. Your app reports the user’s activity via a
heartbeat, and can query who among the user’s friends is online.
Presence data includes optional game and
lobby fields, so friends can see not just that
someone is online, but where they are.
Heartbeat (track presence)
Call this endpoint periodically (every 30–60 seconds is typical)
to report the user as online. Include game and/or
lobby to indicate what the user is doing.
/api/social/presence
POST /api/social/presence
Authorization: Bearer <jwt>
Content-Type: application/json
{
"game": "slots",
"lobby": "high-roller-room"
}
// 200 OK
{ "status": "ok" }
offline. Keep heartbeats
consistent while the user has your app open.
Get friends' presence
Returns a map of friend IDs to their presence status. Each entry
includes status (online or
offline), last_seen timestamp, and optional
current_game and lobby fields.
/api/social/presence
GET /api/social/presence
Authorization: Bearer <jwt>
// 200 OK
{
"user-uuid-1": {
"status": "online",
"last_seen": "2026-05-20T15:00:00Z",
"current_game": "slots",
"lobby": "high-roller-room"
},
"user-uuid-2": {
"status": "offline",
"last_seen": "2026-05-20T12:30:00Z",
"current_game": null,
"lobby": null
}
}
You can filter to specific users by passing a comma-separated list of UUIDs:
GET /api/social/presence?user_ids=uuid-1,uuid-2,uuid-3
Table presence
For game tables or rooms, you can query who is present at a specific table. This is separate from the friends-based presence query — it returns all users at the table, not just friends.
/api/social/presence/table?game={game}&ref_id={ref_id}
GET /api/social/presence/table?game=bit-battle&ref_id=table-42
Authorization: Bearer <jwt>
// 200 OK
{
"users": [
{ "user_id": "uuid", "status": "online", "last_seen": "2026-05-20T15:01:00Z" },
{ "user_id": "uuid", "status": "online", "last_seen": "2026-05-20T15:00:30Z" }
]
}
Untrack presence
When the user closes your app or logs out, call this to immediately remove their presence record rather than waiting for the heartbeat to time out.
/api/social/presence
DELETE /api/social/presence
Authorization: Bearer <jwt>
// 200 OK
{ "status": "ok" }
Chat
The chat system provides messaging within shared contexts —
game lobbies and tables. Chat channels are identified by a
type (lobby or table) and an
optional ref_id that ties the channel to a specific game
room or table instance. Messages are automatically filtered for
profanity and length (max 1,000 characters).
Create or join a channel
Before sending messages, ensure the channel exists. This endpoint is
idempotent — if a channel with the same type and
ref_id already exists, it returns the existing channel
rather than creating a duplicate.
/api/social/chat/channels
POST /api/social/chat/channels
Authorization: Bearer <jwt>
Content-Type: application/json
{
"type": "table",
"ref_id": "bit-battle-table-42"
}
// 200 OK
{
"id": "channel-uuid",
"type": "table",
"ref_id": "bit-battle-table-42"
}
Send a message
Post a message to a channel. The response includes the server-assigned ID, the filtered message body, sender details, and timestamp.
/api/social/chat/{channel_id}/messages
POST /api/social/chat/<channel_id>/messages
Authorization: Bearer <jwt>
Content-Type: application/json
{ "body": "Good luck everyone!" }
// 201 Created
{
"id": "message-uuid",
"sender_id": "user-uuid",
"sender_username": "alice42",
"filtered_body": "Good luck everyone!",
"sent_at": "2026-05-20T15:05:00Z"
}
| Status | Meaning |
|---|---|
400 | Missing message body |
422 | Message too long or caught by profanity filter |
Message history
Retrieve past messages from a channel. Messages are returned in
reverse chronological order with cursor-based pagination. Use the
before parameter with an ISO 8601 timestamp to page
backward through history.
/api/social/chat/{channel_id}/messages
GET /api/social/chat/<channel_id>/messages?limit=25
Authorization: Bearer <jwt>
// 200 OK
[
{
"id": "message-uuid",
"sender_id": "user-uuid",
"sender_username": "bob99",
"filtered_body": "gg",
"sent_at": "2026-05-20T15:04:50Z"
},
...
]
To fetch the next page, pass the sent_at of the last
message as the before parameter:
GET /api/social/chat/<channel_id>/messages?limit=25&before=2026-05-20T15:04:50Z
Integration Patterns
These patterns demonstrate how to combine the social endpoints into practical features within your operator app.
Social sidebar
Show the user’s friends and their online status alongside your app’s main content. This is the most common integration pattern.
-
On app load, fetch the friend list with
GET /api/social/friends. -
Immediately follow up with
GET /api/social/presenceto get each friend’s online status and current activity. -
Render friends sorted by status: online friends (with their
current_gameshown) at the top, offline friends below. -
Poll
GET /api/social/presenceevery 30–60 seconds to keep the sidebar current. -
Check
GET /api/social/friends/pendingperiodically to show a notification badge for incoming requests.
// Pseudocode: social sidebar initialization
const friends = await fetch('/api/social/friends', { headers: auth });
const presence = await fetch('/api/social/presence', { headers: auth });
friends.forEach(friend => {
const status = presence[friend.id];
renderFriendRow(friend.username, status?.status, status?.current_game);
});
Invite to game
Use presence data to see which friends are available, then invite them to join your game table. This pattern combines presence queries with your app’s own invitation logic.
-
Fetch presence with
GET /api/social/presenceand filter for friends whosestatusis"online". - Show an “Invite” button next to each online friend.
- When the user clicks invite, use your app’s own messaging or notification system to deliver the invite. The Buddo social API provides the social graph and presence — your app handles the invitation delivery.
-
Optionally, use the friend’s
current_gamefield to customize the invite message (e.g., “Leave Slots and join my Bit Battle table!”).
Chat in-game
Embed a chat panel in your game UI using the platform’s chat channels. This works for both lobby-wide conversations and per-table chatter.
-
When the user enters a game table, create or join the channel:
POST /api/social/chat/channelswithtype: "table"andref_idset to your table’s unique identifier. -
Load recent history with
GET /api/social/chat/{channel_id}/messages. -
Render messages in a scrollable panel. Show
sender_usernameandfiltered_body. -
Provide a text input that posts to
POST /api/social/chat/{channel_id}/messages. -
Poll for new messages periodically, or use
GET ...messages?before=...for infinite scroll into history.
// Pseudocode: joining a table chat
const channel = await fetch('/api/social/chat/channels', {
method: 'POST',
headers: auth,
body: JSON.stringify({ type: 'table', ref_id: 'my-game-table-7' })
});
const messages = await fetch(
`/api/social/chat/${channel.id}/messages?limit=50`,
{ headers: auth }
);
renderChatPanel(messages);
OAuth Scopes
The social endpoints use JWT bearer authentication, not OAuth scopes. This means social features are available to any authenticated user without requiring specific OAuth scope grants.
However, if your operator app uses OAuth tokens to authenticate users (the standard operator pattern), the user will already have a JWT from logging in to authorize your app. The social API calls use that underlying JWT, not the OAuth access token.
POST /api/oauth/token, the response includes a
session_token alongside the OAuth access token. The
session token can be refreshed via
POST /api/session/refresh to maintain a heartbeat. See the
OAuth PKCE guide for the full flow.
For your OAuth app registration, the following scopes are relevant to social-adjacent features:
| Scope | Purpose |
|---|---|
profile:read |
Read user profile — needed to identify the user and display their username in social contexts |
points:read |
Read point balance — useful if your social features involve gifting or wagering |
points:transfer |
Transfer points between users — enables friend-to-friend gifting |
Endpoint Summary
All social endpoints at a glance. Every endpoint requires a JWT bearer
token via the Authorization header.
| Method | Path | Description |
|---|---|---|
GET |
/api/social/users/search?q={query} |
Search users by username |
GET |
/api/social/friends |
List current user’s friends |
POST |
/api/social/friends |
Send a friend request |
GET |
/api/social/friends/pending |
List pending incoming requests |
PUT |
/api/social/friends/{id} |
Accept or reject a friend request |
DELETE |
/api/social/friends/{id} |
Remove a friendship |
POST |
/api/social/block |
Block a user |
DELETE |
/api/social/block/{user_id} |
Unblock a user |
POST |
/api/social/presence |
Track presence (heartbeat) |
GET |
/api/social/presence |
Get friends’ online presence |
DELETE |
/api/social/presence |
Untrack presence |
GET |
/api/social/presence/table |
Get presence for a game table |
POST |
/api/social/chat/channels |
Create or join a chat channel |
GET |
/api/social/chat/{channel_id}/messages |
Get messages from a channel |
POST |
/api/social/chat/{channel_id}/messages |
Send a message to a channel |