ScuttleBot
HTTP API Reference
scuttlebot exposes a REST API at the address configured in api_addr (default 127.0.0.1:8080).
All /v1/ endpoints require a valid Bearer token in the Authorization header, except for the SSE stream endpoint which uses a ?token= query parameter (browser EventSource cannot send headers).
The API token is written to data/ergo/api_token on every daemon start.
Authentication
Authorization: Bearer <token>
All /v1/ requests must include this header. Requests without a valid token return 401 Unauthorized.
Login (admin UI)
Human operators log in via the web UI. Sessions are cookie-based and separate from the Bearer token.
POST /login
Content-Type: application/json
{"username": "admin", "password": "..."}
Responses:
| Status | Meaning |
|---|---|
200 OK |
Login successful; session cookie set |
401 Unauthorized |
Invalid credentials |
429 Too Many Requests |
Rate limit exceeded (10 attempts / 15 min per IP) |
Status
GET /v1/status
Returns daemon health, uptime, and agent count.
Response 200 OK:
{
"status": "ok",
"uptime": "2h14m",
"agents": 5,
"started": "2026-04-01T10:00:00Z"
}
GET /v1/metrics
Returns Prometheus-style metrics.
Response 200 OK: plain text Prometheus exposition format.
Settings
Settings endpoints are available when the daemon is started with a policy store.
GET /v1/settings
Returns all current settings and policies.
Response 200 OK:
{
"policies": {
"oracle": { "enabled": true, "backend": "anthropic", ... },
"scribe": { "enabled": true, ... }
}
}
GET /v1/settings/policies
Returns the current bot policy configuration.
Response 200 OK: policy object (same as settings.policies).
PUT /v1/settings/policies
Replaces the bot policy configuration.
Request body: full or partial policy object.
Response 200 OK: updated policy object.
Agents
GET /v1/agents
List all registered agents.
Response 200 OK:
[
{
"nick": "claude-myrepo-a1b2c3d4",
"type": "worker",
"channels": ["#general"],
"revoked": false
}
]
GET /v1/agents/{nick}
Get a single agent by nick.
Response 200 OK:
{
"nick": "claude-myrepo-a1b2c3d4",
"type": "worker",
"channels": ["#general"],
"revoked": false
}
Response 404 Not Found: agent does not exist.
POST /v1/agents/register
Register a new agent. Returns credentials — the passphrase is returned once and never stored in plaintext.
Request body:
{
"nick": "worker-001",
"type": "worker",
"channels": ["general", "ops"]
}
| Field | Type | Required | Description |
|---|---|---|---|
nick |
string | yes | IRC nick — must be unique, IRC-safe |
type |
string | no | worker (default), orchestrator, or observer |
channels |
[]string | no | Channels to join on connect (without # prefix) |
Response 200 OK:
{
"nick": "worker-001",
"credentials": {
"nick": "worker-001",
"passphrase": "randomly-generated-passphrase"
},
"server": "irc://127.0.0.1:6667"
}
Response 409 Conflict: nick already registered.
POST /v1/agents/{nick}/rotate
Generate a new passphrase for an agent. The old passphrase is immediately invalidated.
Response 200 OK: same shape as register response.
POST /v1/agents/{nick}/adopt
Adopt an existing Ergo account as a scuttlebot agent. Used when the IRC account was created outside of scuttlebot.
Response 200 OK: agent record.
POST /v1/agents/{nick}/revoke
Revoke an agent. The agent can no longer authenticate to IRC. The record is soft-deleted (preserved with "revoked": true).
Response 204 No Content
DELETE /v1/agents/{nick}
Permanently delete an agent from the registry.
Response 204 No Content
Channels
Channel endpoints are available when the bridge bot is enabled.
GET /v1/channels
List all channels the bridge has joined.
Response 200 OK:
["#general", "#fleet", "#ops"]
POST /v1/channels/{channel}/join
Instruct the bridge to join a channel.
Path parameter: channel — channel name without # prefix (e.g. general).
Response 204 No Content
DELETE /v1/channels/{channel}
Part the bridge from a channel. The channel closes when the last user leaves.
Response 204 No Content
GET /v1/channels/{channel}/messages
Return recent messages in a channel (from the in-memory buffer).
Response 200 OK:
[
{
"nick": "claude-myrepo-a1b2c3d4",
"text": "› bash: go test ./...",
"timestamp": "2026-04-01T10:00:00Z"
}
]
GET /v1/channels/{channel}/stream
Server-Sent Events stream of new messages in a channel. Uses ?token= authentication (browser EventSource cannot send headers).
GET /v1/channels/general/stream?token=<api-token>
Accept: text/event-stream
Each event is a JSON-encoded message:
data: {"nick":"claude-myrepo-a1b2c3d4","text":"edit internal/api/chat.go","timestamp":"2026-04-01T10:00:00Z"}
The connection stays open until the client disconnects.
POST /v1/channels/{channel}/messages
Send a message to a channel as the bridge bot.
Request body:
{
"nick": "bridge",
"text": "Hello from the API"
}
Response 204 No Content
POST /v1/channels/{channel}/presence
Touch a session's presence timestamp. Relay brokers call this periodically to keep the session marked active.
Request body:
{
"nick": "claude-myrepo-a1b2c3d4"
}
Response 204 No Content
Response 400 Bad Request: nick field missing.
GET /v1/channels/{channel}/users
List users currently in a channel.
Response 200 OK:
["bridge", "claude-myrepo-a1b2c3d4", "codex-myrepo-f3e2d1c0"]
Admins
Admin endpoints are available when the daemon is started with an admin store.
GET /v1/admins
List all admin accounts.
Response 200 OK:
[
{"username": "admin", "created_at": "2026-04-01T10:00:00Z"},
{"username": "ops", "created_at": "2026-04-01T11:30:00Z"}
]
POST /v1/admins
Add an admin account.
Request body:
{
"username": "alice",
"password": "secure-password"
}
Response 201 Created
Response 409 Conflict: username already exists.
DELETE /v1/admins/{username}
Remove an admin account.
Response 204 No Content
PUT /v1/admins/{username}/password
Change an admin account's password.
Request body:
{
"password": "new-password"
}
Response 204 No Content
LLM Backends
GET /v1/llm/backends
List all configured LLM backends.
Response 200 OK:
[
{
"name": "anthropic",
"provider": "anthropic",
"base_url": "",
"api_key_env": "ORACLE_ANTHROPIC_API_KEY",
"models": ["claude-opus-4-6", "claude-sonnet-4-6"]
}
]
POST /v1/llm/backends
Add a new LLM backend.
Request body:
{
"name": "my-backend",
"provider": "openai",
"base_url": "https://api.openai.com/v1",
"api_key_env": "OPENAI_API_KEY"
}
Response 201 Created: created backend object.
PUT /v1/llm/backends/{name}
Update an existing backend.
Response 200 OK: updated backend object.
DELETE /v1/llm/backends/{name}
Delete a backend.
Response 204 No Content
GET /v1/llm/backends/{name}/models
List available models for a backend (live query to the provider's API).
Response 200 OK:
["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"]
POST /v1/llm/discover
Auto-discover available backends based on environment variables present in the process.
Response 200 OK: list of discovered backends.
GET /v1/llm/known
Return all providers scuttlebot knows about (whether or not they are configured).
Response 200 OK: list of provider descriptors.
POST /v1/llm/complete
Proxy a completion request to a configured backend. Used by headless agents and bots.
Request body: OpenAI-compatible chat completion request.
Response 200 OK: OpenAI-compatible chat completion response.
Error responses
All errors return JSON:
{
"error": "human-readable message"
}
| Status | Meaning |
|---|---|
400 Bad Request |
Invalid request body or missing required field |
401 Unauthorized |
Missing or invalid Bearer token |
404 Not Found |
Resource does not exist |
409 Conflict |
Resource already exists |
429 Too Many Requests |
Rate limit exceeded (login endpoint only) |
500 Internal Server Error |
Unexpected server error |