<CLAWBOO_HOME>/clawboo.db (default ~/.clawboo/clawboo.db); these routes serve and mutate local state and do not require the Gateway to be up. All POST/PUT bodies are parsed by express.json({ limit: '2mb' }).
The order in
api/index.ts matters: /api/cost-records/summary and /api/exec-settings/all are registered before their shorter prefixes so the two-segment paths are not swallowed.Routes
Cost records: /api/cost-records
Token-usage records, one row per accounted run. The POST handler computes USD from a built-in per-model pricing table (calculateCostUsd), and the summary route aggregates the last 30 days for the cost dashboard.
GET /api/cost-records
Lists cost records, newest first, capped at 500.
- Query params:
- Request body: none.
Responses
200 OK: the matching records (a costRecords row array):
500 Internal Server Error: a DB failure:
Example
POST /api/cost-records
Records one run’s token usage. The handler computes costUsd from the model name and token counts, then upserts a placeholder agents row (the cost_records.agentId foreign key requires the agent to exist) before inserting the record.
- Request body:
Pricing is a built-in table keyed by Claude model ids (opus / sonnet / haiku tiers), with a substring fallback and a
default of 15 per million input/output tokens. An unrecognized model is priced at the default rate.Responses
400 Bad Request: body is not an object:
400 Bad Request: a required field is missing (inputTokens / outputTokens are checked for null/undefined, so 0 passes):
200 OK: the record was inserted:
500 Internal Server Error: a DB failure:
Example
GET /api/cost-records/summary
Aggregates the last 30 days of cost records into dashboard totals, a per-agent breakdown (agent name joined from agents), and a 30-day time series with zero-filled empty days. Takes no parameters.
- Path/query params: none.
- Request body: none.
Responses
200 OK: the aggregation:
500 Internal Server Error: a DB failure:
Example
Chat history: /api/chat-history
Persists per-session chat transcripts in the chat_messages table. Each row stores a JSON-serialized TranscriptEntry; reads parse the JSON back, skipping any corrupt row.
GET /api/chat-history
Loads a session’s transcript entries, oldest first.
- Query params:
- Request body: none.
Responses
400 Bad Request: missing sessionKey:
200 OK: the parsed transcript entries (rows that fail JSON parse are dropped):
500 Internal Server Error: a DB failure:
Example
POST /api/chat-history
Batch-inserts transcript entries for a session. Inserts are idempotent; each row carries the entry’s entryId and conflicts on the unique entry_id index do nothing. Entries without an entryId are skipped.
- Request body:
Responses
400 Bad Request: body is not an object:
400 Bad Request: sessionKey missing or entries not a non-empty array:
200 OK: inserted (idempotent on entryId; saved counts the entries received, not the rows actually written):
500 Internal Server Error: a DB failure:
Example
DELETE /api/chat-history
Clears every message for a session. Used when an agent is deleted.
- Query params:
sessionKey(required). - Request body: none.
Responses
400 Bad Request: missing sessionKey:
200 OK: cleared:
500 Internal Server Error: a DB failure:
Example
Graph layout: /api/graph-layout
Persists Ghost Graph node positions in the graph_layouts table, keyed by the (name, gatewayUrl) unique index. name distinguishes scopes (e.g. atlas-radial, team-<id>, default).
GET /api/graph-layout
Loads saved positions for a layout. Note the query param is url, not gatewayUrl.
- Query params:
- Request body: none.
Responses
200 OK: the saved layout, or an empty positions map when nothing is stored:
This route never returns an error status. A miss returns
{ positions: {} }, and a thrown DB error is also caught and returned as { positions: {} } (HTTP 200).Example
POST /api/graph-layout
Upserts positions for a layout (conflict on (name, gatewayUrl) updates layoutData + updatedAt).
- Request body:
Responses
400 Bad Request: body is not an object:
400 Bad Request: missing gatewayUrl:
200 OK: upserted:
500 Internal Server Error: a DB failure:
Example
Personality: /api/personality
Stores per-agent personality slider values in the agents.personality_config column as a JSON wrapper { values, customText }. SQLite is the source of truth for slider values; the merged SOUL.md is written separately by the client.
GET /api/personality
Loads an agent’s stored personality values and optional custom text.
- Query params:
agentId(required). - Request body: none.
Responses
400 Bad Request: missing agentId:
200 OK: the stored values, or nulls when nothing is stored or the blob is corrupt:
500 Internal Server Error: a DB failure:
Example
POST /api/personality
Upserts an agent’s personality config. The handler ensures a placeholder agents row exists, then sets personality_config to JSON.stringify({ values, customText }). A blank/whitespace customText is stored as null.
- Request body:
Responses
400 Bad Request: body is not an object:
400 Bad Request: agentId or values missing:
200 OK: upserted:
500 Internal Server Error: a DB failure:
Example
Skills: /api/skills
Tracks skill installs in the skills table. The per-agent association lives in the row’s metadata.agentIds array, so a single skill row can be shared across agents. POST runs a supply-chain injection scan and blocks a flagged install with a 422.
GET /api/skills
Lists installed skills, newest first. With agentId, filters to rows whose metadata.agentIds includes that agent.
- Query params:
agentId(optional). - Request body: none.
Responses
200 OK: the skill rows:
500 Internal Server Error: a DB failure (note: skills: [] is still present):
Example
POST /api/skills
Adds a skill (a capability annotation) to an agent. Before recording anything, the handler runs scanForInjection over the install blob (name + source + category + the raw body). A finding blocks the install with 422 and writes a blocked-install audit row; a clean install is also audited (the forensic trail). On a clean scan, an existing skill row merges the agentId into metadata.agentIds; otherwise a new row is inserted.
- Request body:
Responses
400 Bad Request: body is not an object:
400 Bad Request: a required field is missing or not a non-empty string (or category is a non-string):
422 Unprocessable Entity: the injection scan found a destructive / exfil / injection / supply-chain pattern; the install is blocked and audited:
200 OK: installed (merged into an existing row, or a new row inserted):
500 Internal Server Error: a DB failure:
Example
DELETE /api/skills
Removes an agent from a skill’s metadata.agentIds. If that was the last agent, the skill row is deleted entirely; otherwise the row is kept with the agent removed.
- Query params:
id(skill id, required) andagentId(required). - Request body: none.
Responses
400 Bad Request: id or agentId missing:
200 OK: skill not found (idempotent no-op):
200 OK: the agent was the last holder; the row was deleted:
200 OK: the agent was removed but the row remains (other agents still hold it):
500 Internal Server Error: a DB failure:
Example
Exec settings: /api/exec-settings
Stores per-agent execution permission settings in the agents.exec_config column as JSON. Read per agent, read all agents at once during fleet hydration, or upsert one agent.
GET /api/exec-settings
Loads one agent’s execution settings.
- Query params:
agentId(required). - Request body: none.
Responses
400 Bad Request: missing agentId:
200 OK: the parsed exec_config, or null when none is stored:
500 Internal Server Error: a DB failure:
Example
GET /api/exec-settings/all
Returns a map of every agent’s execAsk value. Rows without an exec_config, or with malformed JSON, or whose execAsk is not a string, are skipped. Used during fleet hydration.
- Path/query params: none.
- Request body: none.
Responses
200 OK: the per-agent execAsk map:
500 Internal Server Error: a DB failure:
Example
POST /api/exec-settings
Upserts one agent’s execution settings. The handler ensures a placeholder agents row exists, then stores JSON.stringify(values) in exec_config.
- Request body:
Responses
400 Bad Request: body is not an object:
400 Bad Request: agentId or values missing:
200 OK: upserted:
500 Internal Server Error: a DB failure:
Example
Fleet summary: /api/fleet/summary
A read-only aggregation that joins existing tables/streams into one overview; it never recomputes or re-derives state. It counts live (non-archived) agents per runtime, gets each runtime’s class + health from the adapters and the OpenClaw source, rolls up the last 24h of board tasks and verification verdicts, and counts governance budgets. The per-runtime tile loop is runtime-id-agnostic (open-set runtime strings).
- Path/query params: none.
- Request body: none.
Responses
200 OK: the overview:
A runtime with no agent rows still appears (with zero counts) if an adapter or the OpenClaw source reports for it; OpenClaw is always
connected-substrate and its healthOk reflects whether the server-side source connection is connected.500 Internal Server Error: a failure building the summary:
Example
Boo Zero context: /api/boo-zero/*
Boo Zero is the universal team leader. These routes store the markdown briefs it reads (per-team and global), a Clawboo-side display-name override, and the runtime-neutral leader override that decides which agent is Boo Zero at all. Per-team briefs live in the boo_zero_team_briefs table (FK-cascades on team delete); the global brief, the display name, and the leader override live in the settings key/value table.
A missing brief returns
null content, not a 404; the UI then falls back to a client-side default brief. Likewise a missing display name returns name: null so the caller falls back to the Gateway-side agent name.GET /api/boo-zero/team-briefs/:teamId
Loads a team’s Boo Zero brief.
- Path params:
teamId(required). - Request body: none.
Responses
400 Bad Request: missing teamId:
200 OK: the stored brief, or nulls when none exists:
500 Internal Server Error: a DB failure:
Example
PUT /api/boo-zero/team-briefs/:teamId
Upserts a team’s brief (conflict on teamId updates content + updatedAt).
- Path params:
teamId(required). - Request body:
Responses
400 Bad Request: missing teamId:
400 Bad Request: body missing a string content:
200 OK: upserted:
500 Internal Server Error: a DB failure:
Example
DELETE /api/boo-zero/team-briefs/:teamId
Removes a team’s brief. Idempotent; deleting a non-existent brief is a no-op. (The FK cascade already cleans briefs up when the team itself is deleted; this route is for an explicit user action.)
- Path params:
teamId(required). - Request body: none.
Responses
400 Bad Request: missing teamId:
200 OK: removed (or already absent):
500 Internal Server Error: a DB failure:
Example
GET /api/boo-zero/global-brief
Loads the global Boo Zero brief from the settings key boo-zero:global-brief.
- Path/query params: none.
- Request body: none.
Responses
200 OK: the stored brief, or nulls when unset:
updatedAt is always null on this route; the global brief is stored in the settings KV table, and the handler does not re-query the row’s timestamp.500 Internal Server Error: a DB failure:
Example
PUT /api/boo-zero/global-brief
Sets the global Boo Zero brief.
- Request body:
Responses
400 Bad Request: body missing a string content:
200 OK: saved (updatedAt is Date.now()):
500 Internal Server Error: a DB failure:
Example
GET /api/boo-zero/display-name/:agentId
Loads the Clawboo-side display-name override for Boo Zero, keyed by agent id, from the settings key boo-zero:display-name:<agentId>.
- Path params:
agentId(required). - Request body: none.
Responses
400 Bad Request: missing agentId:
200 OK: the override, or null when unset:
500 Internal Server Error: a DB failure:
Example
PUT /api/boo-zero/display-name/:agentId
Sets the display-name override. The value is trimmed and truncated to 80 chars; an empty string clears the override.
- Path params:
agentId(required). - Request body:
Responses
400 Bad Request: missing agentId:
400 Bad Request: body missing a string name:
200 OK: saved (returns the trimmed/truncated value actually stored):
500 Internal Server Error: a DB failure:
Example
GET /api/boo-zero/override
Reads the runtime-neutral leader override stored in the settings key boo-zero:agent-id, alongside the Boo Zero that resolveBooZero currently lands on and which rung of its chain (override → native → OpenClaw) produced it.
- Request body: none.
Responses
200 OK: the stored override (null when unset or cleared), the effective leader, and the tier:
tier is derived from the stored setting first, so a non-null overrideAgentId always reports 'override'.
500 Internal Server Error: a DB failure:
Example
POST /api/boo-zero/override
Sets or clears the override. Any runtime is legal by design (the resolver does no runtime check), which is what lets a non-native agent lead every team in a mixed install. Setting validates that the agent row exists and is not archived, so a stale id is never stored; clearing writes an empty value, restoring the default override → native → OpenClaw chain.
- Request body:
Responses
400 Bad Request: the body is not an object, or has no agentId key:
400 Bad Request: agentId is neither null nor a non-empty string:
404 Not Found: the id does not match a live agent row:
200 OK: stored (or cleared), with the re-resolved effective leader:
500 Internal Server Error: a DB failure:
Example
Error envelope
Every error response in this group is the standard envelope{ error: string }, except the skills routes (and the graph-layout POST), which use { ok: false, error: string }. The skills GET 500 additionally carries skills: [], and the skills POST 422 carries findings: InjectionFinding[]. The graph-layout GET never returns an error status; a miss or a thrown error both yield { positions: {} } (HTTP 200).
See also
- Cost dashboard + budgets, the UI over cost records
- Governance API, budgets and the budget kill-switch
- Agents API, the agent registry that
agentIdreferences - Teams API, teams, rules, and team-chat (
teamIdreferences) - Boo Zero, briefs, rules, and display name in the UI
- Using the Ghost Graph, what
graph-layoutpositions - Database schema,
cost_records,chat_messages,graph_layouts,skills,boo_zero_team_briefs,agents - REST API overview