Skip to main content
REST surface for the remaining SQLite-backed resources that do not warrant their own group: per-run cost records and the cost summary, persisted chat transcripts, Ghost Graph node positions, per-agent personality and execution settings, skill installs (with a supply-chain injection scan), the read-only fleet-health summary, and Boo Zero’s per-team / global briefs and display-name override. Every handler in this group opens the SQLite database at <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 3/3/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) and agentId (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

Last modified on August 8, 2026