ggbro Docs
← Home

ggbro Developer Documentation

Build bots, integrations, and tools for your ggbro community.

Need a single URL for another AI? Use /developers/reference.md. It is a raw Markdown reference covering bot creation, Socket.IO events, embeds, interactions, and the full REST route catalog.

Introduction

Platform: ggbro is a custom Socket.IO-based chat platform with its own protocol. Bots connect via Socket.IO v4 for real-time events and use a REST API for data fetching. Use raw python-socketio / socket.io-client, or the single-file Python helper at /developers/ggbro.py (also: music_bot.py, welcome_bot.py).

Base URL: https://ggbro.app
API Base: https://ggbro.app/api/v1  (/api/v1)
Socket.IO Gateway: https://ggbro.app (or direct https://ws.ggbro.app)
Discovery: GET https://ggbro.app/api/v1 or GET /api/v1/platform/config

API version: All REST routes live under /api/v1. Future breaking changes will ship as /api/v2 while v1 stays stable. Channel webhook execute URLs use /api/webhooks/:id/:token (not versioned in the path).

Product tour: ggbro Features

The ggbro Bot API allows you to create bots that:

Technology Stack

ComponentTechnologyPackage to Install
Desktop / Web UIElectron + React (TypeScript), Tailwind CSS
BackendNode.js + Express (TypeScript), SQLite
Real-time GatewaySocket.IO v4Python: python-socketio[client] — Node.js: socket.io-client
REST APIHTTP JSONPython: requests — Node.js: axios or built-in fetch
Voice / mediaWebRTCBrowser / Electron APIs
AuthenticationBearer token (JWT)Passed in headers (REST) and handshake auth (Socket.IO)

Quick Start

Here's the fastest way to get a bot running:

  1. Go to the Developer Portal or ggbro app → User SettingsApplications
  2. Create an Application — this automatically creates a bot user and gives you a token
  3. Copy the Bot Token — you'll need it to authenticate
  4. Add the bot to a server you own or admin
  5. Connect via Socket.IO, register slash commands, and start responding!

Minimal Python Bot (5 lines to connect)

bash Install dependencies
pip install "python-socketio[client]" requests
Python minimal_bot.py
import socketio sio = socketio.Client() @sio.event def connect(): print("Connected to ggbro!") sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"}) sio.wait()

Minimal Node.js Bot

bash Install dependencies
npm install socket.io-client
JavaScript minimal_bot.js
const { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } }); socket.on("connect", () => console.log("Connected to ggbro!"));

Authentication

All API requests require a Bearer token in the Authorization header:

HTTP Authentication Header
Authorization: Bearer YOUR_BOT_TOKEN

For Socket.IO connections, pass the token in the handshake auth object:

Python Socket.IO Auth
import socketio sio = socketio.Client(reconnection=True, reconnection_delay=1) sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"})
JavaScript Socket.IO Auth
const { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } });
Keep your token secret! Anyone with your bot token can control your bot. If compromised, regenerate it immediately from the Developer Portal.

Creating a Bot

Create an application via the Developer Portal UI, or via the REST API:

POST /api/v1/oauth2/applications

Create a new application. Automatically creates a bot user account and returns a bot token.

JSON Request Body
{ "name": "My Awesome Bot", "description": "A bot that does cool things" }
JSON Response
{ "application": { "id": "abc123...", "name": "My Awesome Bot", "bot_user_id": "bot456...", "client_secret": "hex...", "default_permissions": 8895488, "invite_permission_locked": false }, "bot_token": "eyJhbGciOi..." }

The bot user starts offline until it connects. Default invite permissions are least-privilege (8895488); change them via the portal or PATCH .../permissions.

Application Management Endpoints

MethodPathDescription
GET/api/v1/oauth2/applicationsList your applications
GET/api/v1/oauth2/applications/:appIdGet application details
PUT/api/v1/oauth2/applications/:appIdUpdate application name and description
POST/api/v1/oauth2/applications/:appId/tokenRegenerate the bot token
DELETE/api/v1/oauth2/applications/:appIdDelete the application and its bot user
POST/api/v1/oauth2/applications/:appId/avatarUpload bot avatar (multipart, field: avatar)
POST/api/v1/oauth2/applications/:appId/bannerUpload bot banner (multipart, field: banner)
PATCH/api/v1/oauth2/applications/:appId/banner-offsetSet banner offset position
PATCH/api/v1/oauth2/applications/:appId/public-inviteToggle public invite on/off
PATCH/api/v1/oauth2/applications/:appId/permissionsSet default invite permissions (default_permissions, invite_permission_locked)

Bot Command Management

MethodPathDescription
GET/api/v1/oauth2/applications/:appId/commandsList all commands registered for this bot
POST/api/v1/oauth2/applications/:appId/commandsCreate a new command for this bot
PUT/api/v1/oauth2/applications/:appId/commands/:cmdIdUpdate a specific command
DELETE/api/v1/oauth2/applications/:appId/commands/:cmdIdDelete a specific command
GET/api/v1/oauth2/servers/:serverId/commandsList all bot commands available in a server

Adding Bot to a Server

Discord-like invite flow: the permissions bitfield in the invite link is what gets granted when a server admin authorizes. New bots start offline and with a least-privilege default (8895488) — not Administrator. Set defaults (including Administrator) in the Developer Portal or via PATCH .../permissions.

PATCH /api/v1/oauth2/applications/:appId/permissions

Update the app's default invite permissions (owner token).
Body: {"default_permissions": 512, "invite_permission_locked": false}
If ADMINISTRATOR (512) is set, it is stored alone (grants all at runtime). Changing defaults only affects new invite links — existing server bot roles are unchanged.
Response: {"default_permissions", "invite_permission_locked", "permission_labels"}

POST /api/v1/oauth2/applications/:appId/servers/:serverId

Add the bot to a server (owner must also be admin/owner of that server). Body optional: {"permissions": <bitfield>}. Creates a managed role named after the bot (without the [BOT] tag) with those permissions. The role cannot be assigned to other members.

DELETE /api/v1/oauth2/applications/:appId/servers/:serverId

Remove the bot from a server. The managed bot role is kept (Discord-like) until a server admin deletes it in Server Settings.

GET /api/v1/oauth2/applications/:appId/servers

List servers the bot is in, including each server's current bot_permissions / permission_labels (read-only for the bot owner). Bot owners cannot remotely change a server's bot role — only that server's admins can, via role settings.

PATCH /api/v1/oauth2/applications/:appId/servers/:serverId/permissions

Disabled (403). Bot owners cannot change permissions on servers. Server admins edit the bot's managed role in Server Settings → Roles.

GET /api/v1/oauth2/invite/:botUserId

Public invite card (no auth). Includes default_permissions, invite_permission_locked, permission_labels, and permission_flags.

POST /api/v1/oauth2/invite/:botUserId/add/:serverId

Authorize the bot onto a server (caller must be server admin/owner; respects public_invite). Body: {"permissions": <bitfield>} — typically the value from the invite URL. If the bot is already in the server, updates the managed role permissions instead. If locked, the bitfield must match the app defaults.

Socket.IO Gateway

Bots connect to the real-time gateway using Socket.IO v4. The gateway provides live events for messages, interactions, typing, presence, and voice.

Important: This is a standard Socket.IO v4 server. Use python-socketio (Python) or socket.io-client (Node.js). Do not use raw WebSocket libraries — Socket.IO has its own protocol on top of WebSocket.

Connecting & Joining Rooms

After connecting, you must join server and channel rooms to receive events:

Python Connect and Join Rooms
import socketio sio = socketio.Client(reconnection=True) @sio.event def connect(): print("Connected!") # Join a server room to receive member events, typing, etc. sio.emit("server:join", "YOUR_SERVER_ID") # Join a channel room to receive messages in that channel sio.emit("channel:join", "YOUR_CHANNEL_ID") sio.connect("https://ggbro.app", auth={"token": "YOUR_BOT_TOKEN"}) sio.wait()
JavaScript Connect and Join Rooms
const { io } = require("socket.io-client"); const socket = io("https://ggbro.app", { auth: { token: "YOUR_BOT_TOKEN" } }); socket.on("connect", () => { console.log("Connected!"); socket.emit("server:join", "YOUR_SERVER_ID"); socket.emit("channel:join", "YOUR_CHANNEL_ID"); });

Room Events

Emit EventPayloadDescription
server:join"serverId" (string)Join a server room to receive member/presence events
channel:join"channelId" (string)Join a channel room to receive message:new events

Slash Commands

Slash commands let users interact with your bot by typing /commandname. The client shows a Discord-style picker: left rail of bots/apps, commands grouped by bot, plus frequently used. Selecting a command with options opens an option builder — options appear sequentially after the command is selected (not while browsing the list); Tab cycles fields; USER / CHANNEL / ROLE / MENTIONABLE options use searchable entity pickers. Option types mirror Discord’s Application Command Option Types.

Client autocomplete ranking: when the user types a query (e.g. /play), matches are ranked exact name → name prefix → name contains → description. Tab/Enter selects the top hit — so /play beats a description match like /np (“Now playing”).

Registering Commands

Register commands via REST using your bot token. This replaces all existing global commands for the app (max 50):

POST /api/v1/oauth2/commands

Bulk register slash commands. Requires bot token auth. Body: array of command objects.

Owner/dashboard alternatives: GET/POST /api/v1/oauth2/applications/:appId/commands, PUT/DELETE .../commands/:cmdId.

Client list: GET /api/v1/oauth2/servers/:serverId/commands returns every bot command in that server (includes bot_id, bot_name, bot_avatar, app_name).

Command Object

FieldTypeDescription
namestring1–32 chars, ^[\w-]{1,32}$, stored lowercased
descriptionstringShown in autocomplete
optionsarrayUp to 25 typed option definitions (see below)
server_idstring?Optional server scope (owner registration only)

Option Types (Discord-compatible)

TypeCodeAliasesParsed value
STRING3string, str, textstring (mentions unwrapped to UUID)
INTEGER4integer, intnumber (int) when parseable
BOOLEAN5boolean, booltrue/false from true/1/yes/y/on or false/0/no/n/off
USER6user, memberuser UUID (from <@id> or raw id)
CHANNEL7channelchannel UUID (from <#id> or raw id)
ROLE8rolerole UUID (from <@&id> or raw id)
MENTIONABLE9mentionableuser or role UUID
NUMBER10number, float, doublefloat when parseable

Missing/unknown type defaults to STRING (3). The desktop/web client also infers USER/CHANNEL/ROLE from common option names (user, member, channel, role, …) when a bot registered them as plain STRING.

Option Fields

FieldTypeDescription
namestringRequired. 1–32 chars, ^[\w-]{1,32}$, lowercased
descriptionstringMax 100 chars
typenumber | stringType code or alias above
requiredbooleanDefault false
choicesarray?Up to 25 {name, value} pairs (value string or number)
autocompleteboolean?Stored as true when set (UI hint; live bot autocomplete events not yet sent)
min_value / max_valuenumber?Numeric bounds (INTEGER / NUMBER)
max_lengthnumber?STRING max length (clamped 1–6000)
channel_typesnumber[]?CHANNEL filter hint (up to 10 numbers)

How Clients Send Arguments

Bot handler rule: always prefer data.options.<name> (or SDK ctx.get_option('name')) over raw data.args. Example: for /play, use options.query — never search using a string that still contains --query. args is the clean positional text (e.g. ovetto 7amra) for simple single-string commands.
Python Register Typed Commands
import requests BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" commands = [ {"name": "ping", "description": "Check if the bot is alive", "options": []}, { "name": "play", "description": "Play a song", "options": [ { "name": "query", "description": "YouTube URL or search term", "type": "string", # or 3 "required": True, "max_length": 200, }, { "name": "volume", "description": "Playback volume", "type": 4, # INTEGER "required": False, "min_value": 0, "max_value": 200, "choices": [ {"name": "Quiet", "value": 50}, {"name": "Normal", "value": 100}, {"name": "Loud", "value": 150}, ], }, ], }, { "name": "timeout", "description": "Timeout a member", "options": [ {"name": "user", "description": "Member to timeout", "type": "user", "required": True}, {"name": "minutes", "description": "Duration in minutes", "type": "integer", "required": True, "min_value": 1, "max_value": 40320}, {"name": "reason", "description": "Moderation reason", "type": 3, "required": False}, ], }, { "name": "announce", "description": "Post to a channel", "options": [ {"name": "channel", "type": "channel", "description": "Target channel", "required": True}, {"name": "role", "type": "role", "description": "Role to ping", "required": False}, {"name": "message", "type": "string", "description": "Announcement text", "required": True}, {"name": "silent", "type": "boolean", "description": "Suppress mentions", "required": False}, ], }, ] r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=commands, headers={"Authorization": f"Bearer {BOT_TOKEN}"}, ) print("Commands registered!" if r.ok else f"Failed: {r.text}")
JavaScript Register Typed Commands
const axios = require("axios"); const BOT_TOKEN = "YOUR_BOT_TOKEN"; const API_URL = "https://ggbro.app"; axios.post(`${API_URL}/api/v1/oauth2/commands`, [ { name: "ping", description: "Check bot latency", options: [] }, { name: "ban", description: "Ban a member", options: [ { name: "user", description: "User to ban", type: "user", required: true }, { name: "reason", description: "Ban reason", type: 3, required: false, choices: [ { name: "Spam", value: "spam" }, { name: "Harassment", value: "harassment" }, { name: "Other", value: "other" }, ], }, { name: "silent", description: "Skip public notice", type: "boolean" }, ], }, { name: "move", description: "Move a user to a voice channel", options: [ { name: "user", type: 6, description: "Member", required: true }, { name: "channel", type: 7, description: "Voice channel", required: true, channel_types: [2] }, ], }, ], { headers: { Authorization: `Bearer ${BOT_TOKEN}` }, }).then(() => console.log("Commands registered!"));

Receiving & Responding to Interactions

When a user runs a slash command, your bot receives interaction:create with a typed options object. Respond with interaction:response:

JSON interaction:create (slash_command)
{ "id": "interaction-uuid", "type": "slash_command", "command": "play", "args": "ovetto 7amra", "options": { "query": "ovetto 7amra" }, "channel_id": "channel-uuid", "server_id": "server-uuid", "message_id": "original-user-message-id", "user": { "id": "user-uuid", "username": "JadaDev" } }
Python Handle Typed Options
@sio.on("interaction:create") def on_interaction(data): if data.get("type") != "slash_command": return cmd = (data.get("command") or "").lower() opts = data.get("options") or {} if cmd == "ping": sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": "Pong! 🏓", }) elif cmd == "timeout": target = opts.get("user") # UUID string minutes = opts.get("minutes") # int reason = opts.get("reason") or "No reason" sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": f"Timed out {target} for {minutes}m ({reason})", })
JavaScript Handle Typed Options
socket.on("interaction:create", (data) => { if (data.type !== "slash_command") return; const cmd = (data.command || "").toLowerCase(); const opts = data.options || {}; if (cmd === "announce") { const channelId = opts.channel; const silent = opts.silent === true; socket.emit("interaction:response", { interaction_id: data.id, channel_id: data.channel_id, content: silent ? "Queued silent announce." : `Will post to <#${channelId}>`, }); } });
Key fields:
data.id → pass as interaction_id in the response
data.command → slash command name
data.args → clean positional text after the command (e.g. ovetto 7amra; never includes --__bot or raw --option flags after server normalize)
data.options → typed map: strings, numbers, and booleans (IDs already unwrapped) — prefer this
data.channel_id / data.server_id / data.user / data.message_id → context
Python helper: /developers/ggbro.py v1.2+ exposes ctx.options, ctx.args, and ctx.get_option('query', ctx.args). Use that for /play-style commands so multi-word queries never include flag text.

Sending Messages

Bots can send messages to any channel they have access to. Use the bot:send_message Socket.IO event:

Ack payloads: bot:send_message, interaction:response, and bot:edit_message all return an acknowledgement payload with {ok, message_id, message}. Store the returned message_id if you plan to edit the message later.
Python Send a Message
# Send a simple text message sio.emit("bot:send_message", { "channel_id": "YOUR_CHANNEL_ID", "content": "Hello from my bot! 🤖" })
JavaScript Send a Message
// Send a simple text message socket.emit("bot:send_message", { channel_id: "YOUR_CHANNEL_ID", content: "Hello from my bot! 🤖" });

Editing a Bot Message

Bot-authored channel messages can be edited later with bot:edit_message. Edited messages automatically show an edited marker and timestamp in the client UI.

JavaScript Edit a Previously Sent Message
socket.emit("bot:edit_message", { channel_id: channelId, message_id: existingMessageId, content: "Updated content", embeds: [{ title: "Updated Panel", description: "This embed was edited in place.", color: "#23A55A" }] });

You can also send messages via the REST API:

POST /api/v1/messages/:channelId/upload

Send a message (with optional file attachments). Use form data with a content field.

Embeds & Components

Bots can send rich embed messages — similar to Discord embeds. Embeds display a colored sidebar, title, description, fields grid, images, and footer. Use them for /serverinfo, now-playing cards, leaderboards, help menus, and more.

Embed Object

FieldTypeDescription
titlestring?Embed title (rendered as a link if url is set)
descriptionstring?Main text body
urlstring?URL the title links to
colorstring?Left border colour as hex string, e.g. "#5865F2"
fieldsarray?Array of {name: string, value: string, inline?: boolean}
thumbnailstring?Small image URL (top-right corner)
imagestring?Large image URL (bottom of embed)
footerobject?{text: string, icon_url?: string}
authorobject?{name: string, url?: string, icon_url?: string}
timestampstring?ISO-8601 timestamp shown next to footer
componentsarray?Interactive components rendered under the embed. Supported types: button, string_select, channel_select

Interactive Components

TypeRequired FieldsNotes
button{type, label, custom_id}Use style = primary, secondary, success, danger, or link. Link buttons use url instead of custom_id.
string_select{type, custom_id, options[]}Each option supports {label, value, description?, emoji?, default?}.
channel_select{type, custom_id}Use channel_types to limit results, for example ["text"].

Sending Embeds

Pass an embeds array in either bot:send_message or interaction:response. You can include content alongside embeds, or send embeds only (with empty content):

Python Send an Embed via bot:send_message
sio.emit("bot:send_message", { "channel_id": channel_id, "content": "", "embeds": [{ "title": "Hello World", "description": "This is a rich embed message!", "color": "#5865F2", "fields": [ {"name": "Field 1", "value": "Some value", "inline": True}, {"name": "Field 2", "value": "Another value", "inline": True}, ], "footer": {"text": "Powered by ggbro"} }] })
Python Reply to a Slash Command with an Embed
@sio.on("interaction:create") def on_interaction(data): if data["command"] == "info": sio.emit("interaction:response", { "interaction_id": data["id"], "channel_id": data["channel_id"], "content": "", "embeds": [{ "title": "Bot Info", "description": "I'm a bot built for ggbro!", "color": "#23a55a", "author": {"name": "My Bot"}, "timestamp": datetime.utcnow().isoformat() }] })
JavaScript Send an Embed
socket.emit("bot:send_message", { channel_id: channelId, content: "", embeds: [{ title: "Now Playing 🎵", description: `Duration: 3:45 | Requested by JadaDev`, color: "#FF0000", thumbnail: "https://i.ytimg.com/vi/.../hqdefault.jpg", author: { name: "Music Bot" }, footer: { text: "Queue: 5 songs remaining" }, components: [ { type: "button", custom_id: "music:pause", label: "Pause", style: "secondary", emoji: { name: "⏸️" } }, { type: "button", custom_id: "music:skip", label: "Skip", style: "primary", emoji: { name: "⏭️" } }, { type: "string_select", custom_id: "music:volume", placeholder: "Set volume", options: [ { label: "50%", value: "50" }, { label: "100%", value: "100", default: true }, { label: "150%", value: "150" } ] }, { type: "channel_select", custom_id: "music:panel-channel", placeholder: "Move panel to a text channel", channel_types: ["text"] } ] }] });

Handling Component Interactions

When a user presses a button or changes a select menu, your bot receives another interaction:create event with type = "message_component".

JavaScript Handle Button and Select Interactions
socket.on("interaction:create", async (data) => { if (data.type !== "message_component") return; if (data.custom_id === "music:skip") { // Update your state, then edit the original control message socket.emit("bot:edit_message", { channel_id: data.channel_id, message_id: data.message_id, embeds: [{ title: "Skipped", description: "Queue advanced.", color: "#5865F2" }] }); } if (data.custom_id === "music:panel-channel") { const selected = data.selected_channels?.[0]; if (!selected) return; socket.emit("bot:send_message", { channel_id: selected.id, embeds: [{ title: "Panel moved", description: "Controls are now posted in this channel." }] }); } });

File Uploads & Attachments

Bots and users can attach files via multipart upload. Prefer Bot REST for text/embeds; use upload for binary attachments.

POST /api/v1/messages/:channelId/upload

Multipart form-data. Fields: content (optional text), files / file parts, optional replyTo, optional remoteUrl (Tenor / direct GIF URL).

Python Upload Attachment
import requests r = requests.post( f"{API_URL}/api/v1/messages/{channel_id}/upload", headers={"Authorization": f"Bearer {BOT_TOKEN}"}, data={"content": "Here is a file"}, files=[("files", ("report.png", open("report.png", "rb"), "image/png"))], ) print(r.json())
JSON Attachment shape on messages
{ "id": "attachment-uuid", "filename": "/uploads/attachments/...", "original_name": "report.png", "mime_type": "image/png", "size": 12345 }

Bot REST API (/api/v1/oauth2/bot/...)

Bot-token-only HTTP surface (requires is_bot). Prefer these over user routes when writing bots. Base path: /api/v1/oauth2/bot.

Messages & DMs

POST/api/v1/oauth2/bot/channels/:channelId/messages

Send channel message. Body: {"content": "...", "embeds": [...], "reply_to": "..."}. Need content and/or embeds. Requires VIEW_CHANNEL + SEND_MESSAGES. For voice channels, chat_enabled must be 1 or returns 403 (Text chat is disabled for this voice channel).

PUT/api/v1/oauth2/bot/channels/:channelId/messages/:messageId

Edit own message. Body: {"content"?, "embeds"?}

DELETE/api/v1/oauth2/bot/channels/:channelId/messages/:messageId

Delete own message.

POST/api/v1/oauth2/bot/channels/:channelId/messages/bulk-delete

Body: {"message_ids": ["...", ...]} (max 100).

GET/api/v1/oauth2/bot/channels/:channelId/messages

Query: ?before=&after=&limit= (1–100, default 50).

POST/api/v1/oauth2/bot/dm/:targetUserId

DM a user who shares a server with the bot. Body: {"content"?, "embeds"?}

JSON POST channel message
{ "content": "Hello", "embeds": [{ "title": "Status", "description": "Online", "color": "#23A55A" }], "reply_to": "optional-message-id" }

Servers, Members, Roles, Invites

GET/api/v1/oauth2/bot/servers/:serverId

Server info + member_count.

GET/api/v1/oauth2/bot/servers/:serverId/channels

List channels.

GET/api/v1/oauth2/bot/servers/:serverId/members

List members.

GET/api/v1/oauth2/bot/servers/:serverId/members/:userId

Member + assigned roles.

DELETE/api/v1/oauth2/bot/servers/:serverId/members/:userId

Kick member (emits member:left).

POST/api/v1/oauth2/bot/servers/:serverId/bans

Body: {"user_id": "...", "reason": "..."}

DELETE/api/v1/oauth2/bot/servers/:serverId/bans/:userId

Unban.

GET/api/v1/oauth2/bot/servers/:serverId/roles

List roles.

POST/api/v1/oauth2/bot/servers/:serverId/members/:userId/roles/:roleId

Assign role.

DELETE/api/v1/oauth2/bot/servers/:serverId/members/:userId/roles/:roleId

Remove role.

POST/api/v1/oauth2/bot/servers/:serverId/invites

Create invite. Body: {"max_uses"?, "expires_in"?} (seconds).

GET/api/v1/oauth2/bot/servers/:serverId/invites

List active invites.

DELETE/api/v1/oauth2/bot/servers/:serverId/invites/:inviteId

Delete invite.

PUT/api/v1/oauth2/bot/presence

Body: {"status": "online|idle|dnd|offline", "activity": {"type": "playing"|"listening"|"watching"|"competing"|"custom", "name": "...", "details"?, "state"?, "started_at"? } | null}. Set activity: null to clear. Presence/activity is cleared on gateway disconnect — re-send after reconnect if needed.

GET/api/v1/oauth2/bot/users/:userId

Public user profile (includes visible activity when the user is online and Activity is enabled).

Timeouts are available on the shared server routes (POST/DELETE /api/v1/servers/:id/timeouts...), not under /oauth2/bot. Bots with moderator permissions (or their admin bot role) can call those with the bot token.

Events Reference

Join rooms with server:join / channel:join to receive server- and channel-scoped events.

Events Your Bot Receives

EventWhenPayload (summary)
interaction:createSlash command or embed componentSee Slash Commands / Embeds sections
message:newNew channel messageFull message + author fields, embeds?, attachments?
message:updateMessage editedHydrated message
message:deletedMessage deleted{channelId, messageId}
message:reactionReaction add/remove{channelId, messageId, emoji, userId, action}
message:pinPin toggled{channelId, messageId, pinned}
member:joinedMember or bot joins{serverId, userId, username, avatar}
member:leftLeave / kick / ban remove{serverId, userId}
member:bannedMember banned{serverId, userId}
member:timeoutMember timed out{serverId, userId, expiresAt}
member:role-updatedBuilt-in role changed{serverId, userId, role}
member:roles-updatedCustom role assign/unassign{serverId, userId}
role:created / updated / deletedRole CRUDRole object or roleId
channel:created / updated / deletedChannel CRUDChannel payload
channel:permissions-updatedChannel override changedOverride or reset flag
category:permissions-updatedCategory override changedOverride or reset flag
user:statusPresence change{userId, status, customStatus?}
typing:start / typing:stopTyping in channel{userId, username?, channelId}
dm:newNew DMDM message object
dm:reactionDM reactionReaction payload
dm:typing:start / stopDM typing{userId, ...}
voice:existingAfter voice joinArray of participants
voice:user-joined / voice:user-leftVoice membershipParticipant / {socketId}
voice:state-updateMute/deafen/video/etc.State fields
voice:speakingSpeaking indicator{socketId, speaking}
voice:admin-mute-update / voice:admin-deafen-updateServer mute/deafenTarget + flags
voice:force-leave / force-disconnect / force-moveModeration / multi-deviceReason / target channel
bot:audio-dataPCM from another bot{pcm, sampleRate, channels, botUserId}

Events Your Bot Can Emit

EventPayloadDescription
interaction:response{interaction_id, channel_id, content?, embeds?}Reply to interaction (ack: {ok, message_id, message})
bot:send_message{channel_id, content?, embeds?}Send channel message
bot:edit_message{message_id, channel_id?, content?, embeds?}Edit own message
bot:delete_message{message_id, channel_id?}Delete own message
server:join / server:leaveserverIdSubscribe to server room
channel:join / channel:leavechannelIdSubscribe to channel room
voice:join{channelId}Join voice
voice:leave(none)Leave voice
bot:voice-query{userId, serverId}Where is this user in voice?
bot:voice-participants{channelId}List voice participants
bot:audio-data{channelId, pcm, sampleRate?, channels?}Relay PCM into voice room
typing:start / typing:stopchannelIdShow typing (humans/bots)
status:set{status}Set presence

REST API Overview

All REST endpoints use the base URL https://ggbro.app/api and require Bearer token authentication in the Authorization header (except public invite/stats/emoji file where noted).

Bots typically use the Bot REST paths under /api/v1/oauth2/bot/... plus shared server/channel/message routes when they have membership and permissions.

Servers

GET/api/v1/servers

List all servers the authenticated account is a member of.

GET/api/v1/servers/:id

Get server details (name, icon, banner, description, member count).

POST/api/v1/servers/:id/icon

Upload server icon (multipart field icon). PNG/JPG/WEBP/GIF (animated), max 8MB. Original extension is kept so GIFs animate. Requires manage-server.

POST/api/v1/servers/:id/banner

Upload server banner (multipart field banner). PNG/JPG/WEBP/GIF (animated), max 10MB. Original extension is kept. Requires manage-server.

PATCH/api/v1/servers/:id/banner-offset

Reposition banner. Body: {"banner_offset": 0-100}.

GET/api/v1/servers/:id/members

List server members with roles, avatars, statuses.

GET/api/v1/servers/:id/gifs

List server custom GIFs/stickers.

POST/api/v1/servers/:id/gifs

Upload a custom GIF/sticker (multipart).

DELETE/api/v1/servers/:id/gifs/:gifId

Delete a server GIF/sticker.

POST/api/v1/servers/:id/mute

Mute a server for the current user. Body: {"duration": 3600}

DELETE/api/v1/servers/:id/mute

Unmute a server for the current user.

GET/api/v1/servers/me/muted

List servers muted by the current user.

Audit Log API

Requires admin / ADMINISTRATOR (or MANAGE_SERVER) permission on the bot's role. New bots no longer get Administrator by default — grant it explicitly in the Developer Portal if needed.

GET/api/v1/servers/:id/audit-log

Query: ?limit=50 (max 100), ?before=<unix_timestamp> for pagination.

JSON Response
{ "entries": [{ "id": "...", "server_id": "...", "actor_id": "moderator-or-user-uuid", "actor_username": "ModName", "actor_avatar": "/uploads/...", "action": "ROLE_ASSIGN", "target_type": "member", "target_id": "member-user-uuid", "target_username": "Victim", "changes": { "role": "Member", "roleId": "role-uuid", "username": "Victim", "userId": "member-user-uuid" }, "created_at": 1710000000 }], "hasMore": false }

target_username is resolved from target_id when target_type is member (including older entries that only stored role fields).

Recorded actions

ActionNotes
MEMBER_JOINHuman or bot join; may include invite metadata
MEMBER_LEAVEVoluntary leave
MEMBER_KICKchanges.moderator + target username
MEMBER_BAN / MEMBER_UNBANBan includes reason + moderator
MEMBER_TIMEOUT / MEMBER_TIMEOUT_REMOVEDuration, reason, moderator
MEMBER_ROLE_UPDATEBuilt-in role change (oldRole/newRole) + member username/userId
BOT_ADDBot invited; actor is inviter, target is bot user
VOICE_JOIN / VOICE_LEAVEChannel target
VOICE_SERVER_MUTE / UNMUTE / DEAFEN / UNDEAFENVoice moderation
VOICE_DISCONNECT / VOICE_MOVEDisconnect / move member
CHANNEL_CREATE / UPDATE / DELETE / DUPLICATE / REORDERChannels
CATEGORY_CREATE / DELETECategories
ROLE_CREATE / UPDATE / DELETERole definition changes
ROLE_ASSIGN / ROLE_UNASSIGNCustom role add/remove. target_id = member. changes: { role, roleId, username, userId }
MESSAGE_EDIT / DELETE / PIN / UNPINMessages
INVITE_CREATE / DELETE / REGENERATEInvites
SERVER_CREATE / UPDATE / DELETE / ICON / BANNER / VANITYServer settings
SERVER_EMOJI_UPLOAD / DELETE / RENAMEEmojis
SERVER_SOUNDBOARD_UPLOAD / UPDATE / DELETESoundboard

Members Moderation

Shared server moderation routes (bot token works when the bot is a member with the right permissions). Prefer Bot REST kick/ban when available; timeouts use these routes.

DELETE/api/v1/servers/:id/members/:targetUserId

Kick. Requires moderator or KICK_MEMBERS. Emits member:left. Audit: MEMBER_KICK.

POST/api/v1/servers/:id/bans

Body: {"targetUserId": "...", "reason": "..."}. Emits member:left + member:banned. Audit: MEMBER_BAN.

DELETE/api/v1/servers/:id/bans/:targetUserId

Unban. Audit: MEMBER_UNBAN.

GET/api/v1/servers/:id/bans

List bans.

POST/api/v1/servers/:id/timeouts

Body: {"targetUserId": "...", "duration": 300, "reason": "..."}. Duration in seconds (min 60, max 28 days). Emits member:timeout. Response includes expiresAt.

DELETE/api/v1/servers/:id/timeouts/:targetUserId

Remove timeout. Audit: MEMBER_TIMEOUT_REMOVE.

Python Timeout via shared REST
requests.post( f"{API_URL}/api/v1/servers/{server_id}/timeouts", headers={"Authorization": f"Bearer {BOT_TOKEN}"}, json={"targetUserId": user_id, "duration": 600, "reason": "spam"}, )

Bot-specific aliases: DELETE /api/v1/oauth2/bot/servers/:serverId/members/:userId (kick), POST .../bans with {"user_id"}, DELETE .../bans/:userId.

Channels

GET/api/v1/channels/:serverId

List channels (permission-filtered) + categories.

GET/api/v1/channels/id/:channelId

Get channel by ID.

POST/api/v1/channels/:serverId

Create channel. Body: {"name": "general", "type": "text"|"voice", "category_id": "..."}

POST/api/v1/channels/:channelId/duplicate

Duplicate a channel (copies permission overrides).

PUT/api/v1/channels/:id

Update name/topic/slowmode/etc.

DELETE/api/v1/channels/:id

Delete channel.

PUT/api/v1/channels/:serverId/reorder

Reorder channels.

POST/api/v1/channels/:serverId/categories

Create category.

PUT/api/v1/channels/categories/:id

Rename category.

DELETE/api/v1/channels/categories/:id

Delete category (channels uncategorized).

PUT/api/v1/channels/:serverId/categories/reorder

Reorder categories. Body: {"order": ["catId1", "catId2"]}

Channel & Category Permission Overrides

allow and deny are bitmasks using the permissions bitfield. Deny wins over allow for a given bit.

GET/api/v1/channels/:channelId/permissions

List channel overrides.

PUT/api/v1/channels/:channelId/permissions/:roleId

Body: {"allow": 4096, "deny": 0}. Emits channel:permissions-updated.

DELETE/api/v1/channels/:channelId/permissions/:roleId

Reset override for role.

GET/api/v1/channels/categories/:categoryId/permissions

List category overrides.

PUT/api/v1/channels/categories/:categoryId/permissions/:roleId

Set category override. Emits category:permissions-updated.

DELETE/api/v1/channels/categories/:categoryId/permissions/:roleId

Reset category override.

JSON Hide channel from @everyone, allow a staff role
// PUT /api/v1/channels/:channelId/permissions/:everyoneRoleId { "allow": 0, "deny": 2048 } // deny VIEW_CHANNEL // PUT /api/v1/channels/:channelId/permissions/:staffRoleId { "allow": 2048, "deny": 0 }

Messages

GET/api/v1/messages/:channelId

Fetch messages (50 per page). Use ?before=messageId for older messages.

GET/api/v1/messages/id/:messageId

Get a single message by ID.

POST/api/v1/messages/:channelId/upload

Send a message with optional file attachments (multipart form data). Field: content for text, files for attachments.

PUT/api/v1/messages/:id

Edit a message. Body: {"content": "..."}. Author or manage-messages.

DELETE/api/v1/messages/:id

Delete a message. Must be the message author or have manage_messages permission.

GET/api/v1/messages/:channelId/search

Search messages in a channel. Query params: ?q=search+term&limit=25

GET/api/v1/messages/attachment/:attachmentId

Get attachment metadata (filename, size, mime type, url).

POST/api/v1/messages/:channelId/read

Mark channel as read (for unread indicators).

Members & Users

GET/api/v1/users/me/info

Get the current authenticated user/bot. Returns: id, username, email, avatar, banner, bio, status, is_bot, created_at.

GET/api/v1/users/:id

Get a user by ID. Returns: username, avatar, status, banner, bio, is_bot, created_at.

GET/api/v1/users/:id/profile

Get a user's full profile including mutual friends and mutual servers.

PUT/api/v1/users/me/profile

Update the current user's profile. Body: {"username": "...", "bio": "..."} (bio max 500 chars)

POST/api/v1/users/me/avatar

Upload a new avatar (multipart field avatar). PNG/JPG/WEBP/GIF (animated), max 2MB. Original extension is kept so GIFs animate.

POST/api/v1/users/me/banner

Upload a new banner (multipart field banner). PNG/JPG/WEBP/GIF (animated), max 5MB. Original extension is kept.

PUT/api/v1/users/me/password

Change password. Body: {"currentPassword": "...", "newPassword": "..."}

PUT/api/v1/servers/:id/members/:targetUserId/role

Change a member's role in a server. Body: {"role": "admin"}

Friends

Friend routes authenticate as the calling account. Bots can call them with a bot token, but friendship UX is primarily human-oriented. There is no separate bot-friends API.

GET/api/v1/friends

List friends (accepted).

GET/api/v1/friends/search

Search users. Query: ?q=username

POST/api/v1/friends/request

Body: {"username": "..."}

POST/api/v1/friends/accept/:friendId

Accept pending request.

DELETE/api/v1/friends/:friendId

Remove friend or reject pending.

GET/api/v1/friends/voice-activity

Friends currently in voice.

Direct Messages

Prefer POST /api/v1/oauth2/bot/dm/:targetUserId for bot DMs (requires a shared server). The routes below are the general DM API.

GET/api/v1/dm/channels

List DM channels.

POST/api/v1/dm/channels/:targetUserId

Open/create DM channel.

GET/api/v1/dm/:targetUserId/messages

Fetch DMs. Supports ?before= / ?around=

POST/api/v1/dm/:targetUserId/messages

Send DM. Body: {"content": "Hello!"}

POST/api/v1/dm/:targetUserId/upload

DM with attachments (multipart).

PUT/api/v1/dm/:targetUserId/messages/:messageId

Edit DM message.

DELETE/api/v1/dm/:targetUserId/messages/:messageId

Delete DM message.

POST/api/v1/dm/:targetUserId/read

Mark read.

GET/api/v1/dm/:targetUserId/search

Search DM history. ?q=

Group DMs

Group DMs are under /api/v1/dm/groups. They are user/group features; bots are not specially provisioned into group DMs, but a bot account that is a group member can use the same routes.

GET/api/v1/dm/groups

List group DMs.

POST/api/v1/dm/groups

Create group. Body includes member IDs / name.

PATCH/api/v1/dm/groups/:groupId

Update group metadata.

POST/api/v1/dm/groups/:groupId/members

Add members.

DELETE/api/v1/dm/groups/:groupId/members/:memberId

Remove member.

GET/api/v1/dm/groups/:groupId/messages

Fetch messages.

POST/api/v1/dm/groups/:groupId/messages

Send message.

POST/api/v1/dm/groups/:groupId/upload

Upload attachments.

PUT/api/v1/dm/groups/:groupId/messages/:messageId

Edit message.

DELETE/api/v1/dm/groups/:groupId/messages/:messageId

Delete message.

POST/api/v1/dm/groups/:groupId/leave

Leave group.

DELETE/api/v1/dm/groups/:groupId

Delete group (owner).

Roles

GET/api/v1/roles/:serverId

List roles.

GET/api/v1/roles/:serverId/role/:roleId

Get one role.

POST/api/v1/roles/:serverId

Create. Body: {"name": "Moderator", "color": "#5865F2", "permissions": 0, "hoist"?: false}

PUT/api/v1/roles/:serverId/:roleId

Update name/color/permissions/hoist.

DELETE/api/v1/roles/:serverId/:roleId

Delete role.

PUT/api/v1/roles/:serverId/reorder

Reorder. Body: array of {"id", "position"}

POST/api/v1/roles/:serverId/assign

Assign. Body: {"targetUserId": "...", "roleId": "..."}. Emits member:roles-updated.

DELETE/api/v1/roles/:serverId/assign/:targetUserId/:roleId

Unassign role.

GET/api/v1/roles/:serverId/member/:targetUserId

Roles on a member.

Bot shortcuts: POST/DELETE /api/v1/oauth2/bot/servers/:serverId/members/:userId/roles/:roleId and GET .../roles.

Permissions Bitfield

BitNameValue
0MANAGE_CHANNELS1
1MANAGE_ROLES2
2KICK_MEMBERS4
3BAN_MEMBERS8
4MANAGE_MESSAGES16
5MANAGE_SERVER32
6MUTE_MEMBERS64
7DEAFEN_MEMBERS128
8MOVE_MEMBERS256
9ADMINISTRATOR512
10ADD_REACTIONS1024
11VIEW_CHANNEL2048
12SEND_MESSAGES4096
13CONNECT8192
14MANAGE_REACTIONS16384
15SPEAK32768
16ATTACH_FILES65536
17EMBED_LINKS131072
18READ_MESSAGE_HISTORY262144
19MENTION_EVERYONE524288
20USE_EXTERNAL_EMOJIS1048576
21STREAM2097152
22USE_SOUNDBOARD4194304
23USE_APPLICATION_COMMANDS8388608

Invite URL: /invite?bot=<botUserId>&permissions=<bitfield>. Authorizing grants a managed role named after the bot with exactly those permissions (Administrator may be requested from the Developer Portal). Defaults in the portal only update the invite link — not existing servers. Bot owners cannot remotely edit a server's bot role; server admins do that in Server Settings. Kick/ban/leave keeps the role until an admin deletes it. New bots default to least-privilege 8895488 (not Administrator). Combine flags with bitwise OR, e.g. KICK_MEMBERS | BAN_MEMBERS | MUTE_MEMBERS = 4|8|64 = 76. Channel overrides use the same bits; deny wins. Uploads/GIFs need ATTACH_FILES; slash commands need USE_APPLICATION_COMMANDS; camera/screen need STREAM; soundboard needs USE_SOUNDBOARD. Bot moderation REST routes enforce the bot's role bits (missing Kick → 403 on kick, etc.).

Emojis

Custom emojis allow servers to upload and use custom image emojis in messages and reactions. Emojis use the format <:name:id> in message content.

GET/api/v1/servers/:id/emojis

List all custom emojis in a server. Requires server membership.
Response: { "emojis": [{ "id", "server_id", "name", "file_path", "original_name", "mime_type", "size", "uploaded_by", "created_at", "uploaded_by_username" }], "canManage": boolean }

GET/api/v1/servers/:id/emojis/public

List server custom emojis (public, no membership required). Useful for cross-server emoji display.

POST/api/v1/servers/:id/emojis

Upload a custom emoji. Multipart form data with field emoji (image, max 256 KB). Optional name field (max 32 chars, alphanumeric/underscore/hyphen). Requires Manage Server permission. Limit: 50 emojis per server.
Response: { "emoji": { "id", "server_id", "name", "file_path", ... } }

DELETE/api/v1/servers/:id/emojis/:emojiId

Delete a custom emoji from a server. Requires Manage Server permission.
Response: { "success": true }

GET/api/v1/servers/my-emojis

List all custom emojis from every server the authenticated user belongs to. Includes server_name on each emoji object.

GET/api/v1/emojis/:id/file

Get a custom emoji image by ID. Returns a redirect (302) to the image file. Can be used as an <img> src directly.

Using Custom Emojis

To include a custom emoji in a message, use the format: <:emoji_name:emoji_id>
For reactions, pass the same format as the emoji field: "<:emoji_name:emoji_id>"
Standard Unicode emojis can also be used directly (e.g. "👍").

Soundboard

GET/api/v1/servers/:id/soundboard

List server soundboard sounds (each includes an icon emoji).

POST/api/v1/servers/:id/soundboard

Upload a sound (multipart). Fields: sound (required audio ≤8MB), optional name (max 40), optional icon (Unicode emoji for the voice picker tile; default 🎶). Requires manage-server. Audit: SERVER_SOUNDBOARD_UPLOAD.

PATCH/api/v1/servers/:id/soundboard/:soundId

Edit a sound. JSON {name?, icon?} or multipart with optional sound (replace audio) plus name/icon. Audit: SERVER_SOUNDBOARD_UPDATE.

DELETE/api/v1/servers/:id/soundboard/:soundId

Delete a sound. Audit: SERVER_SOUNDBOARD_DELETE.

GET/api/v1/nitro/soundboard/catalog

Catalog of playable sounds across eligible servers (GGBRO Plus/soundboard feature). Each sound has id, name, icon, builtin, file_url.

POST/api/v1/nitro/soundboard/play

Play a sound in the caller’s active voice channel.

Reactions & Pins

PUT/api/v1/messages/:channelId/reactions/:messageId

Add or toggle a reaction on a message. Body: {"emoji": "👍"} for Unicode, or {"emoji": "<:name:id>"} for custom emojis. If you already reacted with this emoji, it will be removed (toggle). Adding a brand-new emoji type requires ADD_REACTIONS permission; adding to an existing emoji type is always allowed.

DELETE/api/v1/messages/:channelId/reactions/:messageId/:emoji

Remove your reaction from a message.

GET/api/v1/messages/:channelId/reactions/:messageId

Get all reactions on a message with user lists.

GET/api/v1/messages/:channelId/pins

Get all pinned messages in a channel.

POST/api/v1/messages/:channelId/pins/:messageId

Pin a message to a channel.

DELETE/api/v1/messages/:channelId/pins/:messageId

Unpin a message from a channel.

GET/api/v1/dm/:targetUserId/pins

Get pinned messages in a DM conversation.

POST/api/v1/dm/:targetUserId/pins/:messageId

Pin a DM message.

DELETE/api/v1/dm/:targetUserId/pins/:messageId

Unpin a DM message.

Invites & Bot Invites

POST/api/v1/servers/:id/invites

Create invite. Body: {"max_uses"?, "expires_in"?}

GET/api/v1/servers/:id/invites

List invites.

DELETE/api/v1/servers/:id/invites/:inviteId

Delete invite.

GET/api/v1/servers/:id/invites/:inviteId/uses

Invite use history.

POST/api/v1/servers/:id/invite/regenerate

Regenerate primary invite code.

POST/api/v1/servers/join/:inviteCode

Join via invite code.

GET/api/v1/oauth2/invite/:botUserId

Public bot invite card: name, avatar, description, public_invite, default_permissions, invite_permission_locked, permission_labels, permission_flags.

POST/api/v1/oauth2/invite/:botUserId/add/:serverId

Authorize bot onto a server. Body: {"permissions": <bitfield>}. Respects public_invite (otherwise owner-only). Creates/updates managed bot role.

POST/api/v1/oauth2/applications/:appId/servers/:serverId

Owner adds own bot. Optional body: {"permissions": <bitfield>}.

PATCH/api/v1/oauth2/applications/:appId/permissions

Body: {"default_permissions": number, "invite_permission_locked"?: boolean} — updates invite defaults only.

PATCH/api/v1/oauth2/applications/:appId/public-invite

Body: {"public_invite": true}

PATCH/api/v1/oauth2/applications/:appId/servers/:serverId/permissions

Always 403 — bot owners cannot change server bot roles remotely.

Bot token invite CRUD: /api/v1/oauth2/bot/servers/:serverId/invites (POST/GET/DELETE).

Copying IDs in the App

GGBRO IDs are UUIDs. No separate “Developer Mode” toggle is required — copy actions are always available:

Use these IDs in REST paths, slash option values, and Socket.IO room joins.

Errors & Rate Limits

Most endpoints return JSON errors:

JSON Error
{ "error": "Human readable message" }
StatusMeaning
200 / 201Success / created
400Invalid body or state
401Missing/invalid token
403Authenticated but forbidden (permissions / not a member)
404Resource missing
409Conflict (e.g. duplicate command name)

There is no global bot API rate-limit middleware today. Auth flows (e.g. password reset) use internal rate buckets. Still: avoid tight polling loops; prefer Socket.IO events. Upload size limits (25 MB / 500 MB) and command caps (50 commands, 25 options) apply as hard limits.

Webhooks

Channel webhooks let integrations post messages into a text channel without a bot user session. A channel may have multiple webhooks. Creating/managing requires the Manage Channels permission. Execute URLs are unauthenticated secrets — treat them like passwords.

Important: POST /api/webhooks/:id/:token must not send an Authorization header. The URL token is the credential. Sending a Bearer JWT will not authorize execute and is unnecessary.

Also available in the Developer Portal → Webhooks, the Message Builder (Discohook-style embeds), and in Edit Channel → Integrations inside the app.

GET/api/v1/channels/:channelId/webhooks

List webhooks for a channel. Auth: user Bearer with Manage Channels. Response includes full url and token for managers (for Copy URL).

POST/api/v1/channels/:channelId/webhooks

Create a webhook. Body: {"name":"My Hook","avatar?":"https://…"}. Returns full token and url.

GET/api/webhooks/channel/:channelId

Same as list (alias).

POST/api/webhooks/channel/:channelId

Same as create (alias).

PATCH/api/webhooks/:id

Rename or set avatar. Body: {"name?":"…","avatar?":"…"}. Auth required.

POST/api/webhooks/:id/token

Regenerate token. Returns new full token and url. Auth required. Do not confuse with execute (/:id/:token).

DELETE/api/webhooks/:id

Delete a webhook. Auth required.

GET/api/webhooks/:id/:token

Fetch webhook metadata with the execute token (name, channel, url). No auth header.

POST/api/webhooks/:id/:token

Execute webhook (no Authorization header). Body: {"content?":"…","embeds?":[],"username?":"override","avatar_url?":"…"}. Requires content or embeds. Discord-style embed shapes (image.url, integer color) are accepted and normalized. Query ?wait=true returns the created message JSON; otherwise 204. Wrong id/token → 401 {"error":"Invalid webhook"}.

bash Execute webhook
curl -X POST "https://ggbro.app/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN?wait=true" \ -H "Content-Type: application/json" \ -d '{"content":"Hello from a webhook!","username":"CI Bot"}'

Messages appear with a WEBHOOK badge and the webhook display name/avatar. Rate limit: ~30 executes per minute per webhook.

Stats

GET/api/v1/stats

Public platform statistics: {"users", "bots", "servers", "messages"}

Full Example: Ping Pong Bot (Python)

A complete, copy-paste-ready bot. Responds to /ping with "Pong!", /echo with the user's message, and /hello with a rich embed.

bash Install Dependencies
pip install "python-socketio[client]" requests
Python ping_bot.py — Complete Working Example
""" ggbro Ping Pong Bot (Python) A complete working example for the ggbro platform. ggbro uses Socket.IO v4 for real-time communication. Install: pip install "python-socketio[client]" requests """ import socketio import requests # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" # Get from Developer Portal API_URL = "https://ggbro.app" # --- Socket.IO Client --- sio = socketio.Client(reconnection=True, reconnection_delay=1) # --- Helper: Reply to a slash command --- def reply(ctx, content, embeds=None): """Respond to an interaction (slash command).""" payload = { "interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content } if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) # --- Helper: Send a message to a channel --- def send_message(channel_id, content, embeds=None): """Send a message to any channel.""" payload = {"channel_id": channel_id, "content": content} if embeds: payload["embeds"] = embeds sio.emit("bot:send_message", payload) # --- Events --- @sio.event def connect(): print("[+] Connected to ggbro!") @sio.event def disconnect(): print("[!] Disconnected from ggbro") # --- Slash Command Handler --- @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() args = data.get("args", "") user = data["user"]["username"] print(f"[cmd] /{cmd} from {user}") if cmd == "ping": reply(data, "Pong! 🏓") elif cmd == "echo": message = data.get("options", {}).get("message") or args if message: reply(data, f"📢 {message}") else: reply(data, "❌ Please provide a message!") elif cmd == "hello": # Reply with an embed reply(data, "", embeds=[{ "title": f"Hello, {user}!", "description": "Welcome! I'm a bot running on ggbro.", "color": "#23a55a" }]) # --- Register Commands --- def register_commands(): commands = [ {"name": "ping", "description": "Check if the bot is alive", "options": []}, {"name": "echo", "description": "Repeat your message", "options": [ {"name": "message", "description": "Text to echo", "required": True} ]}, {"name": "hello", "description": "Say hello with an embed", "options": []}, ] r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=commands, headers={"Authorization": f"Bearer {BOT_TOKEN}"} ) if r.ok: print("[+] Commands registered: /ping, /echo, /hello") else: print(f"[!] Failed to register commands: {r.status_code} {r.text}") # --- Main --- if __name__ == "__main__": print("Starting ggbro Ping Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()

Full Example: Ping Pong Bot (JavaScript / Node.js)

bash Install Dependencies
npm install socket.io-client axios
JavaScript ping_bot.js — Complete Working Example
/** * ggbro Ping Pong Bot (Node.js) * A complete working example for the ggbro platform. * ggbro uses Socket.IO v4 for real-time communication. * * Install: npm install socket.io-client axios */ const { io } = require("socket.io-client"); const axios = require("axios"); // --- Configuration --- const BOT_TOKEN = "YOUR_BOT_TOKEN"; // Get from Developer Portal const API_URL = "https://ggbro.app"; // --- Socket.IO Client --- const socket = io(API_URL, { auth: { token: BOT_TOKEN }, reconnection: true, reconnectionDelay: 1000 }); // --- Helper: Reply to a slash command --- function reply(ctx, content, embeds) { const payload = { interaction_id: ctx.id, channel_id: ctx.channel_id, content }; if (embeds) payload.embeds = embeds; socket.emit("interaction:response", payload); } // --- Helper: Send a message to a channel --- function sendMessage(channelId, content, embeds) { const payload = { channel_id: channelId, content }; if (embeds) payload.embeds = embeds; socket.emit("bot:send_message", payload); } // --- Events --- socket.on("connect", () => console.log("[+] Connected to ggbro!")); socket.on("disconnect", () => console.log("[!] Disconnected")); // --- Slash Command Handler --- socket.on("interaction:create", (data) => { const cmd = (data.command || "").toLowerCase(); const args = data.args || ""; const user = data.user.username; console.log(`[cmd] /${cmd} from ${user}`); if (cmd === "ping") { reply(data, "Pong! 🏓"); } else if (cmd === "echo") { const message = (data.options && data.options.message) || args; reply(data, message ? `📢 ${message}` : "❌ Please provide a message!"); } else if (cmd === "hello") { reply(data, "", [{ title: `Hello, ${user}!`, description: "Welcome! I'm a bot running on ggbro.", color: "#23a55a" }]); } }); // --- Register Commands --- async function registerCommands() { try { await axios.post(`${API_URL}/api/v1/oauth2/commands`, [ { name: "ping", description: "Check if the bot is alive", options: [] }, { name: "echo", description: "Repeat your message", options: [ { name: "message", description: "Text to echo", required: true } ]}, { name: "hello", description: "Say hello with an embed", options: [] }, ], { headers: { Authorization: `Bearer ${BOT_TOKEN}` } }); console.log("[+] Commands registered: /ping, /echo, /hello"); } catch (err) { console.error("[!] Failed to register:", err.response?.data || err.message); } } // --- Start --- registerCommands();

Full Example: /serverinfo with Embeds (Python)

A complete bot that responds to /serverinfo with a rich embed containing server name, owner, member count, and creation date:

Python serverinfo_bot.py — Complete Working Example
""" ggbro Server Info Bot Responds to /serverinfo with a rich embed showing server details. Uses Socket.IO for real-time + REST API for data fetching. Install: pip install "python-socketio[client]" requests """ import socketio import requests from datetime import datetime # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" HEADERS = {"Authorization": f"Bearer {BOT_TOKEN}"} sio = socketio.Client(reconnection=True) def reply(ctx, content="", embeds=None): payload = { "interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content, } if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) @sio.event def connect(): print("[+] Connected to ggbro!") @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() if cmd == "serverinfo": server_id = data["server_id"] user = data["user"]["username"] # Fetch server details from REST API r = requests.get(f"{API_URL}/api/v1/servers/{server_id}", headers=HEADERS) if not r.ok: reply(data, "❌ Failed to fetch server info") return server = r.json() # Fetch members mr = requests.get(f"{API_URL}/api/v1/servers/{server_id}/members", headers=HEADERS) members = mr.json().get("members", []) if mr.ok else [] # Find the owner owner = next((m for m in members if m.get("role") == "owner"), None) owner_name = owner["username"] if owner else "Unknown" # Build the embed icon_url = f"{API_URL}/uploads/icons/{server['icon']}" if server.get("icon") else None reply(data, "", embeds=[{ "title": server.get("name", "Unknown Server"), "description": server.get("description") or "No description set.", "color": "#5865F2", "thumbnail": icon_url, "fields": [ {"name": "👑 Owner", "value": owner_name, "inline": True}, {"name": "👥 Members", "value": str(len(members)), "inline": True}, {"name": "🆔 Server ID", "value": f"`{server_id}`"}, ], "footer": {"text": f"Requested by {user}"}, "timestamp": datetime.utcnow().isoformat(), }]) # --- Register Commands --- def register_commands(): r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=[{"name": "serverinfo", "description": "Show server details", "options": []}], headers=HEADERS ) print("[+] /serverinfo registered" if r.ok else f"[!] Failed: {r.text}") if __name__ == "__main__": print("Starting Server Info Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()

Full Example: Welcome Bot (Python)

Greets new members with a rich embed when they join a server:

Python welcome_bot.py — Complete Working Example
""" ggbro Welcome Bot Greets new members with a rich embed when they join. Install: pip install "python-socketio[client]" requests """ import socketio import requests from datetime import datetime # --- Configuration --- BOT_TOKEN = "YOUR_BOT_TOKEN" API_URL = "https://ggbro.app" HEADERS = {"Authorization": f"Bearer {BOT_TOKEN}"} # Set your welcome channel ID per server WELCOME_CHANNELS = { "your-server-id": "your-welcome-channel-id", } sio = socketio.Client(reconnection=True) def send_message(channel_id, content="", embeds=None): payload = {"channel_id": channel_id, "content": content} if embeds: payload["embeds"] = embeds sio.emit("bot:send_message", payload) def reply(ctx, content="", embeds=None): payload = {"interaction_id": ctx["id"], "channel_id": ctx["channel_id"], "content": content} if embeds: payload["embeds"] = embeds sio.emit("interaction:response", payload) @sio.event def connect(): print("[+] Welcome Bot connected!") # Join all configured server rooms for server_id in WELCOME_CHANNELS: sio.emit("server:join", server_id) # --- Welcome new members with an embed --- @sio.on("member:joined") def on_member_join(data): server_id = data.get("serverId") username = data.get("username", "Someone") avatar = data.get("avatar") channel_id = WELCOME_CHANNELS.get(server_id) if not channel_id: return avatar_url = f"{API_URL}/uploads/avatars/{avatar}" if avatar else None send_message(channel_id, "", embeds=[{ "title": f"Welcome, {username}! 👋", "description": "We're glad to have you here. Check out the channels and say hi!", "color": "#23a55a", "thumbnail": avatar_url, "timestamp": datetime.utcnow().isoformat(), }]) # --- Slash command to set welcome channel --- @sio.on("interaction:create") def on_interaction(data): cmd = (data.get("command") or "").lower() if cmd == "setwelcome": channel_id = (data.get("options", {}).get("channel_id") or data.get("args", "")).strip() if not channel_id: reply(data, "❌ Please provide a channel ID!") return WELCOME_CHANNELS[data["server_id"]] = channel_id reply(data, "", embeds=[{ "title": "✅ Welcome Channel Set", "description": f"New members will be greeted in this channel.", "color": "#23a55a" }]) # --- Register Commands --- def register_commands(): r = requests.post( f"{API_URL}/api/v1/oauth2/commands", json=[ {"name": "setwelcome", "description": "Set the welcome channel", "options": [ {"name": "channel_id", "description": "Channel ID for welcome messages", "required": True} ]} ], headers=HEADERS ) print("[+] /setwelcome registered" if r.ok else f"[!] Failed: {r.text}") if __name__ == "__main__": print("Starting Welcome Bot...") register_commands() sio.connect(API_URL, auth={"token": BOT_TOKEN}) sio.wait()