Lummia Bot API
Send-only notification bots. Publish formatted messages into a channel from CI, monitoring, deploys and incident tooling with a single HTTP request.
Base URL in every example is https://api.lummia.io — replace it with your own Lummia host.
Overview
A Lummia bot is a send-only integration. An external service authenticates with a bot token and publishes a message into a channel the bot has been explicitly added to. Bot messages appear in the timeline under the bot's own name and avatar with a small BOT badge.
Bots are intentionally minimal. A bot cannot:
- read channel history or any message
- receive events, replies, or reactions
- run commands or slash-commands
- send or receive direct messages
- poll the API or fetch anything
Typical use cases: build/CI status, deploy and release notes, uptime and monitoring alerts, and incident notifications routed into the right channel.
Quick start
Four steps from zero to your first message.
-
1
Create the bot
In the desktop app open Settings → Workspace → Bots and create a bot. A name is required (an avatar is optional). Messages from the bot appear under that name and avatar, with a BOT badge and an initials fallback when there is no avatar. Managing bots is admin-only.
-
2
Save the token — it is shown once
On create you get a token that begins with lmbot_. It is displayed exactly once and is never retrievable afterwards. Copy it immediately and store it in a secret manager. Lost it? Regenerate a new one (which invalidates the old token instantly).
-
3
Add the bot to a channel
In the target group, open member management from the header's add-people control and use the “Add people or bots” picker to add the bot (bots carry a BOT tag in the list). A bot can only post to channels it has been explicitly added to — there is no workspace-wide broadcast.
-
4
Send your first message
Publish with a single POST. Set the environment variables from the safe token usage section first.
curl -X POST "$LUMMIA_API_BASE/api/bot/messages" \
-H "Authorization: Bot $LUMMIA_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel_id":"'"$LUMMIA_CHANNEL_ID"'","text":"Deploy finished ✅"}'
Success responds 200 with {"ok":true,"message_id":"<uuid>"}.
Creating a bot
Bots are created and managed by a workspace admin in Settings → Workspace → Bots. From there an admin can:
- Create a bot (name required) and set an optional avatar.
- Rename the bot or replace / remove its avatar.
- Regenerate the token (the previous token stops working immediately).
- Revoke the token (keeps the bot and its past messages, but blocks new sends).
- Delete the bot (its past messages keep their attribution).
To see where a bot can post, open it from that list (Settings → Workspace → Bots → click the bot): its detail page lists every channel it has been added to. The same list is available over the API at GET /api/teams/:team_id/bots/:bot_id/channels.
The token is shown once — on create and again on each regenerate. Every other view (including the bot list) shows only a short, non-secret prefix such as lmbot_Ab3f…. Treat the full token like a password.
Adding a bot to a channel
A bot's permission to post is scoped per channel. To let a bot publish into a group, open that group's member management from the header's add-people control and use the “Add people or bots” picker: pick the bot from the list (bots carry a BOT tag) and it is added, exactly as you would add a person. Remove it from the member list to revoke its posting permission.
Adding a bot does not change how human messages are encrypted, and it does not grant the bot any ability to read the conversation (see Security).
Finding the Channel ID
Every send needs a channel_id — the UUID of the destination group. In the desktop app open the group, click the edit (pencil) icon in the channel header to open “Edit group”, and use the “Copy Channel ID” row to copy the channel's UUID to your clipboard, ready to paste into your integration's configuration.
The channel_id is a destination address, not a secret and not a credential. It never replaces the bot token: the token authenticates who is posting, the channel id says where. A request still fails with 404 if the bot has not been added to that channel.
Sending messages
One endpoint. One header. One JSON body.
| Endpoint | POST https://api.lummia.io/api/bot/messages |
|---|---|
| Auth header | Authorization: Bot lmbot_… |
| Body | {"channel_id":"<uuid>","text":"<string>"} |
| Success | 200 {"ok":true,"message_id":"<uuid>"} |
The text field is a plain UTF-8 string using the formatting syntax below, up to 4096 bytes. Bots are rate limited to 30 messages per minute.
curl
curl -X POST "$LUMMIA_API_BASE/api/bot/messages" \
-H "Authorization: Bot $LUMMIA_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel_id":"'"$LUMMIA_CHANNEL_ID"'","text":"Deploy finished ✅"}'
Python (requests)
import os
import requests
resp = requests.post(
f"{os.environ['LUMMIA_API_BASE']}/api/bot/messages",
headers={"Authorization": f"Bot {os.environ['LUMMIA_BOT_TOKEN']}"},
json={
"channel_id": os.environ["LUMMIA_CHANNEL_ID"],
"text": "Deploy finished ✅",
},
timeout=10,
)
resp.raise_for_status()
print(resp.json()) # {"ok": True, "message_id": "..."}
Node.js (fetch)
const res = await fetch(`${process.env.LUMMIA_API_BASE}/api/bot/messages`, {
method: "POST",
headers: {
"Authorization": `Bot ${process.env.LUMMIA_BOT_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
channel_id: process.env.LUMMIA_CHANNEL_ID,
text: "Deploy finished ✅",
}),
});
if (!res.ok) throw new Error(`Bot API ${res.status}`);
console.log(await res.json()); // { ok: true, message_id: "..." }
Environment variables & safe token usage
export LUMMIA_API_BASE="https://api.lummia.io"
export LUMMIA_BOT_TOKEN="lmbot_paste-the-token-shown-once-on-create"
export LUMMIA_CHANNEL_ID="00000000-0000-0000-0000-000000000000"
- Never commit a token to source control — read it from an environment variable or secret manager.
- Store it in your CI/CD secrets, not in a pipeline file.
- If a token leaks, regenerate it immediately; the old one stops working at once.
Text formatting
Bot text uses the exact same formatting as a normal chat message. Everything below is what the client actually renders — nothing more. Real \n characters in the JSON string become line breaks.
inline code
Fenced code blocks
Wrap multiple lines in triple backticks, with an optional language on the opening line:
```bash
kubectl rollout status deploy/web
```
Notes
- Italic is double underscore __like this__. Single *asterisks* and single _underscores_ are not italic.
- Emoji are plain Unicode — paste them straight into the text.
- Escape a literal formatting character with a backslash, e.g. \*not bold\*.
- There are no headings, bullet lists, tables, or images — those Markdown constructs are not parsed.
Examples
Full request bodies with the JSON correctly escaped, each with a preview.
Production incident
{
"channel_id": "00000000-0000-0000-0000-000000000000",
"text": "🚨 **Production incident** — API latency\n\n> p99 latency is 4.2s (SLO: 300ms)\n\nAffected: __checkout__, __search__\nRunbook: [open](https://example.com/runbooks/latency)\nOn call: @oncall"
}
Preview
p99 latency is 4.2s (SLO: 300ms)
Affected: checkout, search
Runbook: open
On call: @oncall
CI report
{
"channel_id": "00000000-0000-0000-0000-000000000000",
"text": "✅ **CI passed** — `main` @ `a1b2c3d`\n\n~~flaky~~ tests all green: **412 passed**, 0 failed\nDuration: 3m 41s\n\n```\nsuite: unit + integration\ncoverage: 91.2%\n```"
}
Preview
main
@ a1b2c3dDuration: 3m 41s
suite: unit + integration
coverage: 91.2%
Failed deployment
{
"channel_id": "00000000-0000-0000-0000-000000000000",
"text": "❌ **Deploy failed** — web @ v1.8.0\n\n> step \"migrate\" exited 1\n\nRolled back to __v1.7.4__. Logs: [view build](https://example.com/builds/9182)\nSecret token was ||not|| exposed."
}
Preview
step "migrate" exited 1
Rolled back to v1.7.4. Logs: view build
Secret token was not exposed.
Errors
Every failure is a JSON body with an error field.
| Status | Body | Meaning |
|---|---|---|
| 400 | {"error":"channel_id is required"} | No channel_id in the body. |
| 401 | {"error":"unauthorized"} | Missing / malformed header, or an unknown, revoked, or deleted token. |
| 404 | {"error":"Not found"} | The bot is not a member of the channel, the channel is in another team or archived, or the channel id is malformed. One flat answer, no existence oracle. |
| 409 | {"error":"no_recipient_devices",…} | No member of the channel has a registered device to encrypt the message for. |
| 422 | {"error":"text must be a non-empty string"} | Empty / whitespace-only text. |
| 422 | {"error":"text is larger than the maximum allowed size"} | Text over 4096 bytes. |
| 429 | {"error":"rate_limited",…} | Over 30 messages per minute for this bot. |
Troubleshooting
- 401 — regenerate the token and update your secret; make sure the header is Authorization: Bot <token> (not Bearer).
- 404 — double-check the channel id and confirm the bot was added to that channel.
- 409 no_recipient_devices — no one in the channel currently has a device to receive the message; have a member sign in on the desktop app, then retry.
- 429 — slow down; back off and retry after the current minute.
Security
Your integration sends the bot's own plaintext to the API over HTTPS. At the gateway Lummia encrypts that bot message for the channel's current recipient devices; what is stored and broadcast is ciphertext. The bot holds no keys of its own and never receives the channel's conversation key, so it cannot read anything in the channel — not its own delivered messages, and not anyone else's.
Adding a bot to a channel does not make human messages readable by the server. A bot is a one-way publisher: text in, encrypted message out. Keep the lmbot_ token secret; anyone holding it can post as the bot into every channel the bot belongs to.