HomeSDK Documentation

SDK Documentation

Canonical reference for the DashClaw SDK (Node v4.7.11 / Python v4.7.11). Node.js and Python parity across all core governance features.

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 — abort on hard block
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 29 governance tools across 10 groups (core governance, optimal files, session continuity, credential hygiene, skill safety, open loops, learning + retrospection, agent inbox, behavior learning, governance posture) plus 6 read-only resources.

Tools (29)

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
Optimal files
dashclaw_optimal_files_preview
Preview optimizer output for a sessionsession_id
dashclaw_optimal_files_manifestGenerate optimal-files manifestsession_id, selections
Session continuity
dashclaw_handoff_create
Write handoff bundle for next sessionbundle, agent_id, project_id
dashclaw_handoff_latestFetch latest unconsumed handoffagent_id, project_id
dashclaw_handoff_consumeMark handoff consumed (idempotent)id, session_id
Credential hygiene
dashclaw_secret_list
List tracked secrets (metadata only)agent_id
dashclaw_secret_dueSecrets coming due for rotationwithin_days, agent_id
dashclaw_secret_mark_rotatedMark secret rotated (operator-confirmed)id
Skill safety
dashclaw_skill_scan
Static safety scan of skill filesskill_name, files
Open loops
dashclaw_loop_add
Register action-scoped commitmentaction_id, loop_type, description
dashclaw_loop_listList open/resolved loopsaction_id, status, priority
dashclaw_loop_closeResolve an open loopid, resolution
Learning + retrospection
dashclaw_assumption_record
Record an unverified assumption underpinning an actionaction_id, assumption, basis
dashclaw_learning_logLog non-obvious decision + outcomedecision, context, outcome
dashclaw_learning_queryQuery prior decisions/lessonsquery, agent_id, limit
dashclaw_decisions_recentRecent governed-action ledgeragent_id, action_type, decision, since
Agent inbox
dashclaw_inbox_list
List inbox messages + unread countagent_id, direction, unread, type, limit
dashclaw_messages_mark_readMark inbox messages as readmessage_ids, agent_id
Behavior learning
dashclaw_behavior_suggestions
Observe-only Policy Coach suggestions from recorded behavioragent_id
Governance posture
dashclaw_posture
Read the org governance posture score + 6 dimensions + findings queue (read-only)dimension
dashclaw_posture_nextThe next prioritized remediation finding from the posture queue (read-only)(none)

Resources (6)

URIDescription
dashclaw://policiesActive policy set
dashclaw://capabilitiesAvailable capabilities and health
dashclaw://agent/{agent_id}/historyRecent action history (last 50)
dashclaw://statusInstance health + operational metrics
dashclaw://code-sessions/projectsClaude Code projects with ingested session data and per-project rollups
dashclaw://code-sessions/sessions/{session_id}Full detail for one ingested Code Session (session, messages, tool uses)

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 / Desktop (claude_desktop_config.json)
{
  "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 database, configuration, auth, deployment, SDK reachability, governance staleness, and livingcode shape drift — auto-fixing safe issues. Invokes GET /api/doctor and POST /api/doctor/fix. For operators, npm run doctor 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                          # rich terminal output, auto-fix safe issues
dashclaw doctor --json                   # CI / scripts
dashclaw doctor --no-fix                 # diagnose only
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)
npm run doctor

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 and dashclaw-platform-intelligence skills 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

# Backfill existing rollouts for analytics
node cli/bin/dashclaw.js code ingest-codex --dry-run
node cli/bin/dashclaw.js code ingest-codex

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 PreToolUse / PostToolUse 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.

Platform Intelligence Skill

dashclaw-platform-intelligence gives an agent a live reference to DashClaw's API surface, governance vocabulary, integration patterns, and troubleshooting playbooks. Regenerated from the codebase via npm run livingcode:refresh so the skill never drifts from the runtime. Distributed as a zip download, mirrored into ~/.claude/skills/ by the refresh, and shipped inside the Claude Code / Codex / Hermes plugin manifests.

Download
# Zip download (regenerated on every livingcode refresh):
curl -O `https://<your-deployment>/downloads/dashclaw-platform-intelligence.zip`

# Source files:
ls public/downloads/dashclaw-platform-intelligence/
#   SKILL.md            (auto-generated from livingcode shape)
#   references/         (api-surface, platform-knowledge, troubleshooting)
#   scripts/            (bootstrap-agent-quick, diagnose, validate-integration)

# Or just run the refresh — installs to ~/.claude/skills/ automatically:
npm run livingcode:refresh

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. The guard response includes a `learning` field with historical performance context when available (recent scores, drift status, learned patterns, feedback summary). 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, agent_risk_score: number | null, non_fabrication?: object[] }>

const result = await claw.guard({ action_type: 'deploy', risk_score: 85 });

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

Agent Lifecycle

claw.heartbeat(status, metadata) / claw.heartbeat(status=..., metadata=...)

Report agent presence and health to the control plane. Call periodically to indicate the agent is alive.

ParameterTypeRequiredDescription
statusstringNoAgent status — 'online', 'busy', 'idle'. Defaults to 'online'
metadataobjectNoArbitrary metadata to include with the heartbeat
await claw.heartbeat('online', { cycle: 42, uptime_ms: 360000 });

claw.reportConnections(connections) / claw.report_connections(connections)

Report active provider connections and their status. Appears in the agent's Fleet profile.

ParameterTypeRequiredDescription
connectionsArray<Object>YesList of { name, type, status } connection objects
await claw.reportConnections([
  { name: 'OpenAI', type: 'llm', status: 'connected' },
  { name: 'Postgres', type: 'database', status: 'connected' },
]);

Loops & Assumptions

claw.registerOpenLoop(actionId, type, desc) / claw.register_open_loop(...)

Register an unresolved dependency for a decision. Open loops track work that must be completed before the decision is fully resolved.

ParameterTypeRequiredDescription
action_idstringYesAssociated action
loop_typestringYesThe category of the loop
descriptionstringYesWhat needs to be resolved
await claw.registerOpenLoop(action_id, 'validation', 'Waiting for PR review');

claw.resolveOpenLoop(loopId, status, res) / claw.resolve_open_loop(...)

Resolve a pending loop.

await claw.resolveOpenLoop(loop_id, 'completed', 'Approved');

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

Record what the agent believed to be true when making a decision.

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

Learning Analytics

claw.getLearningVelocity() / claw.get_learning_velocity()

Compute learning velocity (rate of score improvement) for agents.

Returns: Promise<{ velocity: Array<Object> }>

const { velocity } = await claw.getLearningVelocity();

claw.getLearningCurves() / claw.get_learning_curves()

Compute learning curves per action type to measure efficiency gains.

const curves = await claw.getLearningCurves();

claw.getLessons({ actionType, limit }) / claw.get_lessons(action_type=..., limit=...)

Fetch consolidated lessons from scored outcomes — what DashClaw has learned about this agent's performance patterns.

ParameterTypeRequiredDescription
actionTypestringNoFilter by action type
limitnumberNoMax lessons to return (default 10)

Returns: Promise<{ lessons: Object[], drift_warnings: Object[], agent_id: string }>

const { lessons, drift_warnings } = await claw.getLessons({ actionType: 'deploy' });
lessons.forEach(l => console.log(l.guidance));

claw.recordDecision(entry)

Record a decision/outcome into the learning ledger so the governance loop improves over time. agent_id is auto-injected from the constructor's agentId when omitted. Node SDK only.

ParameterTypeRequiredDescription
decisionstringYesThe decision that was made
contextstringNoSituation the decision was made in
reasoningstringNoWhy this decision was chosen
outcomestringNoWhat happened as a result
confidencenumberNoConfidence in the decision
agent_idstringNoOverrides the constructor agentId for attribution

Returns: Promise<{ decision: Object }>

Node.js
const { decision } = await claw.recordDecision({
  decision: 'Rolled back the auth-service deploy',
  context: 'The new deploy raised the error rate',
  reasoning: 'Faster recovery than a forward fix',
  outcome: 'Error rate returned to baseline',
  confidence: 0.9
});

claw.getLearningRecommendations(filters)

Read learned recommendations for an agent/action_type from the learning ledger. agent_id defaults to the constructor's agentId when omitted. Node SDK only.

ParameterTypeRequiredDescription
agent_idstringNoAgent to read recommendations for (defaults to constructor agentId)
action_typestringNoFilter by action type
include_metricsbooleanNoInclude supporting metrics in the response
lookback_daysnumberNoWindow of history to consider
limitnumberNoMax recommendations to return
Node.js
const recs = await claw.getLearningRecommendations({
  action_type: 'deploy',
  include_metrics: true,
  lookback_days: 30
});

Prompt Management

claw.renderPrompt() / claw.render_prompt()

Fetch rendered prompt from DashClaw.

const { rendered } = await claw.renderPrompt({
  template_id: 'marketing',
  variables: { company: 'Apple' }
});

Prompt Library — versioned template management on top of renderPrompt. Templates hold metadata; each template has one or more versions and exactly one active version, which is what renderPrompt resolves. Create/update/delete and version mutations require an admin key. Node SDK only (no Python equivalent yet).

claw.listPromptTemplates(filters)

List prompt templates. Node SDK only.

ParameterTypeRequiredDescription
categorystringNoFilter templates by category

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

Node.js
const { templates } = await claw.listPromptTemplates({ category: 'marketing' });

claw.getPromptTemplate(templateId)

Fetch a single template by id. Node SDK only.

Node.js
const template = await claw.getPromptTemplate('marketing');

claw.createPromptTemplate({ name, description, category })

Create a template (admin). Node SDK only.

ParameterTypeRequiredDescription
namestringYesTemplate name
descriptionstringNoHuman-readable description
categorystringNoGrouping category

Returns: Promise<{ id, name, description, category }>

Node.js
const tpl = await claw.createPromptTemplate({
  name: 'Cold outreach',
  description: 'First-touch sales email',
  category: 'sales'
});

claw.updatePromptTemplate(templateId, patch)

Update a template's name, description, or category (admin). Node SDK only.

Node.js
await claw.updatePromptTemplate('marketing', { category: 'growth' });

claw.deletePromptTemplate(templateId)

Delete a template along with its versions and runs (admin). Node SDK only.

Returns: Promise<{ deleted: true }>

Node.js
await claw.deletePromptTemplate('marketing');

claw.listPromptVersions(templateId)

List a template's versions, newest first. Node SDK only.

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

Node.js
const { versions } = await claw.listPromptVersions('marketing');

claw.createPromptVersion(templateId, { content, model_hint, parameters, changelog })

Create a new version for a template (admin). Node SDK only.

ParameterTypeRequiredDescription
contentstringYesThe prompt body (may contain variables)
model_hintstringNoSuggested model for this version
parametersobjectNoDefault render parameters
changelogstringNoWhat changed in this version
Node.js
await claw.createPromptVersion('marketing', {
  content: 'Write a launch post for {{company}}.',
  model_hint: 'claude-sonnet',
  changelog: 'Initial draft'
});

claw.getPromptVersion(templateId, versionId)

Fetch a single version of a template. Node SDK only.

Node.js
const version = await claw.getPromptVersion('marketing', 'v3');

claw.activatePromptVersion(templateId, versionId)

Activate a version (admin). Activating one version deactivates the others for that template, so it becomes the version renderPrompt resolves. Node SDK only.

Node.js
await claw.activatePromptVersion('marketing', 'v3');

claw.getPromptStats(filters)

Prompt usage analytics. Node SDK only.

ParameterTypeRequiredDescription
template_idstringNoScope stats to a single template
Node.js
const stats = await claw.getPromptStats({ template_id: 'marketing' });

claw.listPromptRuns(filters)

List recorded prompt runs. Node SDK only.

ParameterTypeRequiredDescription
template_idstringNoFilter by template
version_idstringNoFilter by version
limitnumberNoMax runs to return
Node.js
const runs = await claw.listPromptRuns({ template_id: 'marketing', limit: 20 });

Evaluation Framework

claw.createScorer(name, type, config) / claw.create_scorer(...)

Create a reusable scorer definition for automated evaluation.

ParameterTypeRequiredDescription
namestringYesScorer name
scorer_typestringYesType (llm_judge, regex, range)
configobjectNoScorer configuration
await claw.createScorer('toxicity', 'regex', { pattern: 'bad-word' });

claw.previewScorer({ scorer_type, config, sample })

Dry-run a scorer config against a sample without persisting anything — no eval_scores row is written. Use it to validate a quality gate before wiring the scorer into a profile. Node SDK only.

ParameterTypeRequiredDescription
scorer_typestringYesScorer type (llm_judge, regex, range)
configobjectNoScorer configuration to test
sampleobjectNoSample input to score

Returns: Promise<{ preview, scorer_type, result: { score, label, reasoning, error } }>

Node.js
const { result } = await claw.previewScorer({
  scorer_type: 'regex',
  config: { pattern: 'bad-word' },
  sample: { output: 'this contains a bad-word' }
});
console.log(result.score, result.label);

Scoring Profiles

claw.createScoringProfile(config) / claw.create_scoring_profile(...)

Define weighted quality scoring profiles across multiple scorers.

await claw.createScoringProfile({ 
  name: 'prod-quality', 
  dimensions: [{ scorer: 'toxicity', weight: 0.5 }] 
});

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

Agent Messaging

claw.sendMessage(params) / claw.send_message(**kwargs)

Send a point-to-point message or broadcast to all agents in the organization.

ParameterTypeRequiredDescription
tostringNoTarget agent ID (omit for broadcast)
bodystringYesMessage content
typestringNoaction|info|lesson|question
urgentbooleanNoMark as high priority
await claw.sendMessage({
  to: 'scout-agent-01',
  body: 'I have finished indexing the repository. You can start the analysis.',
  type: 'status'
});

claw.getInbox(options?) / claw.get_inbox(**kwargs)

Retrieve messages from the agent inbox with optional filtering.

ParameterTypeRequiredDescription
typestringNoFilter by message type
unreadbooleanNoOnly return unread messages
limitnumberNoMax messages to return

Returns: Promise<{ messages, total, unread_count }>

const { messages } = await claw.getInbox({ unread: true, limit: 10 });

claw.markRead(messageIds) / claw.mark_read(message_ids)

Mark messages as read for this agent. Direct messages are marked read only for the target agent (or dashboard); broadcasts update read_by for the reading agent.

ParameterTypeRequiredDescription
messageIdsstring[]YesMessage IDs (msg_*) to mark read

Returns: Promise<{ updated }>

const { updated } = await claw.markRead(['msg_abc123']);

claw.archiveMessages(messageIds) / claw.archive_messages(message_ids)

Archive messages for this agent so they no longer surface in the active inbox.

ParameterTypeRequiredDescription
messageIdsstring[]YesMessage IDs (msg_*) to archive

Returns: Promise<{ updated }>

const { updated } = await claw.archiveMessages(['msg_abc123']);

Session Handoffs

claw.createHandoff(handoff) / claw.create_handoff(**kwargs)

Create a session handoff document to persist state between agent sessions or transfer context to another agent.

await claw.createHandoff({
  summary: 'Completed initial data collection from Jira.',
  key_decisions: ['Prioritize high-severity bugs', 'Ignore closed tickets'],
  open_tasks: ['Run security scan on src/', 'Draft fix for #123'],
  next_priorities: ['Security audit']
});

claw.getLatestHandoff() / claw.get_latest_handoff()

Retrieve the most recent handoff for the current agent.

Returns: Promise<Object|null>

const handoff = await claw.getLatestHandoff();

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 best_effort) blocks a replayed jti.
  • Phase 2c — act_status: whether the token is bound to this call. DASHCLAW_ACT_BINDING (default off) compares the request against the token's urn:dashclaw:act-binding claim.

Legacy (v1): public-key pairing

Predates JWKS verification and is retained for older integrations. Enroll agents via public-key pairing and manage approved identities. Pairing requests are created by agents; approval is an admin action. 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 is on the deprecated dashclaw/legacy subpath
import { DashClaw } from 'dashclaw/legacy';
const claw = new DashClaw({ baseUrl, apiKey, agentId });

const { pairing } = await claw.createPairing(publicKeyPem, 'RSASSA-PKCS1-v1_5', '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 (v1 legacy)
const status = await claw.getPairing(pairingId);
console.log(status.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)
// Node SDK (v1 legacy)
await claw.registerIdentity('agent-007', publicKeyPem, '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)
// Node SDK (v1 legacy)
const { identities } = await claw.getIdentities();

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: workflow templates, model strategies, knowledge collections, a capability registry, and a read-only execution graph on actions. Every surface here has a canonical SDK wrapper method in the v2 Node SDK (see sdk/dashclaw.js, 80 methods total). The HTTP examples below are shown first because they're language-agnostic; the equivalent SDK calls (claw.listWorkflowTemplates, claw.execution.capabilities.invoke, etc.) are in sdk/README.md → Execution Studio. 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

Workflow Templates

Package a repeatable operational pattern as a reusable, versioned asset linking policies, prompts, knowledge, capabilities, and a model strategy.

GET /api/workflows/templates

List all workflow templates for the current org. Supports ?status=draft|active|archived, ?limit, ?offset.

List templates
const { templates } = await fetch(`${baseUrl}/api/workflows/templates`, {
  headers: { 'x-api-key': apiKey }
}).then(r => r.json());

POST /api/workflows/templates

Create a workflow template. Slug auto-generated from name if omitted. Starts as v1, status=draft. Body fields: name (required), description, objective, steps, linked_prompt_template_ids, linked_policy_ids, linked_knowledge_collection_ids, linked_capability_ids, linked_capability_tags, model_strategy_id, status.

Create template
await fetch(`${baseUrl}/api/workflows/templates`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Release Hotfix',
    description: 'Ship urgent production patches safely',
    objective: 'Deploy with full policy + approval coverage',
    linked_policy_ids: ['pol_prod_deploy'],
    linked_capability_tags: ['deploy'],
    model_strategy_id: 'mst_balanced_default'
  })
});

GET | PATCH /api/workflows/templates/:templateId

Fetch or partially update a template. PATCH bumps version by 1 when the steps array changes; all linked arrays and metadata can be updated in the same call.

Update steps (bumps version)
await fetch(`${baseUrl}/api/workflows/templates/${templateId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    steps: [{ id: 'plan' }, { id: 'test' }, { id: 'deploy' }]
  })
});

POST /api/workflows/templates/:templateId/duplicate

Clone a template as a new draft (version resets to 1, status='draft'). Accepts optional name and slug overrides in the body.

Duplicate
const { template } = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/duplicate`,
  { method: 'POST', headers: { 'x-api-key': apiKey } }
).then(r => r.json());

POST /api/workflows/templates/:templateId/launch

Launch a template. Creates a new row in action_records with trigger='workflow:<templateId>' and reasoning='WORKFLOW_LAUNCH_META=<json>' carrying the full template context. If the template links a model_strategy_id, the resolved config is fetched and snapshotted onto the launched action and the template. No schema columns were added to action_records — Phase 1 piggybacks on existing trace primitives.

Returns: { launch: { action_id, template_id, template_version, launched_at, resolved_strategy } }

Launch and link to replay
const { launch } = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/launch`,
  {
    method: 'POST',
    headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
    body: JSON.stringify({ agent_id: 'deploy-bot' })
  }
).then(r => r.json());

// The launched action is immediately traceable in decision replay
console.log(`${baseUrl}/decisions/${launch.action_id}`);

GET /api/workflows/templates/:templateId/runs

List past workflow executions for a template. Each run is a parent action_record with step counts from workflow_step_results. Supports status, agent_id, limit, and offset query params.

Returns: { template_id, runs: [{ run_action_id, template_id, status, agent_id, declared_goal, duration_ms, started_at, finished_at, step_count, steps_completed, steps_failed }], total }

List recent runs
const runs = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/runs?limit=10`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

runs.runs.forEach(r =>
  console.log(`${r.status} — ${r.steps_completed}/${r.step_count} steps — ${r.duration_ms}ms`)
);

GET /api/workflows/templates/:templateId/runs/:runActionId

Fetch full run detail including all step results with complete input/output JSON. Powers the run detail page. Each step includes the resolved input after variable interpolation and the full output (no truncation).

Returns: { run_action_id, template_id, template_name, status, agent_id, declared_goal, duration_ms, started_at, finished_at, error_message, steps: [{ step_id, step_index, step_type, step_name, status, input, output, error_message, retry_count, duration_ms }] }

Inspect a failed run
const run = await fetch(
  `${baseUrl}/api/workflows/templates/${templateId}/runs/${runActionId}`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

const failed = run.steps.filter(s => s.status === 'failed');
failed.forEach(s =>
  console.log(`Step ${s.step_name} failed: ${s.error_message}`)
);

Model Strategies

Reusable provider/model strategy records (primary + fallback chain, cost/latency sensitivity, budget cap). Linked from workflow templates and snapshotted at launch.

GET | POST /api/model-strategies

List all strategies or create a new one. Config is validated server-side: primary.provider and primary.model are required; costSensitivity must be one of low | balanced | high-quality; latencySensitivity must be low | medium | high; maxBudgetUsd must be a number; maxRetries must be an integer; fallback, allowedProviders, and disallowedProviders must be arrays if provided.

Create strategy
await fetch(`${baseUrl}/api/model-strategies`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Balanced Default',
    description: 'GPT-4.1 primary, Claude Sonnet 4 fallback',
    config: {
      primary: { provider: 'openai', model: 'gpt-4.1' },
      fallback: [{ provider: 'anthropic', model: 'claude-sonnet-4' }],
      costSensitivity: 'balanced',
      latencySensitivity: 'medium',
      maxBudgetUsd: 0.5,
      maxRetries: 2,
      allowedProviders: ['openai', 'anthropic']
    }
  })
});

GET | PATCH | DELETE /api/model-strategies/:strategyId

Fetch, update, or delete a strategy. PATCH merges config patches over the existing config (primary fields preserved unless overridden). DELETE nulls out the soft reference on any linked workflow_templates rather than orphaning them.

Patch budget only
await fetch(`${baseUrl}/api/model-strategies/${strategyId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ config: { maxBudgetUsd: 1.0 } })
});

POST /api/model-strategies/:strategyId/complete

Execute a chat completion using this strategy. Resolves BYOK provider credentials from org settings, walks the fallback chain (primary provider first, then each fallback), enforces maxBudgetUsd, and returns a normalized response. Supports task_mode to override primary with the corresponding taskModes entry. Providers supported: openai, anthropic, groq, together, perplexity. Returns 502 with provider_errors array when all providers fail.

ParameterTypeRequiredDescription
messagesArray<{ role, content }>YesChat messages (system, user, assistant)
max_tokensnumberNoMax output tokens (default 1024)
temperaturenumberNoSampling temperature (default 0.7)
task_modestringNoOverride primary with taskModes[mode] if defined in strategy config

Returns: { content, provider, model, usage: { input_tokens, output_tokens }, cost_usd, fallback_used, attempts, strategy_id, strategy_name }

Execute completion with fallback
const result = await claw.completeWithStrategy(strategyId, [
  { role: 'user', content: 'Summarize the deploy plan' }
], { max_tokens: 512, task_mode: 'reasoning' });

console.log(result.content);       // LLM response
console.log(result.provider);      // which provider handled it
console.log(result.cost_usd);      // estimated cost
console.log(result.fallback_used); // true if primary failed

Knowledge Collections

Lightweight metadata layer for knowledge sources that workflows and agents can bind to. No embedding or retrieval in Phase 1 — metadata + tags only.

GET | POST /api/knowledge/collections

List collections (filter by ?source_type) or create a new one. source_type must be one of files | urls | external | notes. New collections start with ingestion_status='empty' and doc_count=0.

Create collection
await fetch(`${baseUrl}/api/knowledge/collections`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Runbook Library',
    description: 'Incident response runbooks',
    source_type: 'files',
    tags: ['ops', 'oncall']
  })
});

GET | PATCH /api/knowledge/collections/:collectionId

Fetch or update a collection's metadata (name, description, source_type, tags, ingestion_status).

Fetch
const { collection } = await fetch(
  `${baseUrl}/api/knowledge/collections/${collectionId}`,
  { headers: { 'x-api-key': apiKey } }
).then(r => r.json());

GET | POST /api/knowledge/collections/:collectionId/items

List or add items in a collection. Adding an item increments the parent collection's doc_count atomically and transitions ingestion_status from 'empty' to 'pending' on the first item. Items carry source_uri (required), title, mime_type, status, and a metadata object.

Add an item
await fetch(
  `${baseUrl}/api/knowledge/collections/${collectionId}/items`,
  {
    method: 'POST',
    headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      source_uri: 'https://docs.example.com/runbook.md',
      title: 'Deploy runbook',
      mime_type: 'text/markdown'
    })
  }
);

POST /api/knowledge/collections/:collectionId/sync

Caller-invoked ingestion: fetches source_uri content for each pending item, chunks text (~500 tokens with overlap), generates embeddings via BYOK OpenAI key (text-embedding-3-small, 1536 dims), and stores in the knowledge_chunks table (pgvector). Updates item status (pending → indexed/failed) and collection ingestion_status. Bounded to 50 items per call — designed for Vercel free tier (no cron required).

Returns: { sync: { ingested, failed, chunks_created, errors } }

Sync a collection
// SDK
const { sync } = await claw.syncKnowledgeCollection(collectionId);
console.log(sync.ingested, sync.chunks_created);

POST /api/knowledge/collections/:collectionId/search

Semantic search over chunked + embedded content. Embeds the query via BYOK OpenAI key, then uses pgvector cosine distance to find the most relevant chunks. Returns top-k results with similarity scores, chunk content, and source item metadata.

ParameterTypeRequiredDescription
querystringYesNatural language search query
limitnumberNoMax results (default 5, max 20)

Returns: { query, collection_id, results: Array<{ chunk_id, item_id, content, score, position, token_count, title, source_uri }>, count }

Search a collection
const { results } = await claw.searchKnowledgeCollection(
  collectionId,
  'How do I roll back a deploy?',
  { limit: 5 }
);
results.forEach(r => console.log(`${(r.score * 100).toFixed(1)}%: ${r.content.slice(0, 80)}`));

claw.deleteKnowledgeCollection(collectionId)

Delete a collection (and its items/chunks). Node SDK only.

Returns: Promise<{ deleted, collection_id }>

Node.js
const { deleted, collection_id } = await claw.deleteKnowledgeCollection(collectionId);

Capability Registry

Governed registry of callable capabilities with risk, approval, health, and (future) pricing metadata. Workflow templates can reference capabilities by id or by tag.

GET | POST /api/capabilities

Search or register a capability. GET supports combinable filters: ?category, ?risk_level (low|medium|high|critical), ?search (ILIKE on name/description/tags). source_type must be one of internal_sdk | http_api | webhook | human_approval | external_marketplace. (org_id, slug) is unique — POST returns 409 on duplicate slug.

Register a capability
await fetch(`${baseUrl}/api/capabilities`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Send Slack Message',
    description: 'Posts to a configured Slack channel',
    category: 'messaging',
    source_type: 'http_api',
    auth_type: 'oauth',
    risk_level: 'medium',
    requires_approval: false,
    tags: ['notify', 'slack'],
    health_status: 'healthy',
    docs_url: 'https://docs.example.com/slack'
  })
});

GET | PATCH | DELETE /api/capabilities/:capabilityId

Fetch, update, or delete a capability. PATCH validates risk_level and source_type enums on change. DELETE removes the capability (SDK: claw.deleteCapability(capabilityId)).

Mark degraded
await fetch(`${baseUrl}/api/capabilities/${capabilityId}`, {
  method: 'PATCH',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ health_status: 'degraded' })
});

Capability Runtime

Governed capability invocation with retry policies, circuit breaker, and health tracking. Capabilities with retry_policy retry transient failures automatically. Capabilities with circuit_breaker auto-block after consecutive failures (reset via test route).

POST /api/capabilities/:capabilityId/invoke

Execute a governed capability invocation. Evaluates guard policies, scans for sensitive data, enforces quota, runs the HTTP call with optional retry, and records a full action audit trail. Returns retry_metadata when retry_policy is configured. Returns 503 circuit_breaker_open when the circuit breaker is tripped.

Invoke with payload
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/invoke`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'What is x402?' })
});
const data = await res.json();
// data.success, data.action_id, data.result, data.elapsed_ms, data.governed
// data.retry_metadata (when retry_policy configured): { total_attempts, retried, attempts }

POST /api/capabilities/:capabilityId/test

Run a non-production validation call. Bypasses guard policies and circuit breaker. Updates capability health_status and certification_status based on the result. Use this to certify a capability or reset an open circuit breaker.

Test a capability
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/test`, {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'test input' })
});
const data = await res.json();
// data.tested, data.health_status, data.certification_status

GET /api/capabilities/:capabilityId/health

Fetch derived health summary including success rates (1d/7d), p95 latency, certification status, recent errors, and stale check. Computed from action_records over the past 7 days.

Check capability health
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/health`, {
  headers: { 'x-api-key': apiKey }
});
const health = await res.json();
// health.status (healthy|degraded|failing|untested)
// health.certification_status (certified|stale|failed|uncertified)
// health.success_rate_1d, health.success_rate_7d, health.p95_latency_ms

GET /api/capabilities/:capabilityId/history

Fetch invocation and test event history for a capability. Filter by action_type (capability_invoke, capability_test) and status (completed, failed, running, pending_approval). Supports limit and offset pagination.

Fetch recent failures
const res = await fetch(`${baseUrl}/api/capabilities/${capabilityId}/history?status=failed&limit=10`, {
  headers: { 'x-api-key': apiKey }
});
const history = await res.json();
// history.events[].action_id, action_type, status, error_message, duration_ms

Analytics

GET /api/analytics

Fetch aggregated governance analytics for the organization over a rolling window. Includes action counts, guard decision totals, signal summaries, and assumption stats. Supports ?days (1–365, default 30).

ParameterTypeRequiredDescription
daysnumberNoRolling window in days (1–365). Defaults to 30.

Returns: { actions_total, actions_by_status, guard_decisions_total, guard_decisions_by_outcome, signals_total, assumptions_total }

Fetch 7-day analytics
const res = await fetch(`${baseUrl}/api/analytics?days=7`, {
  headers: { 'x-api-key': apiKey }
});
const data = await res.json();
// data.actions_total, data.guard_decisions_total, data.signals_total

Guard Decisions

GET /api/guard/decisions

List guard evaluation records for the organization. Returns paginated decisions with matched policies and declared goal context. Supports filtering by ?decision (allow|block|flag), ?agent_id, ?limit (max 200), and ?offset.

ParameterTypeRequiredDescription
decisionstringNoFilter by outcome: allow | block | flag
agent_idstringNoFilter to a specific agent
limitnumberNoPage size (max 200, default 50)
offsetnumberNoPagination offset (default 0)

Returns: { decisions: Array<{ id, agent_id, action_type, decision, matched_policies, declared_goal, agent_name, created_at }>, total, stats }

List blocked decisions
const res = await fetch(`${baseUrl}/api/guard/decisions?decision=block&limit=25`, {
  headers: { 'x-api-key': apiKey }
});
const { decisions, total, stats } = await res.json();
// decisions[].decision, decisions[].matched_policies, decisions[].declared_goal

Agent Profile

GET /api/agents/:agentId/profile

Fetch the full governance profile for a specific agent. Includes identity, presence (heartbeat state), trust posture, computed risk signals, and assumptions summary. Returns 404 if the agent has not been seen by the instance.

ParameterTypeRequiredDescription
agentIdstringYesThe agent identifier (path parameter)

Returns: { agent: { agent_id, agent_name, action_count, last_active, presence: { status, last_heartbeat_at, current_task_id } }, trust, signals, assumptions_summary }

Fetch agent profile
const res = await fetch(`${baseUrl}/api/agents/my-agent/profile`, {
  headers: { 'x-api-key': apiKey }
});
const { agent, trust, signals, assumptions_summary } = await res.json();
// agent.presence.status, trust.risk_score, signals, assumptions_summary

Agent Reputation

Per-agent trust vectors computed from your own governed decisions (actions, guard outcomes, evaluations, feedback). Time-decayed (90-day half-life) and Bayesian-smoothed; risk_score wraps the existing 0-100 risk numbers. Each vector can be returned with an Ed25519-signed receipt that re-verifies against the instance JWKS. All reads are org-scoped.

GET /api/reputation/agents/:agentId

Current reputation vector (stored snapshot, or computed read-only when none exists yet). Returns 404 for an unknown agent.

Fetch the vector
const { vector } = await claw.getAgentReputation('agent_42');
// vector: { reliability_score, completion_rate, policy_violation_rate, approval_adherence,
//           quality_score, risk_score, volume_weight, confidence, total_events, last_event_at, computed_at }

POST /api/reputation/agents/:agentId/recompute

Recompute the vector from evidence, persist the snapshot, and store a signed receipt.

Recompute
await claw.recomputeAgentReputation('agent_42');

GET /api/reputation/agents/:agentId/events

Paginated reputation events for an agent (org-scoped).

List events
await claw.listAgentReputationEvents('agent_42', { limit: 50, offset: 0 });

GET /api/reputation/agents/:agentId/receipt

Signed receipt for the current vector (stored, or built read-only).

Receipt
const { receipt } = await claw.getAgentReputationReceipt('agent_42');

POST /api/reputation/verify

Verify a reputation receipt against the instance's published signing keys. The vector hash is checked constant-time and the Ed25519 signature is verified. Returns { ok, kid?, reason? }.

Verify
const { ok } = await claw.verifyReputationReceipt(receipt);

Agent Registry

Register external, org-owned providers that group existing capabilities and are invoked through governance. An invocation routes through the existing capability runtime (auth, timeout, retry, request/response mapping, SSRF defense), the guard, and the action ledger; the registry never reimplements HTTP. Risk derives from risk_class + budget + capability metadata via the existing risk map and predictive risk. x402 and auth metadata are recorded; no payment settlement is performed.

POST /api/agents/registry

Register an external provider. GET /api/agents/registry lists them; GET/PATCH /api/agents/registry/:id read and update one.

Register + group a capability
const { registered_agent } = await claw.registerAgent({ name: 'Pricing API', endpoint: 'https://pricing.example.com', auth_type: 'bearer', risk_class: 'high', default_budget_usd: 5 });
await claw.addAgentCapability(registered_agent.entry_id, 'cap_123');

POST /api/agents/invoke

Invoke a capability through a registered agent, governed end to end by the existing capability runtime + guard + action ledger. Returns 403 when guard blocks, 202 when approval is required, and records a thin invocation referencing the resulting action_id.

Governed invocation
const out = await claw.invokeRegisteredAgent({
  registered_agent_id: registered_agent.entry_id,
  capability_id: 'cap_123',
  agent_id: 'agent-1',
  payload: { q: 'sku-9' }
});
// out.success, out.action_id, out.risk_score, out.result

x402 Spend Governance

Register x402 providers, govern individual purchases through the guard loop, and record spend for audit. The agent executes the actual x402 call itself — DashClaw registers providers, governs purchase intent, and keeps a tamper-evident ledger. DashClaw never holds a wallet.

GET /api/x402/providers

List registered x402 providers (org-scoped). Filter by status.

List providers
const { providers } = await claw.listProviders({ status: 'active' });

POST /api/x402/providers

Register a paid x402 provider. Supply name, category, and optional base_url, description, pricing_model, or metadata.

Register a provider
const { provider } = await claw.createProvider({
  name: 'Exa Search',
  category: 'research',
  base_url: 'https://api.exa.ai',
});

GET /api/x402/providers/:id

Provider detail including registered endpoints.

Fetch provider
const { provider } = await claw.getProvider(provider.provider_id);

PATCH /api/x402/providers/:id

Update a provider (name, status, pricing_model, metadata, etc.).

Update status
await claw.updateProvider(provider.provider_id, { status: 'disabled' });

GET /api/x402/providers/:id/endpoints

List the endpoints registered under a provider.

List endpoints
const { endpoints } = await claw.listProviderEndpoints(provider.provider_id);

POST /api/x402/providers/:id/endpoints

Add an endpoint to a provider. Supply name, endpoint_url, and optional default_price, sensitivity_level, or metadata.

Add an endpoint
await claw.createProviderEndpoint(provider.provider_id, {
  name: 'Search',
  endpoint_url: 'https://api.exa.ai/search',
  default_price: 0.01,
  sensitivity_level: 'low',
});

POST /api/x402/purchases

Govern + record a paid acquisition. Routes through the guard loop. Required: agent_id, provider, declared_goal, purchase_reason, context_gap, expected_value. Returns { action, purchase, decision }; branch on action.status (running | pending_approval).

ParameterTypeRequiredDescription
agent_idstringYesIdentifier of the agent making the purchase
providerstringYesProvider id from createProvider
declared_goalstringYesThe agent's goal that requires this purchase
purchase_reasonstringYesWhy this purchase is necessary
context_gapstringYesWhat information the agent lacks locally
expected_valuestringYesWhat value the agent expects to get

Returns: { action: { id, status }, purchase: { id }, decision: { decision, risk_score } }

Govern a purchase
const { action, purchase, decision } = await claw.recordPurchase({
  agent_id: 'research-agent',
  provider: provider.provider_id,
  declared_goal: 'Find recent papers on quantum computing',
  purchase_reason: 'Context gap: no local data for period 2025-01-01..2026-01-01',
  context_gap: 'No papers in knowledge base for the requested window',
  expected_value: 'Retrieve 10+ relevant citations',
});

if (action.status === 'pending_approval') {
  await claw.waitForApproval(action.id);
}
// Agent now executes the x402 call, then records the result

GET /api/x402/purchases

List governed purchases (org-scoped). Filter by provider_id.

List purchases
const { purchases } = await claw.listPurchases({ provider_id: provider.provider_id });

POST /api/artifacts (Node-only convenience wrapper)

Attach the x402 result snapshot to its purchase action. Reuses the existing artifacts endpoint; links by source_action_id so the snapshot appears in that action's evidence bundle. Python callers post directly to POST /api/artifacts with artifact_type='x402_purchase_result'.

ParameterTypeRequiredDescription
actionIdstringYesThe act_ id returned by recordPurchase
resultobjectYes{ summary?, data?, url? } — snapshot of what the x402 call returned
Record the result
await claw.recordPurchaseResult(action.id, {
  summary: 'Found 14 papers on quantum computing',
  data: { count: 14, citations: ['...'] },
  url: 'https://api.research.example.com/results/...',
});

POST /api/x402/purchases → /api/actions/:id/outcome → /api/artifacts

Convenience: record a SETTLED x402 payment end-to-end in one call — govern + record the purchase, mark it succeeded, and (when given) attach the on-chain receipt. Use this for the pay-outside-a-hook self-report pattern: your agent pays through a native shell / wrapper that OpenClaw's hooks never see, so it must report the spend itself. The server resolves/auto-registers the provider from `provider`, so you don't register one first. Python parity: record_x402_purchase().

ParameterTypeRequiredDescription
agent_idstringYesIdentifier of the agent that paid
providerstringYesProvider name/origin, e.g. "stableenrich.dev" — the server resolves it to a provider_id
spendnumberYesSettled USD amount (> 0)
transaction_hashstringNoOn-chain tx hash, attached as receipt evidence
request_idstringNoProvider request id, attached as receipt evidence

Returns: { action, purchase, decision, outcome }

Self-report a settled payment
const settled = await claw.recordX402Purchase({
  agent_id: 'research-agent',
  provider: 'stableenrich.dev',   // name/origin
  spend: 0.007,                   // settled USD
  transaction_hash: '0xabc…',
  request_id: 'req_123',
});

FinOps Spend

A read-only operator aggregation over already-stored cost. It reconciles spend across surfaces — agent LLM cost, governed x402 purchases, and Claude Code sessions — without doing any new pricing. There is no SDK wrapper; this is a dashboard endpoint that powers the /spend, /spend/x402, and /spend/code UI surfaces.

GET /api/finops/spend

Aggregate spend for the org under one lens. Sums cost already recorded by the governance runtime; it owns no tables and introduces no new pricing.

ParameterTypeRequiredDescription
lensstringNofleet | claude-code. Default fleet. fleet covers agent LLM cost + x402; claude-code covers Code Sessions cost.
periodstringNo7d | 30d | 90d. Default 30d.

Returns: fleet: { lens, period, agent, x402, fleet_total_usd }; claude-code: { lens, period, code_sessions, code_total_usd }

Aggregate fleet spend
// Operator dashboard endpoint — no SDK wrapper
const res = await fetch('/api/finops/spend?lens=fleet&period=30d', {
  headers: { 'x-api-key': process.env.DASHCLAW_API_KEY },
});
const { lens, period, agent, x402, fleet_total_usd } = await res.json();

// Your Claude Code spend (advisory)
const code = await fetch('/api/finops/spend?lens=claude-code&period=7d', {
  headers: { 'x-api-key': process.env.DASHCLAW_API_KEY },
}).then((r) => r.json());
// code.code_sessions, code.code_total_usd

Governance Posture

A gaming-resistant, read-only governance posture score for the org: it measures what the fleet actually GOVERNS versus what it COULD, across six dimensions, and drives a human-gated remediation loop. The score only rises from ACTIVE, proven-to-fire policies — drafting a fix never raises it. Operator surface only; no SDK wrapper. Powers the /posture page. All routes experimental.

GET /api/posture

Compute the current posture score with its six dimension breakdowns, the prioritized findings queue, a summary, and the recent snapshot trend.

Returns: { score, status, cappedBy, dimensions, findings, summary, snapshots, snapshotTs }

Read the org posture
const res = await fetch('/api/posture', {
  headers: { 'x-api-key': process.env.DASHCLAW_API_KEY },
});
const { score, status, dimensions, findings, summary, snapshots } = await res.json();

GET /api/posture/findings

The prioritized remediation queue, plus the risk-accepted ledger and per-status counts. Filter by status or dimension.

ParameterTypeRequiredDescription
statusstringNoopen | drafted | resolved | snoozed | accepted_risk
dimensionstringNoFilter to one of the six posture dimensions

Returns: { findings, riskAccepted, counts }

List the open queue
const res = await fetch('/api/posture/findings?status=open', {
  headers: { 'x-api-key': apiKey },
});
const { findings, riskAccepted, counts } = await res.json();

POST /api/posture/findings/:key/resolve

Human-gated resolution. action='create_draft' inserts an INACTIVE policy draft (never auto-activates, never raises the score); 'snooze' defers it; 'accept_risk' records an explicit acceptance in the ledger. Draft-only by design — a human still activates any policy at /policies.

ParameterTypeRequiredDescription
actionstringYescreate_draft | snooze | accept_risk
notestringNoOperator note (e.g. a risk-acceptance justification)

Returns: { resolved, action, status, policy?, state, finding? }

Draft a remediation (a human activates it later)
const res = await fetch('/api/posture/findings/' + key + '/resolve', {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'content-type': 'application/json' },
  body: JSON.stringify({ action: 'create_draft' }),
});

POST /api/posture/scan

Recompute the posture score and persist a trend snapshot for history.

Returns: { score, status, dimensions, snapshot, summary }

Recompute and record a snapshot
const res = await fetch('/api/posture/scan', {
  method: 'POST',
  headers: { 'x-api-key': apiKey },
});
const { score, snapshot } = await res.json();

Also exposed read-only over MCP — tools dashclaw_posture + dashclaw_posture_next — and as CLI commands dashclaw posture / dashclaw next / dashclaw posture resolve <key> (draft-only). The dashboard view is at /posture.

Code Sessions

Beta

Ingest Claude Code (and Codex) transcripts, price the spend with cache-aware accounting, surface optimizer signals (stuck loops, cache crater, context gaps), and distill a session into an Optimal Files bundle — a root CLAUDE.md, path-scoped rules, hooks, and skill packs you apply locally. The canonical parser runs server-side, so clients never parse transcripts; all routes are experimental.

Ingest transcripts

Three ways in, all landing on the same server-side parser:

  • Stop-hook (live). Set DASHCLAW_CODE_SESSIONS_ENABLED=1 for the Claude Code hooks. After each turn the reporter POSTs the JSONL delta with source_host: 'hook' — fail-silent if the instance is unreachable. Run node scripts/install-hooks.mjs --global to capture every project on your machine (capture-only; no API key in global config).
  • CLI backfill. dashclaw code ingest [--dry-run] walks ~/.claude/projects; dashclaw code ingest-codex walks ~/.codex/sessions. Large transcripts are gzip-compressed on the wire automatically, so real-world sessions stay under the 4.5 MB request limit; files over 40 MB are skipped.
  • Direct API. POST /api/code-sessions/ingest-jsonl (below), or POST /api/code-sessions/ingest-live for per-turn incremental append with finalize: true to close the session.

POST /api/code-sessions/ingest-jsonl

Ingest a Claude Code JSONL transcript (or a delta). The server dedups duplicate usage fragments (Claude Code repeats one model request across many rows), computes cache-aware cost, and runs optimizer + alert detection. Accepts raw lines, a raw-gzip body (x-dashclaw-encoding: gzip header — the primary path for large transcripts), or a legacy base64 compressed_jsonl field (50 MB decompressed cap, 200k lines).

ParameterTypeRequiredDescription
projectobjectYes{ slug, source_host: "hook" | "jsonl" }
jsonl_linesstring[]NoRaw JSONL lines. Either this or compressed_jsonl is required.
compressed_jsonlstringNobase64(gzip(jsonl)) alternative for large transcripts.
session_uuidstringNoValidated against the parser-derived uuid; mismatch is rejected.
tool_use_action_mapobjectNoMaps tool_use ids to governed action_ids (the governance bridge).

Returns: { project: { id, slug }, session: { session_uuid, inserted_messages, inserted_tool_uses, signals_inserted, alerts_inserted }, parser: { jsonl_records, model_requests, duplicate_fragments_skipped } }

Ingest a transcript via the API
// Backfill is easiest via the CLI: dashclaw code ingest
// Direct API — you supply the raw JSONL lines:
const res = await fetch(baseUrl + '/api/code-sessions/ingest-jsonl', {
  method: 'POST',
  headers: { 'x-api-key': apiKey, 'content-type': 'application/json' },
  body: JSON.stringify({
    project: { slug: 'my-repo', source_host: 'jsonl' },
    jsonl_lines: lines,        // string[] — or compressed_jsonl (base64 gzip)
  }),
});
const { session, parser } = await res.json();
// parser.duplicate_fragments_skipped = the cache-aware dedup that
// prevents the Nx cost over-count from repeated usage fragments

Optimal Files

Distill a session into a curated config bundle (root CLAUDE.md, path-scoped rules, hooks, skill candidates), persist the selected files as a manifest, then apply it to disk with the CLI. The server cannot read your filesystem, so generation is preview-only until you apply; every file is secret-redacted before it leaves the server.

POST /api/code-sessions/sessions/:sessionId/optimal-files/preview

Generate the candidate file bundle for a session (read-only). Each file carries a kind, a commit recommendation, a secret-scan result, and an overwrite_risk of 'unknown' (the server can't see your working tree).

Returns: { session_id, bundle: [{ path, kind, title, content, commit_recommendation, secret_scan, overwrite_risk }], groups, analysis }

Preview the bundle
const res = await fetch(
  baseUrl + '/api/code-sessions/sessions/' + sessionId + '/optimal-files/preview',
  { method: 'POST', headers: { 'x-api-key': apiKey } }
);
const { bundle } = await res.json();   // every file already secret-redacted

POST /api/code-sessions/sessions/:sessionId/optimal-files/manifest

Persist a chosen subset of the bundle as an apply-able manifest (24 h TTL, strict path allowlist: CLAUDE.md, .claude/rules/, .claude/hooks/, .claude/skills/). Returns a ready-to-run apply command.

ParameterTypeRequiredDescription
selectionsArray<{ path }>YesPaths chosen from the preview bundle. Paths outside the allowlist are rejected.

Returns: { manifest_id, expires_at, apply_command }

Persist a manifest, then apply it locally
const res = await fetch(
  baseUrl + '/api/code-sessions/sessions/' + sessionId + '/optimal-files/manifest',
  {
    method: 'POST',
    headers: { 'x-api-key': apiKey, 'content-type': 'application/json' },
    body: JSON.stringify({
      selections: [{ path: 'CLAUDE.md' }, { path: '.claude/rules/testing.md' }],
    }),
  }
);
const { manifest_id, apply_command } = await res.json();
// apply_command, e.g.:  dashclaw code apply <manifest_id> --dest=. --yes

Cost, signals & retrospection

Read surfaces over ingested sessions (all GET unless noted, x-api-key required):

  • /api/code-sessions/projects — projects with per-project cost rollups.
  • /api/code-sessions/projects/:projectId/sessions — sessions for a project.
  • /api/code-sessions/sessions/:sessionId — token in/out + cache breakdown, cache-hit %, and cost reconciliation (flags a ≥2× divergence from Mission Control pricing).
  • /api/code-sessions/sessions/:sessionId/autopsy — outcome classification (completed / thrashed / fell_back_to_rules / timed_out / aborted) and where the spend went by tool category.
  • /api/code-sessions/subagent-roi — keep / trim / drop per subagent by success-rate and cost-per-success.
  • /api/code-sessions/memos + POST /memos/regenerate — weekly spend memo (7-day vs prior-7-day).
  • /api/code-sessions/alerts + POST /alerts/read-all — cost-anomaly / cache-crater / stuck-loop alerts.
  • /api/learning/code-signals — optimizer findings aggregated into the learning loop.

Also exposed over MCP — tools dashclaw_optimal_files_preview + dashclaw_optimal_files_manifest, and resources dashclaw://code-sessions/projects + dashclaw://code-sessions/sessions/{session_id}. The dashboard view is at /code-sessions.

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 }