HomeSDK Documentation

SDK Documentation

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 →

Quick Start

1

Install

npm install dashclaw
2

Initialize

import { DashClaw } from 'dashclaw';

const claw = new DashClaw({
  baseUrl: process.env.DASHCLAW_BASE_URL,
  apiKey: process.env.DASHCLAW_API_KEY,
  agentId: 'my-agent'
});
3

Governance Loop

// 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 });
}

MCP Server

@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.

Tools (15)

ToolDescriptionKey Inputs
Core governance
dashclaw_guard
Evaluate policies before risky actionsaction_type, declared_goal, risk_score
dashclaw_recordLog action to audit trailaction_type, declared_goal, status, session_id
dashclaw_invokeExecute governed capabilitycapability_id, declared_goal, payload
dashclaw_capabilities_listDiscover available APIscategory, risk_level, search
dashclaw_policies_listList active policiesagent_id
dashclaw_wait_for_approvalWait for human decisionaction_id, timeout_seconds
dashclaw_session_startRegister agent sessionagent_id, workspace
dashclaw_session_endClose sessionsession_id, status, summary
dashclaw_session_retroRead the session's own defensibility retro (posture + evidenced findings)session_id
Retrospection
dashclaw_assumption_record
Record an unverified assumption underpinning an actionaction_id, assumption, basis
dashclaw_decisions_recentRecent governed-action ledgeragent_id, action_type, decision, since
Agent identity
dashclaw_pair
Enroll identity: generate keypair locally, submit public key for approvalagent_id, agent_name, wait
Team Tasks
dashclaw_task_create
Create a Team Task — one record per multi-agent /team runtask_id, instruction, origin, lead_agent
dashclaw_task_eventAppend one event to a Team Task timelinetask_id, from_agent, to_agent, type, summary
dashclaw_task_updateUpdate a Team Task: status transitions and stored transport session idstask_id, status

Resources (3)

URIDescription
dashclaw://policiesActive policy set
dashclaw://agent/{agent_id}/historyRecent action history (last 50)
dashclaw://statusInstance health + operational metrics

Configuration

Config resolution: CLI args > env vars > defaults. Three config values: url (DASHCLAW_URL, default localhost:3000), apiKey (DASHCLAW_API_KEY), agentId (DASHCLAW_AGENT_ID).

stdio: Claude Code / Cowork (.mcp.json; Desktop chat uses the OAuth connector below)
{
  "mcpServers": {
    "dashclaw": {
      "command": "npx",
      "args": ["@dashclaw/mcp-server"],
      "env": {
        "DASHCLAW_URL": "https://your-instance.vercel.app",
        "DASHCLAW_API_KEY": "oc_live_..."
      }
    }
  }
}
Streamable HTTP: Managed Agents (Python)
mcp_servers=[{
    "type": "url",
    "url": "https://your-instance.vercel.app/api/mcp",
    "headers": {"x-api-key": "oc_live_..."},
    "name": "dashclaw"
}]
Custom connector: Claude app (web / Desktop / Cowork), OAuth, no key
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.

CLI & Doctor

@dashclaw/cli handles terminal approvals and self-host diagnostics. npm run doctor runs the same engine locally with filesystem-level fix powers.

dashclaw doctor

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).

dashclaw doctor
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

Live host canary

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.

Enable (GitHub Actions)
# 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.

Claude Code Plugin

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.

Install
# 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

Codex Plugin

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.

Install
# 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

Hermes Agent Plugin

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.

Install
# 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

OpenClaw Plugin

@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.

Governance Skill

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.

Download
# 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.

Constructor

const claw = new DashClaw({ baseUrl, apiKey, agentId, agentName, authToken });
ParameterTypeRequiredDescription
baseUrl / base_urlstringYesDashboard URL
apiKey / api_keystringYesAPI Key
agentId / agent_idstringYesUnique Agent ID
agentName / agent_namestringNoHuman-readable agent label stored in audit trail for attribution. Automatically included on guard() calls if not overridden.
authToken / auth_tokenstringNoPhase 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.

Behavior Guard

claw.guard(context)

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.

ParameterTypeRequiredDescription
action_typestringYesProposed action type
risk_scorenumberNo0-100
contentstringNoOutbound text to non-fabrication check (used by a non_fabrication policy)
sourceOfTruthobjectNoFacts 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 });

Risk composition (risk_breakdown)

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.

Risk calibration proposals

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.

Tightening proposals

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.

Loosening proposals

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.

Action Recording

claw.createAction(action) / claw.create_action(**kwargs)

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
}

claw.waitForApproval(actionId, { timeout?, interval? }) / claw.wait_for_approval(action_id, timeout=300, interval=5)

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 });
}

claw.updateOutcome(id, outcome) / claw.update_outcome(id, **kwargs)

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,
});

claw.recordAssumption(asm) / claw.record_assumption(asm)

Track agent beliefs.

await claw.recordAssumption({ action_id, assumption: '...' });

Signals

claw.getSignals() / claw.get_signals()

Get current risk signals across all agents.

Returns: Promise<{ signals: Object[] }>

const { signals } = await claw.getSignals();

Policies

claw.simulatePolicy({ policy_type, rules, days })

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.

ParameterTypeRequiredDescription
policy_typestringYesThe policy type to simulate
rulesobjectYesThe proposed policy rules
daysnumberNoHow many days of historical actions to evaluate against

Returns: Promise<{ summary: { total, matches, block, warn, require_approval, allow }, matches, sample_size, window_days }>

Node.js
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');

AI Policy Generator

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."

POST /api/policies/generate

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.

ParameterTypeRequiredDescription
input_textstringYesPlain-English description of the policy you want (max 5000 chars)
dry_runbooleanNoPreview only (default true). false creates the drafts and requires an admin key
answers[{ id, value }]NoAnswers 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 }

Iterative dry-run
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' }
    ]
  })
});

Policy tuning proposals

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.

Degradation observability

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.

Security Scanning

claw.scanPromptInjection(text) / claw.scan_prompt_injection(text)

Scan untrusted input for potential prompt injection or jailbreak attempts.

ParameterTypeRequiredDescription
textstringYesUntrusted 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);
}

Agent Identity

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.

JWKS verification (Phase 2 / 2b / 2c)

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.

Guard with a verified identity
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:

  • Phase 2: verification_status: who signed the token. Configure trust with DASHCLAW_ALLOWED_ISSUER and DASHCLAW_JWT_AUDIENCE.
  • Phase 2b: 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.
  • Phase 2c: 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.

Composed identities (per-harness families)

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:

  • Governance inheritance: pairing and permission lookups for a composed id fall back to the parent's row when no exact row exists; an exact row always wins. Sub-agents are governed from day one without per-sub setup.
  • Fleet grouping: composed ids nest under their parent, so a harness's sub-agent swarm reads as one family (the fan-outs view), not fleet noise.

Public-key pairing

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.

POST /api/pairings

Create an agent pairing request. The agent submits its public key and waits for operator approval.

ParameterTypeRequiredDescription
public_keystringYesPEM-encoded RSA public key
algorithmstringNoKey algorithm. Default: RSASSA-PKCS1-v1_5
agent_namestringNoHuman-readable label for the agent

Returns: { pairing: { id, status, agent_name, created_at } }

Create pairing request
// 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_...

GET /api/pairings

List all pairing requests for the organization. Admin API key required.

Returns: { pairings: Array<{ id, status, agent_name, created_at, approved_at }> }

List pairings (admin)
const res = await fetch('/api/pairings', {
  headers: { 'x-api-key': adminApiKey }
});
const { pairings } = await res.json();

GET /api/pairings/:id

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 } }

Poll pairing status
// 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' } }

POST /api/pairings/:id/approve

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 } }

Approve pairing (admin)
const res = await fetch(`/api/pairings/${pairingId}/approve`, {
  method: 'POST',
  headers: { 'x-api-key': adminApiKey }
});

POST /api/identities

Directly register an agent's public key as a trusted identity. Admin API key required. Bypasses the pairing flow.

ParameterTypeRequiredDescription
agent_idstringYesUnique agent identifier
public_keystringYesPEM-encoded RSA public key
algorithmstringNoKey algorithm. Default: RSASSA-PKCS1-v1_5

Returns: { identity: { agent_id, algorithm, created_at } }

Register identity (admin)
// 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' }),
});

GET /api/identities

List all registered agent identities for the organization. Admin API key required.

Returns: { identities: Array<{ agent_id, algorithm, created_at }> }

List identities (admin)
// HTTP (admin key)
const res = await fetch(baseUrl + '/api/identities', { headers: { 'x-api-key': adminKey } });
const { identities } = await res.json();

DELETE /api/identities/:agentId

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 }

Revoke identity (admin)
const res = await fetch(`/api/identities/${agentId}`, {
  method: 'DELETE',
  headers: { 'x-api-key': adminApiKey }
});

Execution Studio (HTTP API)

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.

Execution Graph

GET /api/actions/:actionId/graph

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 }> }

Fetch graph
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_from

Action Outcome

Five-state terminal outcome on every action: closes the audit-trail gap between "what was approved" and "what actually completed." See durable-execution-finality.md.

POST /api/actions/:actionId/outcome

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 } }

Report success
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'
  })
});

GET /api/actions/:actionId/outcome

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 }

Retry-safe poll
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_RETRY

Coverage

Event 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).

GET /api/coverage

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%.

ParameterTypeRequiredDescription
window_hoursnumberNo1-168, default 24
include_syntheticstringNo"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 }

Read fleet coverage
const res = await fetch('/api/coverage', {
  headers: { 'x-api-key': apiKey },
});
const { coverage } = await res.json();

Fan-outs

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>.

GET /api/agents/fanouts

Recent multi-agent harness sessions, newest-first, grouped by harness_session_id. Synthetic/loadtest agents excluded from the default view.

ParameterTypeRequiredDescription
window_hoursnumberNo1-168, default 24
limitnumberNo1-100, default 20
include_syntheticstringNo"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 }

Read recent fan-outs
const res = await fetch('/api/agents/fanouts', {
  headers: { 'x-api-key': apiKey },
});
const { fanouts } = await res.json();

Hosted Provisioning (operator)

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.

POST /api/hosted/workspaces

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.

ParameterTypeRequiredDescription
turnstile_tokenstringNoCloudflare 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 }

Mint a trial workspace
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_..." }

GET /api/hosted/workspaces/:id

Admin: inspect a trial workspace. Requires an admin-role API key.

ParameterTypeRequiredDescription
idstringYesWorkspace (org) ID, e.g. org_abc

Returns: { workspace_id, status, expires_at, actions_used, trial_action_cap, created_at }

Inspect a trial workspace
curl https://hosted.example.com/api/hosted/workspaces/org_abc \
  -H "x-api-key: <admin_key>"

DELETE /api/hosted/workspaces/:id

Admin: manually delete a trial workspace and revoke its API key.

ParameterTypeRequiredDescription
idstringYesWorkspace (org) ID to delete

Returns: { deleted: true, workspace_id }

Delete a trial workspace
curl -X DELETE https://hosted.example.com/api/hosted/workspaces/org_abc \
  -H "x-api-key: <admin_key>"

POST /api/hosted/cleanup

Cron-safe sweeper for expired trial workspaces. Accepts admin-role API key OR X-Cleanup-Secret header. Safe to run repeatedly: idempotent.

ParameterTypeRequiredDescription
X-Cleanup-SecretheaderNoShared secret set via HOSTED_CLEANUP_SECRET env var. Alternative to admin API key.

Returns: { swept: number, workspace_ids: string[] }

Sweep expired trials (cron)
curl -X POST https://hosted.example.com/api/hosted/cleanup \
  -H "X-Cleanup-Secret: $HOSTED_CLEANUP_SECRET"

Error Handling

Error shape
{ message: "Validation failed", status: 400 }