Documentation · Developer API

Connect your bots to anything

Free on every plan. Let your own in-world scripts, rental boxes, vendors or your own server send IMs, invite residents to your groups and more through your VelvetBots bots.

Overview

Every request goes to https://velvetbots.com/api/v1/ over HTTPS. Send JSON bodies with POST and read everything else with GET (filters go in the query string). Your bot acts exactly as it would from the dashboard: it must be online to do anything in Second Life, and group actions follow your Manage groups settings.

The API never moves L$. You can read a bot’s balance, but sending L$ stays on the dashboard’s Money Control page.

Keys & authentication

  1. Open Dashboard → Developer API and create a key (up to 10).
  2. Give it a name, tick only the permissions it needs, and pick All my bots or just some.
  3. Copy the key straight away — it’s shown once. We only keep a fingerprint of it.

Send it on every request in the X-Velvet-Key header. In LSL:

HTTP_CUSTOM_HEADER, "X-Velvet-Key", API_KEY
PermissionLets the key
Send IMssend instant messages from the bot
Local chatmake the bot speak in local chat, on any channel
Groupslist groups and roles, invite, eject, send group chat and notices
Movementwalk, teleport, sit and stand
Eventsread what the bot heard and saw, and use a webhook
AI repliesreserved for VBL rules that use the AI Brain

Any key can read its bots’ status, look up names and fire VBL rules. Revoke a key on the Developer page and it stops working on the next request.

Responses & errors

Every reply is one flat JSON object with ok. On an error it also has code and message, with a matching HTTP status:

{"ok": false, "code": "bot_offline", "message": "The bot must be online to send an IM."}
StatusCommon codes
400bad_json, bad_uuid, bad_message, message_too_long, bad_channel, bad_position, bad_role
401bad_key — missing, wrong or revoked key
403no_permission, invite_off, group_chat_off, premium_required
404not_found (not your bot, or outside the key’s bots), bad_group
409bot_offline
429rate_limited
LSL tip: llJsonGetValue(body, ["ok"]) returns the constant JSON_TRUE or JSON_FALSE, not the text "true". Compare with JSON_TRUE. For long replies (events), add HTTP_BODY_MAXLENGTH, 16384 to your request.

Rate limits

Basic botPremium bot
Requests per bot per minute30120
Event polls per bot per minute120240
Requests per key per minute that name no bot (/bots, /key, /events)60 (event polls 120)

Second Life also throttles IMs and group invites from one avatar, so spread bulk sends out.

Bots & status

{bot} in a path is the bot’s ID (like h1n1), not its Second Life name. Copy it from Dashboard › Developer API › Your bot IDs, or from the top of the bot’s own page.

GET /api/v1/key

The key’s name, permissions and owner — handy to test a key.

GET /api/v1/bots

Your bots this key covers: id, name, uuid, tier, online, region.

GET /api/v1/bots/{bot}

One bot: online, state, region, sitting_on (what it’s sitting on now), seat (its saved sit spot) and balance (its L$, read-only; null until known).

IM & local chat

POST /api/v1/bots/{bot}/im · permission: Send IMs

{"to": "<avatar uuid>", "message": "Your rent is due tomorrow"} — up to 1000 bytes.

POST /api/v1/bots/{bot}/say · permission: Local chat

{"message": "Welcome!", "channel": 0} — channel is optional (0 = public chat); any other channel is for your own scripts to hear.

Groups

Only groups switched on in Manage groups exist for the API. Basic bots keep their 3-group limit.

GET /api/v1/bots/{bot}/groups · permission: Groups

Each group’s id, name, and whether invite, group_chat and can_notice are on.

GET /api/v1/bots/{bot}/groups/{group}/roles · permission: Groups

The group’s roles (id, name).

POST /api/v1/bots/{bot}/groups/{group}/invite · permission: Groups

{"to": "<avatar uuid>", "role": "<role id, optional>"} — needs Invite ticked in Manage groups. No role = Everyone.

POST /api/v1/bots/{bot}/groups/{group}/eject · permission: Groups

{"to": "<avatar uuid>"} — the bot’s role needs the Eject ability in that group.

POST /api/v1/bots/{bot}/groups/{group}/notice · permission: Groups

{"subject": "Party!", "message": "Saturday 2pm SLT"} — Premium bots, one notice per group every 30 minutes, subject + message up to 510 characters.

POST /api/v1/bots/{bot}/groups/{group}/chat · permission: Groups

{"message": "Hello everyone"} — needs Group chat ticked in Manage groups.

Movement

POST /api/v1/bots/{bot}/walk · permission: Movement

{"x": 128, "y": 64, "z": 22, "run": false, "region": "optional"} — walks in its region, or into a neighbouring one.

POST /api/v1/bots/{bot}/teleport · permission: Movement

{"region": "Region Name", "x": 128, "y": 128, "z": 25} — no region = same region.

POST /api/v1/bots/{bot}/sit · permission: Movement

{"target": "<object uuid>"}

POST /api/v1/bots/{bot}/stand · permission: Movement

No body.

Name to UUID

POST /api/v1/bots/{bot}/lookup

{"name": "jane.doe"} — searches Second Life through your (online) bot. Usually answers in the same call with ready: true and candidates (uuid, name); if not, poll GET /api/v1/bots/{bot}/lookup a few seconds later.

Events

Once a key with Events covers a bot, VelvetBots keeps what that bot hears and sees for 7 days (up to 1000 per bot): IMs, local chat, group chat (groups with Group chat on), L$ payments it receives, and coming online / going offline.

GET /api/v1/events?since={last_id}&limit=25&types=im,payment · permission: Events

Events for every bot the key covers.

GET /api/v1/bots/{bot}/events?since={last_id} · permission: Events

Just one bot.
{"ok": true, "last_id": 1042, "more": false, "events": [
  {"id": 1041, "bot": "abc1", "type": "im", "at": "2026-10-18T12:00:00+00:00",
   "from": "<uuid>", "name": "Jane Doe", "message": "Is unit 4 free?"},
  {"id": 1042, "bot": "abc1", "type": "payment", "at": "...",
   "from": "<uuid>", "name": "Jane Doe", "amount": 250, "txn": "...", "description": ""}
]}
  • Types: im, local, group (adds group), payment, online (adds region), offline.
  • Start without since: you get the latest few and the current last_id. Then always pass the last last_id you got.
  • more: true means there are more waiting — ask again straight away. limit is 1–100 (default 25).
  • Poll every 10–15 seconds from LSL. A server can use a webhook instead.

Webhooks

Optional, for your own server. On the Developer page, add an HTTPS URL to a key with Events. Every event that key covers is POSTed there as the same JSON object as above, with these headers:

X-Velvet-Eventthe event type (ping for the Send test button)
X-Velvet-Event-Idthe event id — use it to ignore repeats
X-Velvet-Signaturet=<unix time>,v1=<hex HMAC-SHA256 of "t.body" with your signing secret>
  • Answer with any 2xx within 5 seconds. Anything else is retried 3 more times (after 5 s, 30 s and 2 min).
  • After 50 events in a row that couldn’t be delivered, the webhook pauses and the Developer page shows why. Press Save to turn it back on. Events stay pollable meanwhile.
  • The URL must be a public HTTPS address. Redirects aren’t followed.
# Python: verify a webhook before trusting it
import hashlib, hmac, time

def verified(secret: str, raw_body: bytes, header: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, sig = int(parts["t"]), parts["v1"]
    if abs(time.time() - t) > 300:          # older than 5 minutes: reject
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Fire a VBL rule

POST /api/v1/bots/{bot}/vbl/fire

{"event": "door", "avatar": "<uuid, optional>", "text": "optional"} — runs the bot’s VBL rules that start with when api "door". The rules’ own actions decide what happens.

LSL example

IM whoever touches the object, from your bot:

string API_KEY = "vbk_...";      // Dashboard > Developer API
string BOT_ID  = "your_bot_id";

default
{
    touch_start(integer n)
    {
        llHTTPRequest("https://velvetbots.com/api/v1/bots/" + BOT_ID + "/im",
            [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/json",
             HTTP_CUSTOM_HEADER, "X-Velvet-Key", API_KEY],
            llList2Json(JSON_OBJECT, ["to", (string)llDetectedKey(0),
                                      "message", "Thanks for visiting!"]));
    }

    http_response(key req, integer status, list meta, string body)
    {
        // "ok" is a JSON boolean: compare with JSON_TRUE, never with "true".
        if (llJsonGetValue(body, ["ok"]) != JSON_TRUE)
            llOwnerSay("Failed: " + llJsonGetValue(body, ["message"]));
    }
}

LSL Starter Pack

Free ready-made scripts: group invites for renters and tenants, an IM relay, an events poller, a local chat listener, a group notice sender, a VBL rule trigger and a status sign. Get it from the Developer page (it’s delivered to your Second Life inventory) or on the Second Life Marketplace. Paste your key and bot ID (Dashboard › Developer API › Your bot IDs) into a script and it’s ready.

Developer API — VelvetBots