Sblurp

ServerService

Each call is POST {base}/sblurp.v1.ServerService/<Call> with a Connect, gRPC or gRPC-Web body. See Protocol and transport for the basics. Field types in links are on the Types page. Tools can read every call and type from schema.json.

Server info (public, no auth)

GetServerInfo

Request GetServerInfoRequest:

No fields.

Response GetServerInfoResponse:

Field Type Notes
name string
description string Markdown, shown in the sidebar
icon_url string
protocol_version int32 currently 1
trusted_issuers repeated string auth issuers this server accepts, e.g. "https://sblurp.chat"
base_url string canonical URL; also the JWT audience
member_count int32
accent_color string optional "#rrggbb" for the rail icon
invite_only bool Join needs a valid invite_code
join_questions repeated string Questions a plugin asks people who join; show each with a text field and send the answers in JoinRequest.answers, in the same order.
banner Attachment optional image above the name in the sidebar
retention_days int32 Message retention. 0 = keep forever. Otherwise the server deletes messages (with their thread replies, reactions and files) older than this many days, and ListMessages stops returning them. Clients hide loaded messages older than now minus this many days; no per-message events.
needs_setup bool A brand-new community whose owner has not finished or skipped the setup wizard yet. Clients show the owner the wizard; others ignore it.

Joining

Authentication: every call except GetServerInfo carries

Authorization: Bearer <token>

The token is either

(a) a JWT from a trusted issuer with aud = this server's base_url, or
(b) a bot token ("sbb_..."), created by an admin.

A human token for a person who has not joined yet is accepted only by Join and GetServerInfo; other calls return PERMISSION_DENIED.

Join

Request JoinRequest:

Field Type Notes
display_name string Profile fields the user agreed to share with this community.
avatar_url string
bio string
invite_code string checked only when the server is invite_only; case-insensitive
answers repeated string to GetServerInfoResponse.join_questions, in order

Response JoinResponse:

Field Type Notes
member Member
created bool false if already a member

GetMe

Request GetMeRequest:

No fields.

Response GetMeResponse:

Field Type Notes
member Member
server_version string The next three are for admins and the owner only; empty for others. "1.2.3", "1.3.0-beta.1", "nightly-20261016-abc1234" or "dev"
server_track string "stable", "beta" or "nightly"
update_available string a newer version on server_track, or ""

UpdateProfile

Request UpdateProfileRequest:

Field Type Notes
display_name optional string
avatar_url optional string
bio optional string
status_text optional string
status_emoji optional string

Response UpdateProfileResponse:

Field Type Notes
member Member

Sync

GetState

Everything a client needs to render the sidebar after connecting.

Request GetStateRequest:

No fields.

Response GetStateResponse:

Field Type Notes
me Member
channels repeated Channel channels I am in (my sidebar), incl. DMs
members repeated Member
read_states repeated ReadState
seq int64 server seq at snapshot time; Subscribe from here
roles repeated Role sorted by position
channel_mode ChannelMode
blocked_member_ids repeated string members I blocked, see BlockMember
muted_member_ids repeated string members I muted, see MuteMember

Subscribe

Server stream. Browsers support server streams over Connect; there is deliberately no bidirectional stream. Clients send with unary calls.

Server stream of Event messages.

Request SubscribeRequest:

Field Type Notes
after_seq int64 Last seq the client has applied. The server first replays every event after it (that the caller may see), then streams live events. If the gap is too large the server sends Event.resync and the client must call GetState again.

Membership and moderation

Rank: owner > admin > moderator > member. Nobody acts on a member of equal or higher rank.

SetMemberRole

Admin or owner only. The new role must be MEMBER, MODERATOR or ADMIN and lower than the caller's, and the target must rank lower than the caller.

Request SetMemberRoleRequest:

Field Type Notes
member_id string
role MemberRole

Response SetMemberRoleResponse:

Field Type Notes
member Member

LeaveCommunity

Leave this community. The owner cannot leave. You may Join again later. Others get member_left.

Request LeaveCommunityRequest:

No fields.

Response LeaveCommunityResponse:

No fields.

KickMember

Moderator or higher, and a higher rank than the target. The target leaves the community and its private channels but may Join again. Not for bots (use BotService.DeleteBot).

Request KickMemberRequest:

Field Type Notes
member_id string

Response KickMemberResponse:

No fields.

BanMember

Like KickMember, and the person's login (iss, sub) cannot Join again.

Request BanMemberRequest:

Field Type Notes
member_id string

Response BanMemberResponse:

No fields.

ListBans

Moderator or higher. Newest first.

Request ListBansRequest:

No fields.

Response ListBansResponse:

Field Type Notes
bans repeated Ban

UnbanMember

Moderator or higher. The login may Join again (with an invite if the community is invite-only) and comes back with the same member id.

Request UnbanMemberRequest:

Field Type Notes
member_id string

Response UnbanMemberResponse:

No fields.

Invites and settings (admin or owner)

An open community lets anyone with a valid login Join. An invite-only one needs JoinRequest.invite_code, except for the first person (the owner) and people who are already members. A banned login cannot join either way.

Roles (admins only, except that everyone sees them in GetState).

CreateInvite

Request CreateInviteRequest:

Field Type Notes
expires_in_seconds int64 0 = never; max one year
max_uses int32 0 = no limit

Response CreateInviteResponse:

Field Type Notes
invite Invite

ListInvites

Every invite that was not revoked, newest first, including expired and used-up ones (compare expires_at_ms and uses to show their state).

Request ListInvitesRequest:

No fields.

Response ListInvitesResponse:

Field Type Notes
invites repeated Invite

RevokeInvite

Request RevokeInviteRequest:

Field Type Notes
code string

Response RevokeInviteResponse:

No fields.

UpdateServerSettings

Request UpdateServerSettingsRequest:

Field Type Notes
invite_only optional bool
channel_mode ChannelMode UNSPECIFIED = leave as is
description optional string Markdown; "" falls back to the server's -description
banner_id optional string an uploaded image's id (POST /upload); "" removes it
icon_id optional string rail icon, an uploaded image's id; "" falls back to -icon-url
retention_days optional int32 0 = keep forever, else 1 to 3650
setup_done bool true clears GetServerInfoResponse.needs_setup

Response UpdateServerSettingsResponse:

Field Type Notes
invite_only bool
channel_mode ChannelMode
description string
banner Attachment
icon_url string
retention_days int32

CreateRole

Request CreateRoleRequest:

Field Type Notes
name string
color string "#rrggbb" or empty

Response CreateRoleResponse:

Field Type Notes
role Role

UpdateRole

Request UpdateRoleRequest:

Field Type Notes
role_id string
name optional string
color optional string
position optional int32

Response UpdateRoleResponse:

Field Type Notes
role Role

DeleteRole

Fails with FAILED_PRECONDITION while the role still limits a channel, so deleting a role never opens a channel to everyone.

Request DeleteRoleRequest:

Field Type Notes
role_id string

Response DeleteRoleResponse:

No fields.

SetMemberRoles

Replaces the member's roles. Works for bots too.

Request SetMemberRolesRequest:

Field Type Notes
member_id string
role_ids repeated string

Response SetMemberRolesResponse:

Field Type Notes
member Member

Custom emoji

Messages and reactions use a custom emoji as ":name:". Unknown names are plain text. The list and the uploads are plain HTTP, see server/README.md:

GET  {base}/emoji      ListEmojiResponse as protojson, with an ETag.
                       Send If-None-Match; 304 means nothing changed.
POST {base}/emoji      multipart "file" parts (images or .zip files);
                       moderator or higher. Reply: UploadEmojiResponse.
GET  {base}/emoji.zip  every emoji as name.ext in one zip.
GET  {base}/emoji/{id} the image, public, cached forever.

After a change every member gets Event.emoji_changed.

DeleteEmoji

Moderator or higher.

Request DeleteEmojiRequest:

Field Type Notes
name string

Response DeleteEmojiResponse:

No fields.

Blocking

BlockMember

Any member, for themselves only. Blocking hides nothing on the server except DMs: the two cannot open a DM with each other or send in a shared DM. Clients collapse a blocked member's messages. The blocker's devices get Event.member_blocked or member_unblocked. Blocking twice is fine.

Request BlockMemberRequest:

Field Type Notes
member_id string

Response BlockMemberResponse:

No fields.

UnblockMember

Request UnblockMemberRequest:

Field Type Notes
member_id string

Response UnblockMemberResponse:

No fields.

MuteMember

Any member, for themselves only. Muting is quieter than blocking and does not stop DMs: a muted member's messages do not count toward my unread or mention counts, and clients collapse them, skip their notifications and hide their typing. Blocking does the same. Only the muter's devices see the mute list (GetState.muted_member_ids, Event.member_muted or member_unmuted). Muting twice is fine.

Request MuteMemberRequest:

Field Type Notes
member_id string

Response MuteMemberResponse:

No fields.

UnmuteMember

Request UnmuteMemberRequest:

Field Type Notes
member_id string

Response UnmuteMemberResponse:

No fields.

Reports (1119)

ReportMessage

Any member, about a message they can see that someone else wrote, with an optional note (up to 1,000 characters). The server keeps a copy of the text, so the report outlives an edit or a delete. A report of a DM message shares that one message with moderators and nothing else from the DM. Reporting the same message again replaces the note. People may send 5 reports, then 1 every 10 minutes (RESOURCE_EXHAUSTED). Nobody is told who reported, and the reporter cannot list reports.

Request ReportMessageRequest:

Field Type Notes
message_id string
note string

Response ReportMessageResponse:

No fields.

ListReports

Moderator or higher. Open reports, newest first, about members the caller outranks (the owner sees reports about everyone, the owner included).

Request ListReportsRequest:

No fields.

Response ListReportsResponse:

Field Type Notes
reports repeated Report

ResolveReport

Moderator or higher, for a report ListReports shows them. Resolving deletes every open report of that message, copies included.

Request ResolveReportRequest:

Field Type Notes
report_id string

Response ResolveReportResponse:

No fields.