Canonical reference for the DashClaw SDK (Node v5.3.0 / Python v5.3.0). Node.js and Python parity across all core governance features.
New
Looking for the whole surface: every page, endpoint, SDK method, CLI command, MCP tool, and hook, with live-captured examples and a stable/experimental mark on each?
Open the Complete Platform Guide →npm install dashclaw
import { DashClaw } from 'dashclaw';
const claw = new DashClaw({
baseUrl: process.env.DASHCLAW_BASE_URL,
apiKey: process.env.DASHCLAW_API_KEY,
agentId: 'my-agent'
});// 1. Ask permission — your abort IS the enforcement on the SDK path
const decision = await claw.guard({
action_type: 'deploy',
risk_score: 85,
declared_goal: 'Update the auth service'
});
if (decision.decision === 'block') {
throw new Error(`Blocked: ${decision.reason || decision.reasons?.join(', ')}`);
}
// 2. Log intent. The server re-evaluates policy here and is the
// authoritative source for HITL gating.
const { action, action_id } = await claw.createAction({
action_type: 'deploy',
declared_goal: 'Update the auth service'
});
// 3. If the server flagged this, wait for a human operator.
// Pass createAction's action_id — NOT the guard's decision_id (a.k.a. decision.action_id).
if (action?.status === 'pending_approval') {
await claw.waitForApproval(action_id);
}
try {
// 4. Log evidence
await claw.recordAssumption({
action_id,
assumption: 'Tests passed'
});
// ... deploy ...
// 5. Record outcome
await claw.updateOutcome(action_id, { status: 'completed' });
} catch (err) {
await claw.updateOutcome(action_id, { status: 'failed', error_message: err.message });
}@dashclaw/mcp-server exposes DashClaw governance over Model Context Protocol. Any MCP-compatible client gets 15 governance tools across 4 groups (core governance, retrospection, agent identity, team tasks) plus 3 read-only resources.
| Tool | Description | Key Inputs |
|---|---|---|
Core governance dashclaw_guard | Evaluate policies before risky actions | action_type, declared_goal, risk_score |
| dashclaw_record | Log action to audit trail | action_type, declared_goal, status, session_id |
| dashclaw_invoke | Execute governed capability | capability_id, declared_goal, payload |
| dashclaw_capabilities_list | Discover available APIs | category, risk_level, search |
| dashclaw_policies_list | List active policies | agent_id |
| dashclaw_wait_for_approval | Wait for human decision | action_id, timeout_seconds |
| dashclaw_session_start | Register agent session | agent_id, workspace |
| dashclaw_session_end | Close session | session_id, status, summary |
| dashclaw_session_retro | Read the session's own defensibility retro (posture + evidenced findings) | session_id |
Retrospection dashclaw_assumption_record | Record an unverified assumption underpinning an action | action_id, assumption, basis |
| dashclaw_decisions_recent | Recent governed-action ledger | agent_id, action_type, decision, since |
Agent identity dashclaw_pair | Enroll identity: generate keypair locally, submit public key for approval | agent_id, agent_name, wait |
Team Tasks dashclaw_task_create | Create a Team Task — one record per multi-agent /team run | task_id, instruction, origin, lead_agent |
| dashclaw_task_event | Append one event to a Team Task timeline | task_id, from_agent, to_agent, type, summary |
| dashclaw_task_update | Update a Team Task: status transitions and stored transport session ids | task_id, status |
| URI | Description |
|---|---|
| dashclaw://policies | Active policy set |
| dashclaw://agent/{agent_id}/history | Recent action history (last 50) |
| dashclaw://status | Instance health + operational metrics |
Config resolution: CLI args > env vars > defaults. Three config values: url (DASHCLAW_URL, default localhost:3000), apiKey (DASHCLAW_API_KEY), agentId (DASHCLAW_AGENT_ID).
{
"mcpServers": {
"dashclaw": {
"command": "npx",
"args": ["@dashclaw/mcp-server"],
"env": {
"DASHCLAW_URL": "https://your-instance.vercel.app",
"DASHCLAW_API_KEY": "oc_live_..."
}
}
}
}mcp_servers=[{
"type": "url",
"url": "https://your-instance.vercel.app/api/mcp",
"headers": {"x-api-key": "oc_live_..."},
"name": "dashclaw"
}]Settings → Connectors → Add custom connector https://your-instance.vercel.app/api/mcp Connect → log in to DashClaw → Authorize. No API key in the UI: the instance runs its own OAuth (DCR + PKCE). Guide: docs/CLAUDE-DESKTOP-PLUGIN.md
In chat clients the connector governs cooperatively: the agent, guided by the governance skill, calls dashclaw_guard / dashclaw_invoke and records its decisions; it is not a kernel-level block, so a non-compliant model could still call a native tool without consulting guard. Hard PreToolUse blocking (fail-closed deny) is a property of the CLI hook path (Claude Code / Codex / Hermes); Cowork hard-gating is not yet verified.
@dashclaw/cli handles terminal approvals and self-host diagnostics. npm run doctor runs the same engine locally with filesystem-level fix powers.
Diagnoses your instance (database, configuration, auth, deployment, SDK reachability, governance staleness, data hygiene, write-path canaries (synthetic self-cleaning writes that prove heartbeat, action-ledger, and guard-audit inserts land) and this machine (stale compiled mcp-server lib, .gitattributes drift, local schema behind code, stale global CLI shim, broken hook installs, leaked machine-scope env vars). Report-only by default; --fix applies safe repairs, re-checks, and prints a what-changed report. Invokes GET /api/doctor and POST /api/doctor/fix (admin keys). For operators, npm run doctor -- --fix on the host adds .env writes, migrations, and default-policy seeding (backs up .env before any write).
npm install -g @dashclaw/cli dashclaw doctor # report-only (default — applies nothing) dashclaw doctor --fix # apply safe fixes, re-check, report what changed dashclaw doctor --json # CI / scripts (includes local machine checks) dashclaw doctor --category database,config # Config resolution: env vars → ~/.dashclaw/config.json (600) → interactive prompt dashclaw logout # remove saved config # Self-host operator (filesystem-level fixes need --fix) npm run doctor -- --fix
The doctor proves your instance works from the inside; the live host canary proves your deployment works from the outside. scripts/live-canary.mjs probes your production hosts hourly as a real unauthenticated client: marketing page, docs, demo entry, trial-mint fail-closed (it passes on the Turnstile rejection, so it never mints junk trials), OAuth discovery, and the MCP handshake's 401 challenge; it files the verdict to POST /api/live-canary. Verdicts render on /setup#live-canary; a fresh failure also raises a posture auditability finding. Canary traffic is stored in its own table and never touches the action or guard ledgers.
# One-off run against your hosts: LIVE_CANARY_MARKETING_ORIGIN=https://your-site \ LIVE_CANARY_HOSTED_ORIGIN=https://your-instance \ node scripts/live-canary.mjs # Scheduled: .github/workflows/live-canary.yml runs hourly. # To report verdicts to your instance, add two repo secrets # (Settings -> Secrets and variables -> Actions): # LIVE_CANARY_REPORT_URL e.g. https://my-dashclaw.vercel.app # LIVE_CANARY_REPORT_KEY an operator API key on that instance # If that key's org is not org_default, also set DASHCLAW_CANARY_ORG_ID # on the instance so /setup renders the canary's runs.
plugins/dashclaw/.claude-plugin/plugin.json is the Claude Code plugin manifest. Distributes the DashClaw MCP server (.mcp-claude.json) plus the dashclaw-governance skill as one installable bundle. Full step-by-step at /guides/claude-code.
# No clone required — the CLI downloads the hooks bundle from your instance: npm i -g @dashclaw/cli dashclaw install claude # prompts for endpoint + API key dashclaw install claude --trial # hosted signup, paste the key # Working from a repo checkout instead: npm run hooks:install
dashclaw install codex wires the same governance surface DashClaw ships for Claude Code into Codex's ~/.codex/config.toml: MCP server config, PreToolUse / PostToolUse / Stop hooks, and the governance protocol in AGENTS.md. Idempotent; re-run after every git pull. Full step-by-step at /guides/codex.
# One command from the DashClaw repo root: node cli/bin/dashclaw.js install codex --project /path/to/your/project # Optional: opt in to legacy notify config for turn-complete records node cli/bin/dashclaw.js install codex --project /path/to/your/project --include-notify
plugins/dashclaw/.hermes-plugin/ ships eight lifecycle hooks for Hermes Agent: pre/post tool, pre/post LLM call with per-turn governance context injection, on-session start/end with live ingest finalize, secret redaction in tool output, and subagent_stop ROI tracking. Full step-by-step at /guides/hermes.
# macOS / Linux — symlinks the plugin, appends 8 hook entries to # ~/.hermes/config.yaml between sentinel markers (idempotent). bash scripts/install-hermes-plugin.sh # Windows powershell -File scripts/install-hermes-plugin.ps1 # 4-section sanity check hermes dashclaw doctor
@dashclaw/openclaw-plugin wires governance into the OpenClaw agent framework. Intercepts before_tool_call / after_tool_call lifecycle hooks, calls guard / record / wait-for-approval automatically, and ships a HOOK.md pack the openclaw CLI installs. Tool classification vocabulary aligns with DashClaw guard action types.
dashclaw-governance teaches governed agents how to use DashClaw correctly: risk thresholds, decision handling (allow / warn / block / require_approval), action recording, approval-wait protocol, and session lifecycle. Pairs with @dashclaw/mcp-server. Auto-installed by the Claude Code, Codex, and Hermes plugins; also downloadable as a standalone zip.
# Zip download from this instance: curl -O `https://<your-deployment>/downloads/dashclaw-governance.zip` # Or copy the source dir directly: cp -r public/downloads/dashclaw-governance ~/.claude/skills/ # Already auto-installed if you ran one of the plugin installers above.
const claw = new DashClaw({ baseUrl, apiKey, agentId, agentName, authToken });| Parameter | Type | Required | Description |
|---|---|---|---|
| baseUrl / base_url | string | Yes | Dashboard URL |
| apiKey / api_key | string | Yes | API Key |
| agentId / agent_id | string | Yes | Unique Agent ID |
| agentName / agent_name | string | No | Human-readable agent label stored in audit trail for attribution. Automatically included on guard() calls if not overridden. |
| authToken / auth_token | string | No | Phase 2: JWT bearer token from your OIDC provider. When set, the server verifies the signature via JWKS and returns verification_status on every guard response; the JWT sub claim overrides agent_id in the audit record. See docs/agent-identity.md. |
Evaluate guard policies for a proposed action. Call this before risky operations. With a non_fabrication policy active, pass `content` + `sourceOfTruth` to verify outbound text before it goes out: a violation blocks (or routes to approval) and is returned under `non_fabrication` with a signed, re-verifiable receipt.
| Parameter | Type | Required | Description |
|---|---|---|---|
| action_type | string | Yes | Proposed action type |
| risk_score | number | No | 0-100 |
| content | string | No | Outbound text to non-fabrication check (used by a non_fabrication policy) |
| sourceOfTruth | object | No | Facts the content may state: { allowedFacts, requiredFacts, forbiddenPatterns?, extract? } |
Returns: Promise<{ decision: string, reasons: string[], risk_score: number, risk_breakdown: object, agent_risk_score: number | null, non_fabrication?: object[] }>
const result = await claw.guard({ action_type: 'deploy', risk_score: 85 });Every guard response carries a risk_breakdown sibling next to risk_score: an itemized ledger of how the score was composed, so no decision ever rests on an unexplained number: base (the action type's base score) → modifiers (server risk factors, each named with its points) → server_total → template (a matching risk template, if any) → client_reported (the risk_score you passed) → effective (the max of those; the server never lets a client under-report below its own floor) → predictive(history-based adjustment from the agent's failure rate and velocity) → final. The breakdown is persisted with the decision and rendered as the Risk composition panel on the action, decision, and replay detail pages.
The score behind guard is pinned by a golden-vector corpus. DashClaw mines your own decision ledger for shapes where the scorer looks miscalibrated (over-scored benign actions, under-scored dangers, repeatedly approved shapes) and renders them as evidence cards on /policies → Calibration proposals; ratify and dismiss are buttons. The same proposals are available at GET /api/calibration/proposals; a ratified proposal is a recorded judgment (?status=ratifiedis the maintainer's forge queue). Nothing changes scoring until the vector is forged and committed.
Policy-tuning proposals only ever loosen (raise thresholds that over-interrupt). Tightening proposals own the other direction: when the same high-risk action type repeatedly reaches allow with no policy in its way, the pattern (mirrored one-to-one from the posture finding) is rendered as an evidence card on /policies → Tightening proposals. Ratify creates an active require_approval policy for that action type in the same click and resolves the posture finding; dismiss records why and stops the re-proposal. The same queue is available at GET /api/policies/tightening. Nothing auto-applies: every policy exists because a human ratified it.
The tightening mirror. When a policy's interruptions are approved ~100% of the time (a wrong interrupt by definition), the pattern renders as an evidence card in the /policies judgment queue. Ratify relaxes the policy in the same click: carve the always-approved action type out of its envelope (the rest stays governed) or, when no surgical fix exists, deactivate it. Undo keeps the change; the policy is a first-class row at /policies. The same queue is available at GET /api/policies/loosening. Harness traffic never counts as evidence, and risk-threshold policies stay with tuning.
Create a governance action record. The server re-evaluates policy at this point, so this call is the authoritative source for HITL gating: if policy requires human review, the response is HTTP 202 with action.status='pending_approval'. Always check action.status before assuming the action is clear to execute. Non-fabrication (optional): pass content + sourceOfTruth (Node) / content + source_of_truth (Python) to have a non_fabrication policy verify the outbound content before the action proceeds: a violation blocks or routes to approval and is recorded with a signed receipt. Session linkage (optional): pass session_id (the sess_… id from a started agent session) to attribute this action to that session, so /sessions can aggregate per-session action count, cost, and risk.
Returns: Promise<{ action: { action_id, status, ... }, action_id, decision, security }>
const { action, action_id } = await claw.createAction({ action_type: 'deploy' });
if (action?.status === 'pending_approval') {
// gate execution on waitForApproval — see the method below
}Wait for a human operator to approve or deny an action. Opens an SSE stream on /api/stream and falls back to polling /api/actions/:id every 5 seconds. Resolves when action.approved_by is set; throws ApprovalDeniedError when the operator denies AND when the approval expires server-side before a decision (a distinct third outcome: check err.status === 'expired' to tell a lapsed window from an operator 'no'; expired approvals render in their own section on /approvals and can no longer release anything); throws on timeout. IMPORTANT: pass the action_id returned by createAction(), NOT the action_id returned by guard(). They refer to different database tables and waiting on a guard decision ID will never resolve. Approvals can be resolved from the dashboard (/approvals), the CLI (dashclaw approve <id>), the mobile PWA (/approve), or, if the instance has Telegram configured (TELEGRAM_BOT_TOKEN), via an inline Approve/Reject button pushed to the admin Telegram chat. All four surfaces call the same /api/approvals/:id endpoint, so waitForApproval unblocks the agent within ~1 second regardless of which surface was used.
// Correct — wait on createAction's action_id
const { action, action_id } = await claw.createAction({ action_type: 'deploy' });
if (action?.status === 'pending_approval') {
await claw.waitForApproval(action_id, { timeout: 600_000 });
}Log final results. Accepts status, output_summary, error_message, duration_ms, tokens_in, tokens_out, model, cost_estimate. When tokens + model are supplied without cost_estimate, the server derives cost from the pricing table.
await claw.updateOutcome(action_id, {
status: 'completed',
tokens_in: result.usage.input_tokens,
tokens_out: result.usage.output_tokens,
model: result.model,
});Track agent beliefs.
await claw.recordAssumption({ action_id, assumption: '...' });Get current risk signals across all agents.
Returns: Promise<{ signals: Object[] }>
const { signals } = await claw.getSignals();Side-effect-free dry-run of a single proposed policy against recent historical actions: nothing is persisted. Use it to preview how a policy would have decided before committing it; pairs with guard() for live enforcement. Node SDK only.
| Parameter | Type | Required | Description |
|---|---|---|---|
| policy_type | string | Yes | The policy type to simulate |
| rules | object | Yes | The proposed policy rules |
| days | number | No | How many days of historical actions to evaluate against |
Returns: Promise<{ summary: { total, matches, block, warn, require_approval, allow }, matches, sample_size, window_days }>
const sim = await claw.simulatePolicy({
policy_type: 'risk_threshold',
rules: { max_risk_score: 70 },
days: 30
});
console.log(sim.summary.block, 'of', sim.summary.total, 'would block');Turns a plain-English request into guard-policy drafts. The flow is iterative and never dead-ends: a clear request returns drafts; a vague one returns a best-effort draft plus suggested clarifications; an underspecified one returns clarifications only, never an empty "be more specific" rejection. Send the answered clarifications back in answers to refine. Authored from Policies → Custom → AI generator in the dashboard. Requires an LLM provider key in Settings; without one the endpoint returns 422 with "No LLM provider configured."
Generate guard-policy drafts from natural language. dry_run (default true) previews drafts and is open to any org member; dry_run: false creates the drafts and is admin-only. The dashboard saves the reviewed/edited draft via POST /api/policies rather than creating with dry_run: false.
| Parameter | Type | Required | Description |
|---|---|---|---|
| input_text | string | Yes | Plain-English description of the policy you want (max 5000 chars) |
| dry_run | boolean | No | Preview only (default true). false creates the drafts and requires an admin key |
| answers | [{ id, value }] | No | Answers to clarifications from a prior dry-run call, used to refine the drafts |
Returns: dry_run: { drafts: [{ name, policy_type, rules, confidence }], assumptions: string[], clarifications: [{ id, question, field, suggestions: string[], multi }], warnings, input_hash }
const res = await fetch(`${baseUrl}/api/policies/generate`, {
method: 'POST',
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
input_text: 'stop my agents from deleting things I care about',
dry_run: true
})
});
const { drafts, assumptions, clarifications } = await res.json();
// drafts → a best-effort protected_path draft
// [{
// name: 'Protect critical paths from deletion',
// policy_type: 'protected_path',
// rules: { paths: ['.env', 'secrets/', 'migrations/'], action: 'block' },
// confidence: 0.6
// }]
//
// assumptions → ['Assumed "things I care about" means config and secret files']
//
// clarifications → suggested-value chip sets to tighten the draft
// [
// { id: 'paths', question: 'Which paths should be protected?', field: 'rules.paths',
// suggestions: ['.env', 'secrets/', 'migrations/', 'src/'], multi: true },
// { id: 'strictness', question: 'How strict should the guard be?', field: 'rules.action',
// suggestions: ['block', 'require approval', 'warn'], multi: false }
// ]
// Refine by sending the picked answers back:
await fetch(`${baseUrl}/api/policies/generate`, {
method: 'POST',
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
input_text: 'stop my agents from deleting things I care about',
dry_run: true,
answers: [
{ id: 'paths', value: ['.env', 'secrets/', 'migrations/'] },
{ id: 'strictness', value: 'block' }
]
})
});DashClaw mines your own decision outcomes for policies whose thresholds look wrong in practice (a gate every human rubber-stamps, a cap that never fires) and renders each as an evidence-backed proposal on /policies, where Apply and Dismiss are buttons (apply is a two-step confirm with an inline reason). The same feed is available at GET /api/policies/proposals, which returns { policies, proposals, degradation }. Nothing changes a policy until a human clicks Apply.
Guard evaluation runs under a deadline (DASHCLAW_GUARD_DEADLINE_MS, default 3500 ms). When the deadline fires or an evaluation phase fails (for example a policy webhook), the guard does not silently allow: it falls back, using the per-policy on_failure override first, then the instance-wide DASHCLAW_GUARD_FALLBACK, then fail-closed require_approval, and marks the decision degraded.
Degradations are observable, not just recorded: GET /api/policies/proposals?days=30 returns a degradation rollup: { window_days, total, degraded, rate, last_degraded_at, by_day }. /policiesrenders it as a strip ("N of M decisions were deadline degradations") whenever the count is non-zero. A rising rate means your policies are being decided by fallback, not evaluation; tune the deadline or the failing phase.
Scan untrusted input for potential prompt injection or jailbreak attempts.
| Parameter | Type | Required | Description |
|---|---|---|---|
| text | string | Yes | Untrusted input to scan |
Returns: Promise<{ clean: boolean, risk_level: string, recommendation: string }>
const result = await claw.scanPromptInjection(userInput);
if (!result.clean) {
console.warn('Injection risk:', result.risk_level);
}DashClaw verifies which agent took each action on three independent axes, each returned on the guard response and recorded in the decisions ledger. The current path is JWKS-verified JWTs (Phase 2 / 2b / 2c); the public-key pairing API further down remains for older (v1) integrations. Full setup guide: docs/agent-identity.md.
Attach an OIDC bearer token (or pass authToken to the SDK constructor). DashClaw fetches the issuer's keys from its /.well-known/jwks.json, verifies the signature (EdDSA, RS256–512, ES256–512), and on success overrides any body-supplied agent_id with the token's sub: proof beats self-assertion. A downed issuer fails soft to unverified and never blocks a decision.
import { DashClaw } from 'dashclaw';
const claw = new DashClaw({
baseUrl: process.env.DASHCLAW_BASE_URL,
apiKey: process.env.DASHCLAW_API_KEY,
authToken: agentJwt, // OIDC bearer token minted by your IdP
});
const { decision, verification_status } = await claw.guard({
action_type: 'deploy', risk_score: 80,
});
// verification_status: 'verified' | 'unverified' | 'expired'
// | 'failed' | 'unknown_issuer' | 'exp_too_far'Three independent axes travel back on the response, each in its own field so a downed issuer or absent claim degrades gracefully instead of hard-failing:
verification_status: who signed the token. Configure trust with DASHCLAW_ALLOWED_ISSUER and DASHCLAW_JWT_AUDIENCE.replay_status: whether the token was reused. DASHCLAW_JTI_REPLAY_PROTECTION (off / best_effort / required, default required) blocks a replayed jti, for verified-JWT traffic only; API-key callers are never touched by it.act_status: whether the token is bound to this call. DASHCLAW_ACT_BINDING (default best_effort, blocks only a positive mismatch) compares the request against the token's urn:dashclaw:act-binding claim.One operator runs the same logical agent across several harnesses, and each harness spawns sub-agents. DashClaw encodes this as composed ids: <parent>:<sub> (e.g. claude-code:explore is the explore sub-agent of the claude-code parent). The base id before the first : is the family. No registration step is needed: the convention alone activates the behavior:
Enroll agents via public-key pairing and manage approved identities. Pairing requests are created by agents; approval is an operator action (one click on the /identities page). Once approved, the agent's public key is registered as a trusted identity for signature verification.
Create an agent pairing request. The agent submits its public key and waits for operator approval.
| Parameter | Type | Required | Description |
|---|---|---|---|
| public_key | string | Yes | PEM-encoded RSA public key |
| algorithm | string | No | Key algorithm. Default: RSASSA-PKCS1-v1_5 |
| agent_name | string | No | Human-readable label for the agent |
Returns: { pairing: { id, status, agent_name, created_at } }
// Node SDK — pairing enrollment is canonical
import { DashClaw } from 'dashclaw';
const claw = new DashClaw({ baseUrl, apiKey, agentId });
const { pairing } = await claw.createPairing(publicKeyPem, { algorithm: 'RSASSA-PKCS1-v1_5', agentName: 'my-agent' });
console.log(pairing.id); // pair_...List all pairing requests for the organization. Admin API key required.
Returns: { pairings: Array<{ id, status, agent_name, created_at, approved_at }> }
const res = await fetch('/api/pairings', {
headers: { 'x-api-key': adminApiKey }
});
const { pairings } = await res.json();Get a specific pairing request by ID. Used by agents to poll for approval status.
Returns: { pairing: { id, status, agent_name, created_at, approved_at } }
// Node SDK: block until the operator approves (or poll the route directly)
const paired = await claw.waitForPairing(pairingId);
// HTTP: GET /api/pairings/:id -> { pairing: { status: 'pending' | 'approved' | 'expired' } }Approve a pending pairing request. Admin API key required. On approval, the agent's public key is registered as a trusted identity.
Returns: { pairing: { id, status, approved_at } }
const res = await fetch(`/api/pairings/${pairingId}/approve`, {
method: 'POST',
headers: { 'x-api-key': adminApiKey }
});Directly register an agent's public key as a trusted identity. Admin API key required. Bypasses the pairing flow.
| Parameter | Type | Required | Description |
|---|---|---|---|
| agent_id | string | Yes | Unique agent identifier |
| public_key | string | Yes | PEM-encoded RSA public key |
| algorithm | string | No | Key algorithm. Default: RSASSA-PKCS1-v1_5 |
Returns: { identity: { agent_id, algorithm, created_at } }
// HTTP (admin key)
await fetch(baseUrl + '/api/identities', {
method: 'POST',
headers: { 'x-api-key': adminKey, 'content-type': 'application/json' },
body: JSON.stringify({ agent_id: 'agent-007', public_key: publicKeyPem, algorithm: 'RSASSA-PKCS1-v1_5' }),
});List all registered agent identities for the organization. Admin API key required.
Returns: { identities: Array<{ agent_id, algorithm, created_at }> }
// HTTP (admin key)
const res = await fetch(baseUrl + '/api/identities', { headers: { 'x-api-key': adminKey } });
const { identities } = await res.json();Revoke a registered agent identity. Admin API key required. The agent's public key is removed and signature verification will fail for future actions.
Returns: { success: true }
const res = await fetch(`/api/identities/${agentId}`, {
method: 'DELETE',
headers: { 'x-api-key': adminApiKey }
});Governance packaging: a read-only execution graph and durable action outcomes on actions. Every surface here has a canonical SDK wrapper method in the Node SDK (see sdk/dashclaw.js, 31 methods total). The HTTP examples below are shown first because they're language-agnostic; the equivalent SDK calls are in sdk/README.md. Full OpenAPI definitions are at docs/openapi/critical-stable.openapi.json.
Read-only execution graph (nodes + edges) for any action. Reuses the existing trace data plus correlated assumptions and open loops, zero schema change. Powers the Graph tab on decision replay.
Returns: { rootActionId, nodes: Array<{ id, type, status, riskScore, ... }>, edges: Array<{ source, target, type, label }> }
const res = await fetch(`${baseUrl}/api/actions/${actionId}/graph`, {
headers: { 'x-api-key': apiKey }
});
const { rootActionId, nodes, edges } = await res.json();
// node ids: action:<id>, assumption:<id>, loop:<id>
// edge types: parent_child | related | assumption_of | loop_fromFive-state terminal outcome on every action: closes the audit-trail gap between "what was approved" and "what actually completed." See durable-execution-finality.md.
Record the terminal outcome of an approved action. One-shot: the first successful POST wins, subsequent POSTs return 409 with the current state. status must be one of completed | partial | failed. error_message is required when status=failed; progress (object) is required when status=partial. lost_confirmation is reserved for the system sweep.
Returns: { outcome: { action_id, status, outcome_at, summary, error_message, progress, elapsed_ms }, security: { clean, findings_count } }
await fetch(`${baseUrl}/api/actions/${actionId}/outcome`, {
method: 'POST',
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'completed',
summary: 'Deployed dashclaw 2.13.4 to production'
})
});Read the current outcome state. Returns the full outcome shape including elapsed_ms (outcome_at − created_at, or now − created_at while still pending). Agents call this before retrying to avoid re-executing already-completed actions.
Returns: { action_id, status, outcome_at, summary, error_message, progress, elapsed_ms }
const outcome = await fetch(
`${baseUrl}/api/actions/${actionId}/outcome`,
{ headers: { 'x-api-key': apiKey } }
).then(r => r.json());
// completed → SKIP, failed | lost_confirmation → RETRY,
// pending → WAIT, partial → CLEANUP_THEN_RETRYEvent coverage, orthogonal to posture's policy coverage ("is it governed"). Answers "did the ledger actually see everything that happened." The Claude Code Stop hook POSTs one fail-silent per-turn report comparing transcript tool_use ground truth against the session's recorded action map; every closed action also carries a close_source (outcome | stop_autoclose | direct) so outcome coverage is computable from durable data. Operator surface only; no SDK wrapper. Powers the Coverage column on the /agents page and a posture finding when either figure drops below 90% (min 20 sampled).
Per-agent record coverage (sum(recorded)/sum(expected) over a 24h window) and outcome coverage (share of hook-recorded actions closed with a real outcome vs Stop-hook auto-close). An agent with no reports renders an explicit no-evidence state rather than 100%.
| Parameter | Type | Required | Description |
|---|---|---|---|
| window_hours | number | No | 1-168, default 24 |
| include_synthetic | string | No | "1" includes synthetic/loadtest agents; diagnostics only, real views and posture always exclude them |
Returns: { coverage: [{ agentId, expected, recorded, recordPct, outcomePct, outcomeSample }], window_hours, lastUpdated }
const res = await fetch('/api/coverage', {
headers: { 'x-api-key': apiKey },
});
const { coverage } = await res.json();Multi-agent lineage as persisted evidence, not a client-side guess. Every recorded action carries its harness session (harness_session_id); subagent leaf actions carry the subagent instance uuid (subagent_uuid); spawn rows carry the spawned agent's uuid via outcome_metadata.spawned_agent_uuid on the outcome PATCH, the one outcome_metadata key the server persists. A fan-out reads as one governed unit with per-leaf attribution, joined at read time. Powers the Fan-outs panel on the /agents page, deep-linking to /swarm?swarm_id=<harness_session_id>.
Recent multi-agent harness sessions, newest-first, grouped by harness_session_id. Synthetic/loadtest agents excluded from the default view.
| Parameter | Type | Required | Description |
|---|---|---|---|
| window_hours | number | No | 1-168, default 24 |
| limit | number | No | 1-100, default 20 |
| include_synthetic | string | No | "1" includes synthetic/loadtest agents; diagnostics only, the /agents panel never sets it |
Returns: { fanouts: [{ harness_session_id, parent_agent_id, agents, agent_count, spawn_count, action_count, linked_leaf_count, first_at, last_at }], window_hours, lastUpdated }
const res = await fetch('/api/agents/fanouts', {
headers: { 'x-api-key': apiKey },
});
const { fanouts } = await res.json();Operator-facing routes exposed only when DASHCLAW_HOSTED=true. These are not SDK methods; they produce the API key that downstream SDKs consume. Self-host deploys are unaffected; all routes return 404 when the flag is unset.
Mint a new trial workspace. Public, gated by DASHCLAW_HOSTED flag + Turnstile + IP rate limit. Returns the workspace ID, a one-time API key, and onboarding URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| turnstile_token | string | No | Cloudflare Turnstile challenge token. Required in production; omit in dev bypass mode. |
Returns: { workspace_id, api_key, endpoint, expires_at, trial_action_cap, key_prefix, next_steps_url }
curl -X POST https://hosted.example.com/api/hosted/workspaces \
-H "content-type: application/json" \
-d '{"turnstile_token": "..."}'
# → { "workspace_id": "org_...", "api_key": "oc_live_...", "endpoint": "...",
# "expires_at": "...", "trial_action_cap": 10000, "key_prefix": "oc_live_",
# "next_steps_url": "https://hosted.example.com/connect?hosted=org_..." }Admin: inspect a trial workspace. Requires an admin-role API key.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Workspace (org) ID, e.g. org_abc |
Returns: { workspace_id, status, expires_at, actions_used, trial_action_cap, created_at }
curl https://hosted.example.com/api/hosted/workspaces/org_abc \ -H "x-api-key: <admin_key>"
Admin: manually delete a trial workspace and revoke its API key.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Workspace (org) ID to delete |
Returns: { deleted: true, workspace_id }
curl -X DELETE https://hosted.example.com/api/hosted/workspaces/org_abc \ -H "x-api-key: <admin_key>"
Cron-safe sweeper for expired trial workspaces. Accepts admin-role API key OR X-Cleanup-Secret header. Safe to run repeatedly: idempotent.
| Parameter | Type | Required | Description |
|---|---|---|---|
| X-Cleanup-Secret | header | No | Shared secret set via HOSTED_CLEANUP_SECRET env var. Alternative to admin API key. |
Returns: { swept: number, workspace_ids: string[] }
curl -X POST https://hosted.example.com/api/hosted/cleanup \ -H "X-Cleanup-Secret: $HOSTED_CLEANUP_SECRET"
{ message: "Validation failed", status: 400 }