{
  "components": {
    "schemas": {
      "AddChannelMembersRequest": {
        "description": "Any member of a private channel may add others to it. The new members get channel_updated (treat it as an upsert).",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "memberIds": {
            "description": "max 50",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "AddChannelMembersResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "Attachment": {
        "description": "",
        "properties": {
          "contentType": {
            "type": "string"
          },
          "height": {
            "format": "int32",
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "size": {
            "format": "int64",
            "type": "string"
          },
          "url": {
            "description": "relative to the server base URL",
            "type": "string"
          },
          "width": {
            "description": "images only",
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "Ban": {
        "description": "A banned login. The member row stays, so member.id keeps working.",
        "properties": {
          "bannedAtMs": {
            "format": "int64",
            "type": "string"
          },
          "bannedBy": {
            "description": "member id",
            "type": "string"
          },
          "member": {
            "$ref": "#/components/schemas/Member",
            "description": "the profile they had when banned"
          }
        },
        "type": "object"
      },
      "BanMemberRequest": {
        "description": "Like KickMember, and the person's login (iss, sub) cannot Join again.",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "BanMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "BlockMemberRequest": {
        "description": "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.",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "BlockMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "BrowseChannelsRequest": {
        "description": "Every TEXT channel I can see, joined or not (OPT_IN's \"Browse channels\").",
        "properties": {},
        "type": "object"
      },
      "BrowseChannelsResponse": {
        "description": "",
        "properties": {
          "channels": {
            "items": {
              "$ref": "#/components/schemas/Channel"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "Channel": {
        "description": "",
        "properties": {
          "category": {
            "description": "sidebar section, e.g. \"General\"; empty = none",
            "type": "string"
          },
          "createdAtMs": {
            "format": "int64",
            "type": "string"
          },
          "headerImage": {
            "$ref": "#/components/schemas/Attachment",
            "description": "optional image behind the channel header"
          },
          "id": {
            "type": "string"
          },
          "isDefault": {
            "description": "TEXT only: OPT_IN mode joins new members to it",
            "type": "boolean"
          },
          "kind": {
            "$ref": "#/components/schemas/ChannelKind"
          },
          "lastMessageSeq": {
            "description": "seq of the newest message, 0 if none",
            "format": "int64",
            "type": "string"
          },
          "memberCount": {
            "description": "set by BrowseChannels only; 0 elsewhere",
            "format": "int32",
            "type": "integer"
          },
          "memberIds": {
            "description": "set for DM and PRIVATE channels",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "description": "empty for DMs",
            "type": "string"
          },
          "position": {
            "description": "sort order inside the category",
            "format": "int32",
            "type": "integer"
          },
          "roleIds": {
            "description": "TEXT only. Empty: every member can see the channel. Otherwise only members with one of these roles (and admins and the owner) can.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "topic": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ChannelKind": {
        "description": "- `CHANNEL_KIND_TEXT`: every member can see it, unless role_ids limits it\n- `CHANNEL_KIND_PRIVATE`: invite only\n- `CHANNEL_KIND_DM`: direct message, 2+ members",
        "enum": [
          "CHANNEL_KIND_UNSPECIFIED",
          "CHANNEL_KIND_TEXT",
          "CHANNEL_KIND_PRIVATE",
          "CHANNEL_KIND_DM"
        ],
        "type": "string"
      },
      "ChannelMode": {
        "description": "How members get into text channels. Private channels and DMs keep their explicit member lists in both modes.\n\n- `CHANNEL_MODE_ROLES`: Discord-like: you are in every text channel you can see\n- `CHANNEL_MODE_OPT_IN`: Slack-like: browse the channels you can see, join and leave them",
        "enum": [
          "CHANNEL_MODE_UNSPECIFIED",
          "CHANNEL_MODE_ROLES",
          "CHANNEL_MODE_OPT_IN"
        ],
        "type": "string"
      },
      "ConnectError": {
        "description": "A Connect error. See [Errors](https://sblurp.chat/docs/api/errors).",
        "properties": {
          "code": {
            "description": "Such as `permission_denied` or `not_found`.",
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateBotRequest": {
        "description": "Admin or owner only. username is 2 to 32 of a-z, 0-9, _, . and -.",
        "properties": {
          "avatarUrl": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "username": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateBotResponse": {
        "description": "",
        "properties": {
          "bot": {
            "$ref": "#/components/schemas/Member"
          },
          "token": {
            "description": "shown once; the server stores only a hash",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateChannelRequest": {
        "description": "Admin or owner only. Names are normalized: \"Book Club\" becomes book-club.",
        "properties": {
          "category": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/ChannelKind",
            "description": "TEXT or PRIVATE"
          },
          "name": {
            "type": "string"
          },
          "topic": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateChannelResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "CreateInviteRequest": {
        "description": "",
        "properties": {
          "expiresInSeconds": {
            "description": "0 = never; max one year",
            "format": "int64",
            "type": "string"
          },
          "maxUses": {
            "description": "0 = no limit",
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "CreateInviteResponse": {
        "description": "",
        "properties": {
          "invite": {
            "$ref": "#/components/schemas/Invite"
          }
        },
        "type": "object"
      },
      "CreateRoleRequest": {
        "description": "",
        "properties": {
          "color": {
            "description": "\"#rrggbb\" or empty",
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CreateRoleResponse": {
        "description": "",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        },
        "type": "object"
      },
      "CustomEmoji": {
        "description": "",
        "properties": {
          "name": {
            "description": "1 to 62 of a-z, 0-9, - and _; unique on this server",
            "type": "string"
          },
          "url": {
            "description": "relative to the server base URL; never changes, cache forever",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeleteBotRequest": {
        "description": "Admin or owner only. The bot's messages stay; its username is freed.",
        "properties": {
          "botId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeleteBotResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "DeleteEmojiRequest": {
        "description": "Moderator or higher.",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeleteEmojiResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "DeleteMessageRequest": {
        "description": "Your own messages, or anyone's for a moderator or higher.",
        "properties": {
          "messageId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeleteMessageResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "DeletePluginRequest": {
        "description": "Deletes the file and the plugin's bot. The bot's messages stay.",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeletePluginResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "DeleteRoleRequest": {
        "description": "Fails with FAILED_PRECONDITION while the role still limits a channel, so deleting a role never opens a channel to everyone.",
        "properties": {
          "roleId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeleteRoleResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "EditMessageRequest": {
        "description": "Your own messages only.",
        "properties": {
          "messageId": {
            "type": "string"
          },
          "text": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "EditMessageResponse": {
        "description": "",
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          }
        },
        "type": "object"
      },
      "EmojiUploadResult": {
        "description": "",
        "properties": {
          "error": {
            "description": "why it was rejected",
            "type": "string"
          },
          "file": {
            "description": "the file name, or its path inside a zip",
            "type": "string"
          },
          "name": {
            "description": "emoji name made from the file name",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/EmojiUploadStatus"
          }
        },
        "type": "object"
      },
      "EmojiUploadStatus": {
        "description": "- `EMOJI_UPLOAD_STATUS_EXISTS`: skipped: the name is taken\n- `EMOJI_UPLOAD_STATUS_REJECTED`: see error",
        "enum": [
          "EMOJI_UPLOAD_STATUS_UNSPECIFIED",
          "EMOJI_UPLOAD_STATUS_ADDED",
          "EMOJI_UPLOAD_STATUS_EXISTS",
          "EMOJI_UPLOAD_STATUS_REJECTED"
        ],
        "type": "string"
      },
      "Event": {
        "description": "",
        "properties": {
          "atMs": {
            "format": "int64",
            "type": "string"
          },
          "channelCreated": {
            "$ref": "#/components/schemas/Channel",
            "description": "One of `kind`."
          },
          "channelDeleted": {
            "description": "One of `kind`. channel id",
            "type": "string"
          },
          "channelModeChanged": {
            "$ref": "#/components/schemas/ChannelMode",
            "description": "One of `kind`."
          },
          "channelUpdated": {
            "$ref": "#/components/schemas/Channel",
            "description": "One of `kind`."
          },
          "emojiChanged": {
            "description": "One of `kind`. custom emoji added or deleted; refetch GET /emoji",
            "type": "boolean"
          },
          "heartbeat": {
            "description": "One of `kind`. ephemeral, every 25s keeps proxies open",
            "type": "boolean"
          },
          "memberBlocked": {
            "description": "One of `kind`. member id; I blocked them (from any of my devices)",
            "type": "string"
          },
          "memberJoined": {
            "$ref": "#/components/schemas/Member",
            "description": "One of `kind`."
          },
          "memberLeft": {
            "description": "One of `kind`. member id",
            "type": "string"
          },
          "memberMuted": {
            "description": "One of `kind`. member id; I muted them (from any of my devices)",
            "type": "string"
          },
          "memberUnblocked": {
            "description": "One of `kind`. member id; I unblocked them",
            "type": "string"
          },
          "memberUnmuted": {
            "description": "One of `kind`. member id; I unmuted them",
            "type": "string"
          },
          "memberUpdated": {
            "$ref": "#/components/schemas/Member",
            "description": "One of `kind`."
          },
          "messageCreated": {
            "$ref": "#/components/schemas/Message",
            "description": "One of `kind`."
          },
          "messageDeleted": {
            "$ref": "#/components/schemas/Message",
            "description": "One of `kind`."
          },
          "messageUpdated": {
            "$ref": "#/components/schemas/Message",
            "description": "One of `kind`. edits, reactions, reply counts"
          },
          "pluginsChanged": {
            "description": "One of `kind`. a plugin was installed, enabled, disabled or deleted; admins call ListPlugins",
            "type": "boolean"
          },
          "presence": {
            "$ref": "#/components/schemas/PresenceEvent",
            "description": "One of `kind`. ephemeral"
          },
          "presenceBatch": {
            "$ref": "#/components/schemas/PresenceBatch",
            "description": "One of `kind`. ephemeral; apply each change like presence"
          },
          "readState": {
            "$ref": "#/components/schemas/ReadState",
            "description": "One of `kind`. my own read state, from another device"
          },
          "resync": {
            "description": "One of `kind`. client must call GetState",
            "type": "boolean"
          },
          "roleDeleted": {
            "description": "One of `kind`. role id; drop it from members and channels",
            "type": "string"
          },
          "roleUpdated": {
            "$ref": "#/components/schemas/Role",
            "description": "One of `kind`. created or changed"
          },
          "seq": {
            "description": "0 for ephemeral events (typing, presence, heartbeat)",
            "format": "int64",
            "type": "string"
          },
          "serverUpdated": {
            "description": "One of `kind`. description, banner or icon changed; call GetServerInfo",
            "type": "boolean"
          },
          "typing": {
            "$ref": "#/components/schemas/TypingEvent",
            "description": "One of `kind`. ephemeral"
          }
        },
        "type": "object"
      },
      "GetLinkPreviewsRequest": {
        "description": "Previews of up to 5 http(s) links in a message I can see. The server fetches each page once, caches it for a day and serves the image itself, so viewers never contact the linked site. Links that have no preview are left out.",
        "properties": {
          "messageId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "GetLinkPreviewsResponse": {
        "description": "",
        "properties": {
          "previews": {
            "items": {
              "$ref": "#/components/schemas/LinkPreview"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "GetMeRequest": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "GetMeResponse": {
        "description": "",
        "properties": {
          "member": {
            "$ref": "#/components/schemas/Member"
          },
          "serverTrack": {
            "description": "\"stable\", \"beta\" or \"nightly\"",
            "type": "string"
          },
          "serverVersion": {
            "description": "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\"",
            "type": "string"
          },
          "updateAvailable": {
            "description": "a newer version on server_track, or \"\"",
            "type": "string"
          }
        },
        "type": "object"
      },
      "GetServerInfoRequest": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "GetServerInfoResponse": {
        "description": "",
        "properties": {
          "accentColor": {
            "description": "optional \"#rrggbb\" for the rail icon",
            "type": "string"
          },
          "banner": {
            "$ref": "#/components/schemas/Attachment",
            "description": "optional image above the name in the sidebar"
          },
          "baseUrl": {
            "description": "canonical URL; also the JWT audience",
            "type": "string"
          },
          "description": {
            "description": "Markdown, shown in the sidebar",
            "type": "string"
          },
          "iconUrl": {
            "type": "string"
          },
          "inviteOnly": {
            "description": "Join needs a valid invite_code",
            "type": "boolean"
          },
          "joinQuestions": {
            "description": "Questions a plugin asks people who join; show each with a text field and send the answers in JoinRequest.answers, in the same order.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "memberCount": {
            "format": "int32",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "needsSetup": {
            "description": "A brand-new community whose owner has not finished or skipped the setup wizard yet. Clients show the owner the wizard; others ignore it.",
            "type": "boolean"
          },
          "protocolVersion": {
            "description": "currently 1",
            "format": "int32",
            "type": "integer"
          },
          "retentionDays": {
            "description": "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.",
            "format": "int32",
            "type": "integer"
          },
          "trustedIssuers": {
            "description": "auth issuers this server accepts, e.g. \"https://sblurp.chat\"",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "GetStateRequest": {
        "description": "Everything a client needs to render the sidebar after connecting.",
        "properties": {},
        "type": "object"
      },
      "GetStateResponse": {
        "description": "",
        "properties": {
          "blockedMemberIds": {
            "description": "members I blocked, see BlockMember",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "channelMode": {
            "$ref": "#/components/schemas/ChannelMode"
          },
          "channels": {
            "description": "channels I am in (my sidebar), incl. DMs",
            "items": {
              "$ref": "#/components/schemas/Channel"
            },
            "type": "array"
          },
          "me": {
            "$ref": "#/components/schemas/Member"
          },
          "members": {
            "items": {
              "$ref": "#/components/schemas/Member"
            },
            "type": "array"
          },
          "mutedMemberIds": {
            "description": "members I muted, see MuteMember",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "readStates": {
            "items": {
              "$ref": "#/components/schemas/ReadState"
            },
            "type": "array"
          },
          "roles": {
            "description": "sorted by position",
            "items": {
              "$ref": "#/components/schemas/Role"
            },
            "type": "array"
          },
          "seq": {
            "description": "server seq at snapshot time; Subscribe from here",
            "format": "int64",
            "type": "string"
          }
        },
        "type": "object"
      },
      "InstallPluginRequest": {
        "description": "Installs or replaces <name>.js and starts it. The source must compile and run its top level within the time limit, or nothing changes. 256 KB max.",
        "properties": {
          "name": {
            "description": "a valid username; a trailing \".js\" is dropped",
            "type": "string"
          },
          "source": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "InstallPluginResponse": {
        "description": "",
        "properties": {
          "plugin": {
            "$ref": "#/components/schemas/Plugin"
          }
        },
        "type": "object"
      },
      "Invite": {
        "description": "",
        "properties": {
          "code": {
            "description": "10 characters, A-Z and 2-7",
            "type": "string"
          },
          "createdAtMs": {
            "format": "int64",
            "type": "string"
          },
          "createdBy": {
            "description": "member id",
            "type": "string"
          },
          "expiresAtMs": {
            "description": "0 = never",
            "format": "int64",
            "type": "string"
          },
          "maxUses": {
            "description": "0 = no limit",
            "format": "int32",
            "type": "integer"
          },
          "uses": {
            "description": "joins it allowed so far",
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "JoinChannelRequest": {
        "description": "OPT_IN mode only (FAILED_PRECONDITION in ROLES mode). TEXT channels only.",
        "properties": {
          "channelId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "JoinChannelResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "JoinRequest": {
        "description": "",
        "properties": {
          "answers": {
            "description": "to GetServerInfoResponse.join_questions, in order",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "avatarUrl": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "displayName": {
            "description": "Profile fields the user agreed to share with this community.",
            "type": "string"
          },
          "inviteCode": {
            "description": "checked only when the server is invite_only; case-insensitive",
            "type": "string"
          }
        },
        "type": "object"
      },
      "JoinResponse": {
        "description": "",
        "properties": {
          "created": {
            "description": "false if already a member",
            "type": "boolean"
          },
          "member": {
            "$ref": "#/components/schemas/Member"
          }
        },
        "type": "object"
      },
      "KickMemberRequest": {
        "description": "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).",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "KickMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "LeaveChannelRequest": {
        "description": "",
        "properties": {
          "channelId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "LeaveChannelResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "LeaveCommunityRequest": {
        "description": "Leave this community. The owner cannot leave. You may Join again later. Others get member_left.",
        "properties": {},
        "type": "object"
      },
      "LeaveCommunityResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "LinkPreview": {
        "description": "",
        "properties": {
          "description": {
            "type": "string"
          },
          "imageUrl": {
            "description": "relative to the server base URL; \"\" if none",
            "type": "string"
          },
          "siteName": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "description": "the link as written in the message",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListBansRequest": {
        "description": "Moderator or higher. Newest first.",
        "properties": {},
        "type": "object"
      },
      "ListBansResponse": {
        "description": "",
        "properties": {
          "bans": {
            "items": {
              "$ref": "#/components/schemas/Ban"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListBotsRequest": {
        "description": "Any member.",
        "properties": {},
        "type": "object"
      },
      "ListBotsResponse": {
        "description": "",
        "properties": {
          "bots": {
            "items": {
              "$ref": "#/components/schemas/Member"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListEmojiResponse": {
        "description": "",
        "properties": {
          "emoji": {
            "description": "sorted by name",
            "items": {
              "$ref": "#/components/schemas/CustomEmoji"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListInvitesRequest": {
        "description": "Every invite that was not revoked, newest first, including expired and used-up ones (compare expires_at_ms and uses to show their state).",
        "properties": {},
        "type": "object"
      },
      "ListInvitesResponse": {
        "description": "",
        "properties": {
          "invites": {
            "items": {
              "$ref": "#/components/schemas/Invite"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListMessagesRequest": {
        "description": "",
        "properties": {
          "afterId": {
            "description": "page forwards from this message (newer ones)",
            "type": "string"
          },
          "aroundId": {
            "description": "Set at most one of before_id, after_id and around_id. this message, about limit/2 older ones and the rest newer (jump to a search hit)",
            "type": "string"
          },
          "beforeId": {
            "description": "page backwards from this message; empty = newest",
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "limit": {
            "description": "default 50, max 200",
            "format": "int32",
            "type": "integer"
          },
          "threadRootId": {
            "description": "set to list a thread's replies",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListMessagesResponse": {
        "description": "",
        "properties": {
          "hasMore": {
            "description": "older messages exist before the first one",
            "type": "boolean"
          },
          "hasMoreAfter": {
            "description": "newer messages exist after the last one (after_id and around_id only)",
            "type": "boolean"
          },
          "messages": {
            "description": "oldest first",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListPluginsRequest": {
        "description": "Admin or owner only, like everything below.",
        "properties": {},
        "type": "object"
      },
      "ListPluginsResponse": {
        "description": "",
        "properties": {
          "plugins": {
            "items": {
              "$ref": "#/components/schemas/Plugin"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ListReportsRequest": {
        "description": "Moderator or higher. Open reports, newest first, about members the caller outranks (the owner sees reports about everyone, the owner included).",
        "properties": {},
        "type": "object"
      },
      "ListReportsResponse": {
        "description": "",
        "properties": {
          "reports": {
            "items": {
              "$ref": "#/components/schemas/Report"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "MarkReadRequest": {
        "description": "",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "seq": {
            "format": "int64",
            "type": "string"
          }
        },
        "type": "object"
      },
      "MarkReadResponse": {
        "description": "",
        "properties": {
          "readState": {
            "$ref": "#/components/schemas/ReadState"
          }
        },
        "type": "object"
      },
      "Member": {
        "description": "",
        "properties": {
          "avatarUrl": {
            "description": "may be empty",
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "id": {
            "description": "UUIDv7, local to this server",
            "type": "string"
          },
          "joinedAtMs": {
            "format": "int64",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/MemberKind"
          },
          "presence": {
            "$ref": "#/components/schemas/Presence"
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          },
          "roleIds": {
            "description": "custom roles (visibility only), see Role",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "statusEmoji": {
            "type": "string"
          },
          "statusText": {
            "description": "short custom status, may be empty",
            "type": "string"
          },
          "username": {
            "description": "unique on this server",
            "type": "string"
          }
        },
        "type": "object"
      },
      "MemberKind": {
        "description": "",
        "enum": [
          "MEMBER_KIND_UNSPECIFIED",
          "MEMBER_KIND_HUMAN",
          "MEMBER_KIND_BOT"
        ],
        "type": "string"
      },
      "MemberRole": {
        "description": "",
        "enum": [
          "MEMBER_ROLE_UNSPECIFIED",
          "MEMBER_ROLE_MEMBER",
          "MEMBER_ROLE_MODERATOR",
          "MEMBER_ROLE_ADMIN",
          "MEMBER_ROLE_OWNER"
        ],
        "type": "string"
      },
      "Message": {
        "description": "",
        "properties": {
          "attachments": {
            "items": {
              "$ref": "#/components/schemas/Attachment"
            },
            "type": "array"
          },
          "authorId": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "clientId": {
            "description": "echo of SendMessageRequest.client_id, for optimistic UI",
            "type": "string"
          },
          "createdAtMs": {
            "format": "int64",
            "type": "string"
          },
          "createdSeq": {
            "description": "seq of message_created; never changes. Compare this with last_read_seq.",
            "format": "int64",
            "type": "string"
          },
          "deleted": {
            "type": "boolean"
          },
          "editedAtMs": {
            "description": "0 if never edited",
            "format": "int64",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "lastReplyAtMs": {
            "format": "int64",
            "type": "string"
          },
          "reactions": {
            "items": {
              "$ref": "#/components/schemas/Reaction"
            },
            "type": "array"
          },
          "replyCount": {
            "description": "on a thread root",
            "format": "int32",
            "type": "integer"
          },
          "seq": {
            "description": "event seq that last changed this message (edits, reactions, replies bump it)",
            "format": "int64",
            "type": "string"
          },
          "text": {
            "description": "markdown subset: bold, italic, code, links, mentions <@member_id>",
            "type": "string"
          },
          "threadRootId": {
            "description": "set when this message is a thread reply",
            "type": "string"
          }
        },
        "type": "object"
      },
      "MuteMemberRequest": {
        "description": "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.",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "MuteMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "OpenDMRequest": {
        "description": "Any member, with 1 to 8 others. Returns the existing DM when one has exactly these members, and puts the caller back in it if they left it. PERMISSION_DENIED when the caller and one of the others blocked each other (either way).",
        "properties": {
          "memberIds": {
            "description": "the others; the caller is added",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "OpenDMResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "Plugin": {
        "description": "A JavaScript plugin in the plugins folder of the data directory. name is also its bot's username.",
        "properties": {
          "botId": {
            "description": "\"\" until it has loaded once",
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "error": {
            "description": "why it did not load, or its last hook failure",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "running": {
            "description": "loaded and taking hooks",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "Presence": {
        "description": "",
        "enum": [
          "PRESENCE_UNSPECIFIED",
          "PRESENCE_ONLINE",
          "PRESENCE_AWAY",
          "PRESENCE_OFFLINE"
        ],
        "type": "string"
      },
      "PresenceBatch": {
        "description": "Many presence changes at once, the latest per member. The server sends this instead of single presence events when several are waiting (a reconnect burst after a restart).",
        "properties": {
          "changes": {
            "items": {
              "$ref": "#/components/schemas/PresenceEvent"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "PresenceEvent": {
        "description": "",
        "properties": {
          "memberId": {
            "type": "string"
          },
          "presence": {
            "$ref": "#/components/schemas/Presence"
          }
        },
        "type": "object"
      },
      "ReactRequest": {
        "description": "At most 50 different emoji per message.",
        "properties": {
          "emoji": {
            "type": "string"
          },
          "messageId": {
            "type": "string"
          },
          "remove": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "ReactResponse": {
        "description": "",
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          }
        },
        "type": "object"
      },
      "Reaction": {
        "description": "",
        "properties": {
          "emoji": {
            "description": "unicode emoji or \":shortcode:\"",
            "type": "string"
          },
          "memberIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "ReadState": {
        "description": "",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "lastReadSeq": {
            "format": "int64",
            "type": "string"
          },
          "mentionCount": {
            "format": "int32",
            "type": "integer"
          },
          "unreadCount": {
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "RemoveChannelMemberRequest": {
        "description": "Removes a member from a private channel. Your own id leaves the channel, and in a DM (your own id only) hides it until someone writes there again; anyone else needs moderator or higher and a higher rank than theirs. The removed member gets channel_deleted (the channel is gone for them); the rest get channel_updated.",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "RemoveChannelMemberResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "Report": {
        "description": "",
        "properties": {
          "author": {
            "$ref": "#/components/schemas/Member",
            "description": "who wrote the message (may have left)"
          },
          "dm": {
            "description": "the message is in a DM; moderators cannot open it",
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "message": {
            "$ref": "#/components/schemas/Message",
            "description": "copy at report time: id, channel_id, author_id, text, created_at_ms, thread_root_id"
          },
          "note": {
            "type": "string"
          },
          "reportedAtMs": {
            "format": "int64",
            "type": "string"
          },
          "reporter": {
            "$ref": "#/components/schemas/Member"
          }
        },
        "type": "object"
      },
      "ReportMessageRequest": {
        "description": "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.",
        "properties": {
          "messageId": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ReportMessageResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "ResolveReportRequest": {
        "description": "Moderator or higher, for a report ListReports shows them. Resolving deletes every open report of that message, copies included.",
        "properties": {
          "reportId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ResolveReportResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "RevokeInviteRequest": {
        "description": "",
        "properties": {
          "code": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "RevokeInviteResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "Role": {
        "description": "A custom role, made by admins. Roles only decide which text channels a member can see (Channel.role_ids). MemberRole still decides permissions; call that one \"Permission\" in the UI and these \"Roles\".",
        "properties": {
          "color": {
            "description": "\"#rrggbb\" or empty",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "description": "unique, case-insensitive",
            "type": "string"
          },
          "position": {
            "description": "sort order, low first",
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "RotateBotTokenRequest": {
        "description": "Admin or owner only. The old token stops working at once.",
        "properties": {
          "botId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "RotateBotTokenResponse": {
        "description": "",
        "properties": {
          "token": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchRequest": {
        "description": "",
        "properties": {
          "channelId": {
            "description": "optional filter",
            "type": "string"
          },
          "limit": {
            "description": "default 20, max 100",
            "format": "int32",
            "type": "integer"
          },
          "query": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SearchResponse": {
        "description": "",
        "properties": {
          "messages": {
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SendMessageRequest": {
        "description": "Any member who can see the channel. Text up to 8,000 characters, at most 10 attachments. In a DM, PERMISSION_DENIED when the sender and another member blocked each other (either way). A DM message puts members who left that DM back in it (they get channel_updated). People may send 5 messages per second (burst 10); bots follow the server's -bot-rate and -bot-burst.",
        "properties": {
          "attachmentIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "channelId": {
            "type": "string"
          },
          "clientId": {
            "description": "idempotency key from the client",
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "threadRootId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SendMessageResponse": {
        "description": "",
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          }
        },
        "type": "object"
      },
      "SetChannelAccessRequest": {
        "description": "Who can see a TEXT channel, and whether OPT_IN joins new members to it. Admins only. Replaces role_ids.",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "isDefault": {
            "type": "boolean"
          },
          "roleIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SetChannelAccessResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "SetMemberRoleRequest": {
        "description": "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.",
        "properties": {
          "memberId": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          }
        },
        "type": "object"
      },
      "SetMemberRoleResponse": {
        "description": "",
        "properties": {
          "member": {
            "$ref": "#/components/schemas/Member"
          }
        },
        "type": "object"
      },
      "SetMemberRolesRequest": {
        "description": "Replaces the member's roles. Works for bots too.",
        "properties": {
          "memberId": {
            "type": "string"
          },
          "roleIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SetMemberRolesResponse": {
        "description": "",
        "properties": {
          "member": {
            "$ref": "#/components/schemas/Member"
          }
        },
        "type": "object"
      },
      "SetPluginEnabledRequest": {
        "description": "Disabling stops the plugin and keeps its file as <name>.js.disabled.",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SetPluginEnabledResponse": {
        "description": "",
        "properties": {
          "plugin": {
            "$ref": "#/components/schemas/Plugin"
          }
        },
        "type": "object"
      },
      "SetTypingRequest": {
        "description": "",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "threadRootId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SetTypingResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "SubscribeRequest": {
        "description": "",
        "properties": {
          "afterSeq": {
            "description": "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.",
            "format": "int64",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TypingEvent": {
        "description": "",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "memberId": {
            "type": "string"
          },
          "threadRootId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UnbanMemberRequest": {
        "description": "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.",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UnbanMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "UnblockMemberRequest": {
        "description": "",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UnblockMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "UnmuteMemberRequest": {
        "description": "",
        "properties": {
          "memberId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UnmuteMemberResponse": {
        "description": "",
        "properties": {},
        "type": "object"
      },
      "UpdateChannelRequest": {
        "description": "Admin or owner only. DMs cannot be updated.",
        "properties": {
          "category": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "headerImageId": {
            "description": "An uploaded image's id (POST /upload) for the header; \"\" removes it.",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "position": {
            "format": "int32",
            "type": "integer"
          },
          "topic": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateChannelResponse": {
        "description": "",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        },
        "type": "object"
      },
      "UpdateProfileRequest": {
        "description": "",
        "properties": {
          "avatarUrl": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "statusEmoji": {
            "type": "string"
          },
          "statusText": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateProfileResponse": {
        "description": "",
        "properties": {
          "member": {
            "$ref": "#/components/schemas/Member"
          }
        },
        "type": "object"
      },
      "UpdateRoleRequest": {
        "description": "",
        "properties": {
          "color": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "position": {
            "format": "int32",
            "type": "integer"
          },
          "roleId": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateRoleResponse": {
        "description": "",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        },
        "type": "object"
      },
      "UpdateServerSettingsRequest": {
        "description": "",
        "properties": {
          "bannerId": {
            "description": "an uploaded image's id (POST /upload); \"\" removes it",
            "type": "string"
          },
          "channelMode": {
            "$ref": "#/components/schemas/ChannelMode",
            "description": "UNSPECIFIED = leave as is"
          },
          "description": {
            "description": "Markdown; \"\" falls back to the server's -description",
            "type": "string"
          },
          "iconId": {
            "description": "rail icon, an uploaded image's id; \"\" falls back to -icon-url",
            "type": "string"
          },
          "inviteOnly": {
            "type": "boolean"
          },
          "retentionDays": {
            "description": "0 = keep forever, else 1 to 3650",
            "format": "int32",
            "type": "integer"
          },
          "setupDone": {
            "description": "true clears GetServerInfoResponse.needs_setup",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "UpdateServerSettingsResponse": {
        "description": "",
        "properties": {
          "banner": {
            "$ref": "#/components/schemas/Attachment"
          },
          "channelMode": {
            "$ref": "#/components/schemas/ChannelMode"
          },
          "description": {
            "type": "string"
          },
          "iconUrl": {
            "type": "string"
          },
          "inviteOnly": {
            "type": "boolean"
          },
          "retentionDays": {
            "format": "int32",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "UploadEmojiResponse": {
        "description": "",
        "properties": {
          "results": {
            "items": {
              "$ref": "#/components/schemas/EmojiUploadResult"
            },
            "type": "array"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearer": {
        "description": "A sign-in token made for this server, or a bot token (`sbb_...`). See [Sign-in and tokens](https://sblurp.chat/docs/api/auth).",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "externalDocs": {
    "url": "https://sblurp.chat/docs/api"
  },
  "info": {
    "description": "Every call of a Sblurp community server. The server speaks [Connect RPC](https://connectrpc.com): each call is a `POST` of a JSON body. JSON follows protojson: field names are lowerCamelCase, 64-bit numbers are strings and enums are their names. Generated from proto/sblurp/v1/sblurp.proto. Read the [API docs](https://sblurp.chat/docs/api) for the basics.",
    "title": "Sblurp community server API",
    "version": "1"
  },
  "openapi": "3.1.0",
  "paths": {
    "/sblurp.v1.BotService/CreateBot": {
      "post": {
        "description": "Admin or owner only. username is 2 to 32 of a-z, 0-9, _, . and -.",
        "operationId": "CreateBot",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBotRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateBotResponse"
                }
              }
            },
            "description": "CreateBotResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/DeleteBot": {
      "post": {
        "description": "Admin or owner only. The bot's messages stay; its username is freed.",
        "operationId": "DeleteBot",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteBotRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteBotResponse"
                }
              }
            },
            "description": "DeleteBotResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/DeletePlugin": {
      "post": {
        "description": "Deletes the file and the plugin's bot. The bot's messages stay.",
        "operationId": "DeletePlugin",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeletePluginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletePluginResponse"
                }
              }
            },
            "description": "DeletePluginResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Deletes the file and the plugin's bot",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/InstallPlugin": {
      "post": {
        "description": "Installs or replaces <name>.js and starts it. The source must compile and run its top level within the time limit, or nothing changes. 256 KB max.",
        "operationId": "InstallPlugin",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InstallPluginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstallPluginResponse"
                }
              }
            },
            "description": "InstallPluginResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Installs or replaces <name>.js and starts it",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/ListBots": {
      "post": {
        "description": "Any member.",
        "operationId": "ListBots",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListBotsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListBotsResponse"
                }
              }
            },
            "description": "ListBotsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/ListPlugins": {
      "post": {
        "description": "Admin or owner only, like everything below.",
        "operationId": "ListPlugins",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListPluginsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListPluginsResponse"
                }
              }
            },
            "description": "ListPluginsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only, like everything below",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/RotateBotToken": {
      "post": {
        "description": "Admin or owner only. The old token stops working at once.",
        "operationId": "RotateBotToken",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RotateBotTokenRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RotateBotTokenResponse"
                }
              }
            },
            "description": "RotateBotTokenResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.BotService/SetPluginEnabled": {
      "post": {
        "description": "Disabling stops the plugin and keeps its file as <name>.js.disabled.",
        "operationId": "SetPluginEnabled",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPluginEnabledRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetPluginEnabledResponse"
                }
              }
            },
            "description": "SetPluginEnabledResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Disabling stops the plugin and keeps its file as <name>.js.disabled",
        "tags": [
          "BotService"
        ]
      }
    },
    "/sblurp.v1.ChatService/AddChannelMembers": {
      "post": {
        "description": "Any member of a private channel may add others to it. The new members get channel_updated (treat it as an upsert).",
        "operationId": "AddChannelMembers",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddChannelMembersRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddChannelMembersResponse"
                }
              }
            },
            "description": "AddChannelMembersResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member of a private channel may add others to it",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/BrowseChannels": {
      "post": {
        "description": "Every TEXT channel I can see, joined or not (OPT_IN's \"Browse channels\").",
        "operationId": "BrowseChannels",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrowseChannelsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseChannelsResponse"
                }
              }
            },
            "description": "BrowseChannelsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Every TEXT channel I can see, joined or not (OPT_IN's \"Browse channels\")",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/CreateChannel": {
      "post": {
        "description": "Admin or owner only. Names are normalized: \"Book Club\" becomes book-club.",
        "operationId": "CreateChannel",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChannelRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateChannelResponse"
                }
              }
            },
            "description": "CreateChannelResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/DeleteMessage": {
      "post": {
        "description": "Your own messages, or anyone's for a moderator or higher.",
        "operationId": "DeleteMessage",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteMessageResponse"
                }
              }
            },
            "description": "DeleteMessageResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Your own messages, or anyone's for a moderator or higher",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/EditMessage": {
      "post": {
        "description": "Your own messages only.",
        "operationId": "EditMessage",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EditMessageResponse"
                }
              }
            },
            "description": "EditMessageResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Your own messages only",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/GetLinkPreviews": {
      "post": {
        "description": "Previews of up to 5 http(s) links in a message I can see. The server fetches each page once, caches it for a day and serves the image itself, so viewers never contact the linked site. Links that have no preview are left out.",
        "operationId": "GetLinkPreviews",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetLinkPreviewsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetLinkPreviewsResponse"
                }
              }
            },
            "description": "GetLinkPreviewsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Previews of up to 5 http(s) links in a message I can see",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/JoinChannel": {
      "post": {
        "description": "OPT_IN mode only (FAILED_PRECONDITION in ROLES mode). TEXT channels only.",
        "operationId": "JoinChannel",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JoinChannelRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JoinChannelResponse"
                }
              }
            },
            "description": "JoinChannelResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "OPT_IN mode only (FAILED_PRECONDITION in ROLES mode)",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/LeaveChannel": {
      "post": {
        "description": "",
        "operationId": "LeaveChannel",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveChannelRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveChannelResponse"
                }
              }
            },
            "description": "LeaveChannelResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "LeaveChannel",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/ListMessages": {
      "post": {
        "description": "",
        "operationId": "ListMessages",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListMessagesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListMessagesResponse"
                }
              }
            },
            "description": "ListMessagesResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "ListMessages",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/MarkRead": {
      "post": {
        "description": "",
        "operationId": "MarkRead",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MarkReadRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarkReadResponse"
                }
              }
            },
            "description": "MarkReadResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "MarkRead",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/OpenDM": {
      "post": {
        "description": "Any member, with 1 to 8 others. Returns the existing DM when one has exactly these members, and puts the caller back in it if they left it. PERMISSION_DENIED when the caller and one of the others blocked each other (either way).",
        "operationId": "OpenDM",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenDMRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenDMResponse"
                }
              }
            },
            "description": "OpenDMResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member, with 1 to 8 others",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/React": {
      "post": {
        "description": "At most 50 different emoji per message.",
        "operationId": "React",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReactRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactResponse"
                }
              }
            },
            "description": "ReactResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "At most 50 different emoji per message",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/RemoveChannelMember": {
      "post": {
        "description": "Removes a member from a private channel. Your own id leaves the channel, and in a DM (your own id only) hides it until someone writes there again; anyone else needs moderator or higher and a higher rank than theirs. The removed member gets channel_deleted (the channel is gone for them); the rest get channel_updated.",
        "operationId": "RemoveChannelMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveChannelMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveChannelMemberResponse"
                }
              }
            },
            "description": "RemoveChannelMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Removes a member from a private channel",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/Search": {
      "post": {
        "description": "",
        "operationId": "Search",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            },
            "description": "SearchResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Search",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/SendMessage": {
      "post": {
        "description": "Any member who can see the channel. Text up to 8,000 characters, at most 10 attachments. In a DM, PERMISSION_DENIED when the sender and another member blocked each other (either way). A DM message puts members who left that DM back in it (they get channel_updated). People may send 5 messages per second (burst 10); bots follow the server's -bot-rate and -bot-burst.",
        "operationId": "SendMessage",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendMessageResponse"
                }
              }
            },
            "description": "SendMessageResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member who can see the channel",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/SetChannelAccess": {
      "post": {
        "description": "Who can see a TEXT channel, and whether OPT_IN joins new members to it. Admins only. Replaces role_ids.",
        "operationId": "SetChannelAccess",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetChannelAccessRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetChannelAccessResponse"
                }
              }
            },
            "description": "SetChannelAccessResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Who can see a TEXT channel, and whether OPT_IN joins new members to it",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/SetTyping": {
      "post": {
        "description": "",
        "operationId": "SetTyping",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetTypingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetTypingResponse"
                }
              }
            },
            "description": "SetTypingResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "SetTyping",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ChatService/UpdateChannel": {
      "post": {
        "description": "Admin or owner only. DMs cannot be updated.",
        "operationId": "UpdateChannel",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChannelRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateChannelResponse"
                }
              }
            },
            "description": "UpdateChannelResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "ChatService"
        ]
      }
    },
    "/sblurp.v1.ServerService/BanMember": {
      "post": {
        "description": "Like KickMember, and the person's login (iss, sub) cannot Join again.",
        "operationId": "BanMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BanMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BanMemberResponse"
                }
              }
            },
            "description": "BanMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Like KickMember, and the person's login (iss, sub) cannot Join again",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/BlockMember": {
      "post": {
        "description": "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.",
        "operationId": "BlockMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlockMemberResponse"
                }
              }
            },
            "description": "BlockMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member, for themselves only",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/CreateInvite": {
      "post": {
        "description": "",
        "operationId": "CreateInvite",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateInviteResponse"
                }
              }
            },
            "description": "CreateInviteResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "CreateInvite",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/CreateRole": {
      "post": {
        "description": "",
        "operationId": "CreateRole",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateRoleResponse"
                }
              }
            },
            "description": "CreateRoleResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "CreateRole",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/DeleteEmoji": {
      "post": {
        "description": "Moderator or higher.",
        "operationId": "DeleteEmoji",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteEmojiRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteEmojiResponse"
                }
              }
            },
            "description": "DeleteEmojiResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/DeleteRole": {
      "post": {
        "description": "Fails with FAILED_PRECONDITION while the role still limits a channel, so deleting a role never opens a channel to everyone.",
        "operationId": "DeleteRole",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteRoleResponse"
                }
              }
            },
            "description": "DeleteRoleResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Fails with FAILED_PRECONDITION while the role still limits a channel, so deleting a role never opens a channel to everyone",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/GetMe": {
      "post": {
        "description": "",
        "operationId": "GetMe",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetMeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMeResponse"
                }
              }
            },
            "description": "GetMeResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "GetMe",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/GetServerInfo": {
      "post": {
        "description": "",
        "operationId": "GetServerInfo",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetServerInfoRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetServerInfoResponse"
                }
              }
            },
            "description": "GetServerInfoResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "security": [],
        "summary": "GetServerInfo",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/GetState": {
      "post": {
        "description": "Everything a client needs to render the sidebar after connecting.",
        "operationId": "GetState",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetStateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetStateResponse"
                }
              }
            },
            "description": "GetStateResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Everything a client needs to render the sidebar after connecting",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/Join": {
      "post": {
        "description": "",
        "operationId": "Join",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JoinRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JoinResponse"
                }
              }
            },
            "description": "JoinResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Join",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/KickMember": {
      "post": {
        "description": "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).",
        "operationId": "KickMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KickMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KickMemberResponse"
                }
              }
            },
            "description": "KickMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher, and a higher rank than the target",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/LeaveCommunity": {
      "post": {
        "description": "Leave this community. The owner cannot leave. You may Join again later. Others get member_left.",
        "operationId": "LeaveCommunity",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveCommunityRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaveCommunityResponse"
                }
              }
            },
            "description": "LeaveCommunityResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Leave this community",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/ListBans": {
      "post": {
        "description": "Moderator or higher. Newest first.",
        "operationId": "ListBans",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListBansRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListBansResponse"
                }
              }
            },
            "description": "ListBansResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/ListInvites": {
      "post": {
        "description": "Every invite that was not revoked, newest first, including expired and used-up ones (compare expires_at_ms and uses to show their state).",
        "operationId": "ListInvites",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListInvitesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListInvitesResponse"
                }
              }
            },
            "description": "ListInvitesResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Every invite that was not revoked, newest first, including expired and used-up ones (compare expires_at_ms and uses to show their state)",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/ListReports": {
      "post": {
        "description": "Moderator or higher. Open reports, newest first, about members the caller outranks (the owner sees reports about everyone, the owner included).",
        "operationId": "ListReports",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListReportsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListReportsResponse"
                }
              }
            },
            "description": "ListReportsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/MuteMember": {
      "post": {
        "description": "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.",
        "operationId": "MuteMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MuteMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MuteMemberResponse"
                }
              }
            },
            "description": "MuteMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member, for themselves only",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/ReportMessage": {
      "post": {
        "description": "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.",
        "operationId": "ReportMessage",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportMessageResponse"
                }
              }
            },
            "description": "ReportMessageResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Any member, about a message they can see that someone else wrote, with an optional note (up to 1,000 characters)",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/ResolveReport": {
      "post": {
        "description": "Moderator or higher, for a report ListReports shows them. Resolving deletes every open report of that message, copies included.",
        "operationId": "ResolveReport",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResolveReportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolveReportResponse"
                }
              }
            },
            "description": "ResolveReportResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher, for a report ListReports shows them",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/RevokeInvite": {
      "post": {
        "description": "",
        "operationId": "RevokeInvite",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevokeInviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeInviteResponse"
                }
              }
            },
            "description": "RevokeInviteResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "RevokeInvite",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/SetMemberRole": {
      "post": {
        "description": "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.",
        "operationId": "SetMemberRole",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMemberRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetMemberRoleResponse"
                }
              }
            },
            "description": "SetMemberRoleResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Admin or owner only",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/SetMemberRoles": {
      "post": {
        "description": "Replaces the member's roles. Works for bots too.",
        "operationId": "SetMemberRoles",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMemberRolesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetMemberRolesResponse"
                }
              }
            },
            "description": "SetMemberRolesResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Replaces the member's roles",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/Subscribe": {
      "post": {
        "description": "**Server stream. You cannot call it from this page or with plain JSON.** Use a Connect client such as connect-es or connect-go. Each frame is an [Event](https://sblurp.chat/docs/api/types#event). See [Events](https://sblurp.chat/docs/api/events).\n\nServer stream. Browsers support server streams over Connect; there is deliberately no bidirectional stream. Clients send with unary calls.",
        "operationId": "Subscribe",
        "requestBody": {
          "content": {
            "application/connect+json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/connect+json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            },
            "description": "A stream of Event messages"
          }
        },
        "summary": "Server stream (not callable from the explorer)",
        "tags": [
          "ServerService"
        ],
        "x-connect-server-stream": true
      }
    },
    "/sblurp.v1.ServerService/UnbanMember": {
      "post": {
        "description": "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.",
        "operationId": "UnbanMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnbanMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnbanMemberResponse"
                }
              }
            },
            "description": "UnbanMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "Moderator or higher",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/UnblockMember": {
      "post": {
        "description": "",
        "operationId": "UnblockMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnblockMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnblockMemberResponse"
                }
              }
            },
            "description": "UnblockMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "UnblockMember",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/UnmuteMember": {
      "post": {
        "description": "",
        "operationId": "UnmuteMember",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnmuteMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnmuteMemberResponse"
                }
              }
            },
            "description": "UnmuteMemberResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "UnmuteMember",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/UpdateProfile": {
      "post": {
        "description": "",
        "operationId": "UpdateProfile",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProfileRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateProfileResponse"
                }
              }
            },
            "description": "UpdateProfileResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "UpdateProfile",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/UpdateRole": {
      "post": {
        "description": "",
        "operationId": "UpdateRole",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateRoleResponse"
                }
              }
            },
            "description": "UpdateRoleResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "UpdateRole",
        "tags": [
          "ServerService"
        ]
      }
    },
    "/sblurp.v1.ServerService/UpdateServerSettings": {
      "post": {
        "description": "",
        "operationId": "UpdateServerSettings",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateServerSettingsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateServerSettingsResponse"
                }
              }
            },
            "description": "UpdateServerSettingsResponse"
          },
          "default": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectError"
                }
              }
            },
            "description": "Error"
          }
        },
        "summary": "UpdateServerSettings",
        "tags": [
          "ServerService"
        ]
      }
    }
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "servers": [
    {
      "description": "A community server",
      "url": "{server}",
      "variables": {
        "server": {
          "default": "https://community.sblurp.chat",
          "description": "The community server's base URL"
        }
      }
    }
  ],
  "tags": [
    {
      "externalDocs": {
        "description": "ServerService reference",
        "url": "https://sblurp.chat/docs/api/server-service"
      },
      "name": "ServerService"
    },
    {
      "externalDocs": {
        "description": "ChatService reference",
        "url": "https://sblurp.chat/docs/api/chat-service"
      },
      "name": "ChatService"
    },
    {
      "externalDocs": {
        "description": "BotService reference",
        "url": "https://sblurp.chat/docs/api/bot-service"
      },
      "name": "BotService"
    }
  ]
}
