Guide

Bots & command sync

A creator running four tools should still have one botin their chat, not four. This page is how your app connects to that shared bot, publishes its commands into it without stepping on anyone else's, and — if the creator makes it a moderator — acts on chat. Every endpoint, field and limit below is read out of the live API.

The one thing to understand first

A Velora bot is not a second account that a creator logs into and shares a password with. It is a bot identity owned by their existing account, which they authorize into a channel with a specific permission set they can revoke at any time — and which multiple apps can connect to at once.

That last part matters most for integrators. A creator running your tool alongside two others does not end up with three bots in their chat. They have one bot, and each app publishes its own commands into it. Velora tracks which app owns which command, so nothing collides silently and disconnecting one app never disturbs another.

ConcernHow Velora handles it
Second account for the botNot needed. The bot is an identity owned by the user's account.
Sharing credentials with a toolNever. Apps connect through OAuth; no password is exchanged.
Getting a bot out of a channelThe channel owner revokes it themselves. No support ticket.
Limiting what a bot may doFour explicit permissions, granted per channel.
Several tools, one chatOne shared bot. Commands are namespaced per app.
Two apps claiming the same commandDeterministic override with automatic restore. See below.

The four moving parts

1. The bot identity

A bot has its own unique username, display name and avatar, and is owned by exactly one user account. A user may own unlimited bots, but only one of them can have Velora Bot Studio (VBS) features — the built-in commands, timers, variables and event triggers — enabled at a time. Bots without VBS are just identities your app drives.

2. Channel permission — moderator role

A bot moderates by being granted the moderator role in a channel, exactly like a human moderator, by the broadcaster. There is no separate bot-permission system. Covered in full under Moderating chat below.

3. The app connection

When a creator connects your app to one of their bots, Velora records the link between your client_id and that bot, along with who connected it, whether it is active, when it was last used and how many commands you currently have synced. Several apps can hold an active connection to the same bot at the same time. Each connection is independent — disconnecting yours leaves the others alone.

4. Synced commands

Your app publishes its command list to Velora so the bot's profile page can show viewers what the bot actually responds to. Commands are stored per (bot, app) pair, so the platform always knows which tool owns which trigger.

Connecting your app to a bot

All endpoints below are called with the user's OAuth token, so they run in your app's context automatically — you never pass your client_id in the body.

GET/api/integrations/oauth/bot/availableevery bot this user owns and could connect

Show this list and let the creator choose. It includes their VBS bot and any bots created by other tools — picking an existing one is how they end up with a single shared bot rather than one per app.

GET/api/integrations/oauth/bot/currentthe bot currently connected to your app for this user

Call this on startup. If it returns a connection, you are already set up and should not prompt the creator again.

POST/api/integrations/oauth/bot/selectconnect an existing bot
Request body
{ "botId": "b3f1c2a4-..." }
POST/api/integrations/oauth/bot/createcreate a new bot and connect it
Request body
{
  "botName":     "CoolBot",              // required, 3-30 chars, [a-zA-Z0-9_] only
  "description": "Does cool things",     // optional, max 500 chars
  "avatarUrl":   "https://..."           // optional; see avatar upload below
}

Returns 409 if the name is taken. Always check first with GET /api/integrations/oauth/bot/check-name/:botName.

Prefer “select” over “create”. Offer the existing-bot list first and treat creating a new one as the fallback. A creator with four tools should still have one bot in their chat.

GET/api/integrations/oauth/bot/connectionslist this user's bot connections
DELETE/api/integrations/oauth/bot/disconnectdisconnect your app from its bot

Disconnecting removes your synced commands and restores any built-in commands your app had overridden. It does not delete the bot and does not affect other apps.

Command sync

POST/api/integrations/oauth/bot/commands/syncpublish your full command list

Requires the bot:commands scope. This is a full replacementfor your app's commands on that bot: send the complete list every time. Triggers you omit are removed. Other apps' commands are untouched.

Request
POST /api/integrations/oauth/bot/commands/sync
Authorization: Bearer <user access token>

{
  "botId": "b3f1c2a4-...",
  "commands": [
    {
      "trigger": "uptime",
      "description": "How long the stream has been live",
      "aliases": ["up"],
      "permissions": { "level": "everyone" },
      "cooldown": { "globalCooldown": 5, "userCooldown": 15 },
      "isPublic": true
    }
  ]
}

Field reference

FieldRules
triggerRequired, max 50 chars. A leading ! is stripped, and matching is case-insensitive — send uptime or !uptime, both land the same.
descriptionOptional, max 500 chars. Shown on the bot's public profile.
aliasesOptional, up to 20 strings.
permissions.levelOne of everyone, followers, subscribers, tier1, tier2, tier3, vips, moderators, streamer_only. Optional followerAgeMinutes for follower-gated commands.
cooldownglobalCooldown and userCooldown, seconds, 0–86400.
isPublicDefaults to true. Set false to keep a command working but hidden from the public list.

Up to 500 commands per sync. Duplicate triggers inside a single payload are rejected with 400 — deduplicate before sending.

Creator-written descriptions are never overwritten. If a creator edits the description of one of your commands on their bot page, that edit survives every subsequent sync. Treat description as your default, not your property.

When your command collides with a built-in

If you sync a trigger that the bot already has as a Velora Bot Studio command, the built-in is deactivated and marked as overridden by your app— yours wins, and the creator's original definition is preserved rather than deleted.

When you later drop that trigger from your sync, or disconnect entirely, Velora restores the built-in automatically — but only if no other connected app is still syncing the same trigger. Nothing is silently lost, and no creator has to go re-enable anything by hand.

The response tells you exactly what happened:

Response 200
{
  "success": true,
  "synced":    { "total": 12 },
  "overrides": {
    "vbsCommandsDisabled": 1,
    "vbsCommandsRestored": 0
  }
}

Surfacing vbsCommandsDisabled in your own UI is a kindness — it lets a creator see that your !uptime replaced theirs, instead of wondering why theirs stopped answering.

What a bot can actually do

The short version: read chat, talk in chat, moderate chat, and run commands gated by viewer role — plus react to everything happening on the channel through the Events API.

You want to…UseNeeds
Read every message as it arrivesChat WebSocketchat:read
Reply, announce, respond to a commandChat WebSocket / send endpointchat:write + a connected bot
Delete a single messagemoderate · action: "delete" + messageIdchat:moderate + mod role
Time a viewer outmoderate · action: "timeout"chat:moderate + mod role
Ban / unbanmoderate · action: "ban" / "unban"chat:moderate + mod role
Mods-only or subs-only commandspermissions.level on the synced commandNothing extra — Velora enforces it
React to follows, subs, raids, redemptionsEvents APIEvent scopes

Two independent gates, and both matter. Moderation is gated on who your bot is in that channel (the moderator role). Commands are gated on who the viewer is(their role in chat). A mods-only command still runs under the creator's own rules even if your bot is not itself a moderator — the two systems do not depend on each other.

Moderating chat

Yes, a bot can moderate — and the way it gets that power is deliberately the same way a human does. There is no separate bot-permission system to learn: a bot is granted the moderator role in a channel, exactly like any other moderator, by the broadcaster.

Every moderation call is checked against that role at request time. The broadcaster always passes; anyone else must hold the moderator role in that specific channel. A bot with the role in one channel has no power in any other.

CapabilityScopeWhat it needs
Read chatchat:readNothing extra — see the Chat WebSocket docs.
Send messageschat:writeA connected bot to send as.
Delete · timeout · ban · unbanchat:moderateModerator role in that channel, granted by the broadcaster. Otherwise 403.

Both halves are required for moderation: your app needs the chat:moderate OAuth scope and the acting account needs the moderator role. A scope alone grants nothing — that is the point. A creator who authorizes your app has not thereby handed you their chat; they still have to make the bot a mod, and they can un-mod it the moment they want to, from the same place they manage every other moderator.

POST/api/integrations/oauth/chat/channels/:channelId/moderatedelete · timeout · ban · unban
Request body
{
  "action":          "timeout",          // delete | timeout | ban | unban
  "targetUsername":  "someviewer",       // or targetUserId
  "durationSeconds": 600,                // timeout only; default 600
  "reason":          "spam",             // optional
  "messageId":       "..."               // delete only
}

403 if the acting account is not a moderator or the broadcaster · 400 on an unknown action, a missing target, or delete without messageId.

Moderation actions are attributed to the bot, not to your app.A timeout issued through your integration shows the bot's name in chat, so viewers see a consistent identity and moderators can tell which tool acted. Name your bot accordingly.

If a moderation call returns 403, do not retry it.The account simply is not a moderator there. Surface it to the creator as “make the bot a moderator in your channel” — that is a thing they can fix in ten seconds, and a retry loop will never fix it for them.

Gating a command by viewer role

Separately from moderation, every command carries its own permission level, enforced by Velora before the command runs. Set it in the permissions field when you sync (see Command sync above):

LevelWho can run it
everyoneAnyone in chat.
followersFollowers. Add followerAgeMinutes to require a minimum follow age — the standard defence against a fresh account spamming a command.
subscribersAny active subscriber.
tier1 · tier2 · tier3Subscribers at that tier or above — tier2 also admits tier 3.
vipsVIPs and moderators (mods are never locked out of a VIP command).
moderatorsModerators and the broadcaster.
streamer_onlyThe broadcaster alone.

An unrecognised level is denied, not allowed — if you send a typo, the command stops working rather than opening up to everyone. Pair the level with cooldown for rate control; the two are independent.

Bot names

Bot names live in the same namespace as user accounts. A bot named CoolBot means no user can register CoolBot, and vice versa. This is deliberate: it guarantees that a name in chat always identifies exactly one entity.

RuleDetail
Length3–30 characters
Allowed charactersLetters, numbers and underscore only — [a-zA-Z0-9_]. No spaces, dots or dashes.
UniquenessCase-insensitive across bots and users. CoolBot and coolbot are the same name.
Display casingPreserved as typed. The creator sees CoolBot even though the unique key is lowercase.
GET/api/integrations/oauth/bot/check-name/:botNamecheck availability before creating
Response 200
{ "available": true, "botName": "coolbot" }

Check as the creator types, not just on submit. Hitting 409 after they picked an avatar and a description is a bad first experience with your integration.

Renaming is limited to once every 90 days

A bot that people recognise in chat should not be able to change identity on a whim, so bot renames are on a 90-day cooldown. Attempting a rename inside that window fails with a clear error rather than silently succeeding.

GET/api/bots/:id/username-cooldowncheck whether a rename is currently allowed
Response 200
{
  "canChange":     false,
  "lastChangeAt":  "2026-01-01T10:30:00Z",
  "nextChangeAt":  "2026-04-01T10:30:00Z",
  "daysRemaining": 45
}

If your app offers renaming, call this before showing the field and surface daysRemainingwhen it is locked. A disabled input with “available in 45 days” is a far better experience than an error after they have typed a new name.

Bot avatars

POST/api/integrations/oauth/bot/:botId/avatarupload a custom avatar

multipart/form-data with a single file field. The caller must own the bot.

Request and response
POST /api/integrations/oauth/bot/b3f1c2a4-.../avatar
Content-Type: multipart/form-data

file: <binary>

→ 200
{
  "success": true,
  "bot": {
    "id":          "b3f1c2a4-...",
    "username":    "coolbot",
    "displayName": "CoolBot",
    "avatarUrl":   "https://assets.velora.tv/bot-avatars/b3f1c2a4-.../a1b2.png"
  }
}
ConstraintValue
FormatsJPEG, PNG, GIF, WebP. Anything else returns 400.
Max size5 MB
StorageServed from assets.velora.tv under a per-bot path with a random filename.
Errors400 bad type or size · 404 not your bot · 503 storage unavailable

Each upload gets a fresh randomised filename, so the returned avatarUrl changes every time and is safe to cache indefinitely. Always store the URL you get back rather than constructing one.

You can also pass an avatarUrlwhen creating a bot if you already host the image. Uploading is preferred — it survives your CDN going away, and it keeps the bot looking right even if a creator later stops using your tool.

Where a bot appears to viewers

A bot is a visible identity on Velora, not an invisible API caller. Knowing where it shows up tells you what your synced metadata is actually for.

In chat

With its display name, avatar and a bot badge, so viewers can always tell a bot from a person.

On its profile page

Avatar, description, and its full command list, grouped by the app that published each one. This is where your description and permissions fields are read by humans.

On the viewer card

Clicking a bot in chat shows a bot-specific card: no follow button, no gift-sub button, no subscription actions. A bot is not a creator and Velora does not pretend otherwise.

Because commands are grouped by app on the profile page, a creator can see at a glance that !uptime came from your tool and !rafflecame from another. Write descriptions for that reader — a viewer deciding whether to type the command — not for your own internal docs.

What stays in the creator's hands

Creators manage their own bot from their Velora dashboard: profile and avatar, built-in commands, timed messages, variables, event triggers and shoutout overrides. Your app does not need to reimplement any of that, and generally shouldn't.

The dividing line is simple. You own your commands; the creator owns the bot. They can rename it, re-avatar it, revoke it from a channel, or disconnect your app entirely — without asking you, and without breaking the other tools connected to it.

Being a good citizen in a shared bot

Offer the existing-bot list before offering to create one.

The whole point is one bot per creator, not one per tool.

Sync the complete list every time.

Partial syncs delete the commands you left out.

Sync on change, not on a timer.

There is no reason to re-push an unchanged list every minute.

Do not claim generic triggers you do not implement.

Every trigger you sync is one another app cannot own, and may silently override a creator’s own command.

Disconnect cleanly.

Calling disconnect restores whatever you overrode. Leaving a stale connection behind holds triggers hostage.

Show the override count.

If your sync disabled a creator’s built-in command, tell them.

Related