Claw-in-a-Box logoClaw-in-a-Box
Documentation
Base URL  https://api.clawinabox.xyz Health  GET /healthz live Console  Open operator workbench ↗ Version  0.9.0 live Auth  open for unclaimed agents; X-Agent-Secret for claimed identity mutations and strict guard checks Format  JSON in, JSON out

Overview

You are an agent. Increasingly, agents hire other agents, call paid APIs, and move money. The common shortcut — hand a sub-agent your full credentials and hope — fails in three familiar ways: scope creep (the helper ends up with more power than the task needs), zombie access (nobody remembers what was handed out, so nothing gets cleanly withdrawn), and confused deputies (a token leaks and whoever holds it is believed).

Claw-in-a-Box makes the safe pattern the easy pattern, across three capabilities:

1 · Delegatable capability tokens

Mint a root capability, then delegate attenuated children offline (macaroon-style HMAC chaining). A child can never hold a scope its parent lacks, never outlive its parent, and is bound to a single audience. Revoke any token and every descendant dies with it — by construction, not bookkeeping.

2 · Spend-policy verdicts

Before acting, ask POST /v1/guard/check and get back allow review deny with the exact rules that fired. Four policy primitives: per-transaction and daily spend limits, destination allowlists, human-approval thresholds, and time windows.

3 · Claimed identity and execution binding

Pay once through x402 to anchor an agent_id to the settlement wallet and receive an agent_secret exactly once. Claimed agents can protect identity mutations, enable strict guard authentication, recover access with an EIP-191 wallet signature, and bind an allowed decision to one short-lived, one-shot execution.

Quickstart

Sixty seconds, copy-paste. Everything is plain HTTP and JSON. Unclaimed agents can use the free routes without authentication; claimed identity mutations and strict guard checks require X-Agent-Secret.

# 0) set the base URL and confirm the service is up
BASE=https://api.clawinabox.xyz
curl -s $BASE/healthz

# 1) check a spend BEFORE doing it
curl -s -X POST $BASE/v1/guard/check \
  -H "Content-Type: application/json" \
  -d '{"agent_id":"my-agent","action":"transfer","amount":150,"destination":"0xabc"}'
# -> {"verdict":"review","triggered_rules":["require_approval"],...}

# 2) mint a root capability, then delegate a NARROWER child
ROOT=$(curl -s -X POST $BASE/v1/tokens \
  -H "Content-Type: application/json" \
  -d '{"subject":"my-agent","scopes":["read","write","pay"]}' | jq -r .token)

curl -s -X POST $BASE/v1/tokens/delegate \
  -H "Content-Type: application/json" \
  -d "{\"parent_token\":\"$ROOT\",\"audience\":\"worker-1\",\"scopes\":[\"read\"],\"ttl_seconds\":600}"

# 3) revoke the root — every child dies with it, transitively
curl -s -X POST $BASE/v1/tokens/revoke \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$ROOT\"}"
Try the verdicts. On the home page demo, amount 30allow, 150review, 999deny. Or split one big spend into several small ones and watch the daily total catch it.

How it works

The design thesis is that bounded authorization — a grant that can only shrink as it moves, and dies with its ancestors — is one abstraction that should be enforced wherever an agent acts.

Attenuation is enforced, not requested

Tokens are HMAC-chained: each child's signature is keyed by its parent's. That makes three properties hold on every verify, on the server, with no way for a client to forge or broaden them without the secret:

PropertyWhat it means
Subset scopesA child's scopes must be a subset of its parent's. Requesting more returns 403 scope_escalation.
Clamped lifetimeA child's expiry is clamped to its parent's — a child never outlives its parent.
Bound audienceEach token is minted for a single audience and should be presented only by that party.

Cascading revocation

Revoke any token and every token delegated under it fails verification from that moment, transitively, with revoked_ancestor. Revoke the root to kill the entire tree. This is cryptographic, not a lookup table you have to keep in sync.

One idea, three enforcement surfaces

The same four policy primitives compile to different surfaces; what changes is who enforces them.

SurfaceInstanceGuarantee grade
HTTP servicethis token + guard APIgateway-enforced
Agent-protocol simNANDA Town auth: delegatable plugingateway-enforced, adversarially validated
On-chain smart accountssession-key constraint compiler (roadmap)protocol-enforced

Guarantees & limits

Read this before pointing anything important at the hosted demo.

Gateway-grade, not protocol-grade. Guarantees hold as long as callers route decisions through this service. The service can refuse to bless an action, but it cannot physically stop an agent that ignores a deny. For enforcement that makes the action impossible, the same policy semantics compile to on-chain session-key constraints (on the roadmap).

Concretely:

WhatStatus
Token forgery / broadeningPrevented by construction — impossible without the server secret.
Attenuation & cascading revocationEnforced on every verify, server-side.
Hosted statePERSISTENCE=on: revocations, daily spend, Telegram bindings, pending approvals, claimed identities, verdict rows and audit events persist in MySQL.
Restart guaranteePending verdicts hydrate with their remaining expiry. Runtime consumption is single-instance authoritative; this is not multi-replica coordination.
Deployment modesoff keeps legacy in-memory behavior; shadow dual-writes while memory remains authoritative; on hydrates durable state at boot.
Production fundsDo not use the hosted demo for real money at this stage.

API reference — Spend guard

POST/v1/guard/checkverdict before you act

Send the action you intend to take; get back a verdict and the rules that fired.

Request fields

FieldTypeReqNotes
agent_idstringnoidentity for daily accumulation (default anonymous)
actionstringnofree-form label, e.g. transfer, api_call
amountnumberyes for spendsin whatever unit your policy uses
destinationstringnochecked against allowlist rules if present
policystring / objectnopreset name or inline policy object (default standard)
waitbooleannofor a review, hold the request until a human decides or the timeout expires
bindbooleannoreturn a short-lived, one-shot verdict_id when the decision is allowed
Claimed agents. Identity mutations always require X-Agent-Secret. A claimed agent with strict mode enabled also requires that header on free and paid guard checks. Unclaimed agents retain the legacy open flow.

Response

{
  "verdict": "allow | review | deny",
  "triggered_rules": ["spend_limit.per_tx"],
  "reasons": ["amount 999 exceeds per-tx limit 200"],
  "policy_used": "standard",
  "spent_today_after": 150,
  "evaluated_at": "2026-07-09T12:00:00.000Z"
}

Interpreting the verdict

VerdictWhat your agent should do
allowProceed. The amount is recorded against the agent's daily total.
reviewDo not proceed autonomously. Surface the action to your human operator and only continue on explicit confirmation. Treat it as a hard stop, not a soft warning.
denyDo not proceed and do not retry with the same parameters. reasons tells you which limit to respect.

deny always wins over review.

Human approval response

{
  "verdict": "review",
  "approval_id": "a1b2c3d4e5f60708",
  "approval_status": "pending",
  "poll": "/v1/approvals/a1b2c3d4e5f60708"
}

Poll the returned path, or send "wait":true. Approval is final for that request; a human-approved request is charged when it resolves, and later automated decisions see the updated daily ledger.

Execution binding

A policy decision should authorize one concrete execution, not become a reusable permission slip. Add "bind":true to a guard request. An immediate allow, or a later human approval, includes:

{
  "verdict": "allow",
  "verdict_id": "<one-shot id>",
  "expires_in_seconds": 300
}
POST/v1/verdicts/{id}/consumeconsume once immediately before execution
curl -s -X POST "$BASE/v1/verdicts/$VERDICT_ID/consume" \
  -H "Content-Type: application/json" \
  -d '{}'
CaseBehavior
First valid consumeHTTP 200; the execution authorization is spent.
Second consumeHTTP 409 already_consumed with the first consumption time.
Unknown or expired idHTTP 404.
Expires before useThe same-day spend charge is refunded automatically.
Restart behavior. Pending verdicts survive a single-instance restart with only their remaining expiry. This is a restart guarantee, not a distributed lock across multiple replicas.

Policies

GET/v1/policiesreturns all presets in full

Presets

PresetPer-txDailyReview above
conservative5020020
standard (default)2001000100
permissive10005000500

Inline policy schema — four primitives

{
  "name": "my-policy",
  "rules": [
    {"type": "spend_limit", "per_tx": 200, "daily": 1000},
    {"type": "allowlist", "field": "destination", "values": ["0xgood"]},
    {"type": "require_approval", "when_amount_over": 100},
    {"type": "time_window", "allow_utc_hours": [[9, 18]]}
  ]
}
RuleSemantics
spend_limitDenies when a single amount exceeds per_tx, or when the agent's accumulated daily total would exceed daily.
allowlistDenies any destination not listed. Omit or set "mode":"off" to disable.
require_approvalDowngrades the verdict to review above the threshold.
time_windowDenies outside the given UTC hour ranges.

Pay-to-Claim agent identity

Unclaimed ids keep the legacy open behavior. To protect an identity, claim it through either x402 rail. There is no free claim path.

POST/paid/v1/agents/claimUSDC on Base
POST/paid-okx/v1/agents/claimUSDT0 on X Layer
{"agent_id": "my-agent"}

After the normal 402 handshake and successful settlement, the first claim returns HTTP 201. The settlement wallet becomes the durable owner.

{
  "agent_id": "my-agent",
  "agent_secret": "<shown exactly once>",
  "claimed_at": "2026-07-19T00:00:00.000Z",
  "claimed_by": "0x...payer wallet"
}
Store the secret immediately. Only its SHA-256 hash is retained. Never put a secret in a URL or JSON body; send it only in the X-Agent-Secret header. Repeating a claim returns 409 already_claimed and is not settled.

Rotate the secret

POST/v1/agents/rotate
curl -s -X POST "$BASE/v1/agents/rotate" \
  -H "Content-Type: application/json" \
  -H "X-Agent-Secret: $AGENT_SECRET" \
  -d '{"agent_id":"my-agent"}'

Rotation atomically invalidates the previous secret and returns its replacement once.

Enable or disable strict guard authentication

POST/v1/agents/strict
curl -s -X POST "$BASE/v1/agents/strict" \
  -H "Content-Type: application/json" \
  -H "X-Agent-Secret: $AGENT_SECRET" \
  -d '{"agent_id":"my-agent","strict":true}'

Strict agents must send X-Agent-Secret on guard checks across free and paid rails. Claim, rotation and strict-mode changes fail closed with 503 feature_disabled unless durable persistence is connected and hydrated.

Wallet-signature secret recovery

POST/v1/agents/recovertwo-phase EIP-191 recovery

1 · Issue a challenge

{"agent_id": "my-agent"}

The response contains a five-minute nonce, canonical message and expires_at.

2 · Sign and submit

Sign the exact message with the claiming wallet using EIP-191 personal_sign, then send:

{
  "agent_id": "my-agent",
  "nonce": "...",
  "signature": "0x..."
}

Success returns a new one-time agent_secret and immediately invalidates the old one. Nonces are domain-bound, stored only as hashes, consumed once in the same transaction, and swept after expiry.

Current limitation. v0.9 recovery supports EOA/EIP-191 wallets. EIP-1271 contract wallets, custodians, and signers that cannot produce the claiming wallet's EIP-191 signature require manual operator recovery.

Issue a root capability

POST/v1/tokens
{"subject": "my-agent", "scopes": ["read", "write", "pay"], "ttl_seconds": 3600}

Returns {"token": "<base64url>"}. ttl_seconds defaults to 3600.

Delegate an attenuated child

POST/v1/tokens/delegateoffline — no issuer round-trip
{"parent_token": "<token>", "audience": "worker-1", "scopes": ["read"], "ttl_seconds": 600}

Returns {"token": "<child token>"}. The service enforces:

RuleBehavior
Subset scopesChild scopes must be a subset of the parent's, else 403 scope_escalation.
Clamped expiryChild expiry is clamped to the parent's — a child never outlives its parent.
Dead parents can't delegateA revoked or expired parent cannot delegate.

Any token holder can delegate. Chains can be arbitrarily deep; every hop attenuates.

Verify a presented token

POST/v1/tokens/verify
{"token": "<token>", "presenter": "worker-1"}

presenter is optional but recommended: when present, the service also checks the token is being presented by its bound audience. Success:

{
  "valid": true,
  "context": {
    "subject": "worker-1", "scopes": ["read"],
    "expires_at": 1760000000,
    "chain_tids": ["a1b2...", "c3d4..."], "depth": 2
  }
}

Failures return HTTP 400/403 with {"valid": false, "verdict": "deny", "error": "<code>", "detail": "<human readable>"}.

Revoke with cascade

POST/v1/tokens/revoke
{"token": "<token>"}

Returns {"revoked_tid": "...", "cascades": true}. Every token delegated under the revoked one fails verification from this moment, transitively, with revoked_ancestor. Revoke the root to kill the entire tree.

v0.9 operational reads

These views expose only the minimum data required by each role. They require a connected, hydrated PERSISTENCE=on deployment and return 503 feature_disabled instead of falling back to memory.

Public aggregate metrics

GET/v1/metricspublic · 15-second cache

A fixed aggregate-only schema: claimed and strict agent counts, approval and verdict counts, active spend agents, and ledger-change totals. It never includes agent ids, wallets, destinations, chats, individual amounts, event payloads or revenue.

Agent-owner spend

GET/v1/agents/{id}/spendrequires that agent's secret
curl -s "$BASE/v1/agents/my-agent/spend" \
  -H "X-Agent-Secret: $AGENT_SECRET"

Returns the current daily total and the latest 50 v0.9-forward ledger changes. History is observational and PII-minimized: delta, balance-after, reason, reference id and time only. It is never used to authorize spend.

Operator approval feed

GET/v1/approvals?status=pending&limit=25host operator only
curl -s "$BASE/v1/approvals?status=pending&limit=25" \
  -H "Authorization: Bearer $OPERATOR_BEARER_KEY"

status is optional: pending, approved, denied or expired. limit is 1–100 and defaults to 25. The operator key grants this read-only god-view; it grants no mutation and does not bypass agent-secret checks.

Error codes

Failures use stable error codes. Token-specific and v0.9 identity/execution errors include:

CodeMeaning
invalid_tokenMalformed or unparseable token.
invalid_signatureHMAC does not match — forged or tampered.
scope_escalationA child requested a scope its parent lacks.
expired_ancestorThe token or one of its ancestors has expired.
revoked_ancestorThe token or an ancestor was revoked.
audience_mismatchpresenter is not the token's bound audience.
missing_fieldA required request field was absent.
invalid_fieldA field has the wrong type or value.
forbiddenThe claimed agent secret is missing or invalid.
unauthorizedThe operator bearer credential is missing or invalid.
not_foundThe agent, verdict or other requested resource does not exist or has expired.
already_consumedA one-shot verdict id has already authorized an execution.
feature_disabledThe route requires connected, hydrated durable storage or deployment configuration that is unavailable.

Route review approvals to your own Telegram

By default, when your agent trips a review rule, the approval request goes to the service operator. You can instead bind your own Telegram so every review for your agent lands on your phone with Approve / Deny buttons.

Buyer checklist. Prefer a focused, shareable version of this flow? Open the standalone Telegram binding guide.
Official Telegram bot: @ClawInABoxBot. Check the exact username before sending a bind code to avoid impersonators and scammers.

Open a chat with the bot

Open the official Claw-in-a-Box bot @ClawInABoxBot, verify the exact username to avoid scammers, and press Start. This can't be skipped — Telegram won't let a bot message someone who has never opened the chat.

Request a one-time bind code

curl -X POST https://api.clawinabox.xyz/v1/operators/register \
  -H "Content-Type: application/json" \
  -H "X-Agent-Secret: $AGENT_SECRET" \
  -d '{"agent_id":"YOUR_AGENT_ID"}'

The secret header is required when the id is claimed. Omit it only for an unclaimed id using the legacy open flow.

Response includes a bind_code valid for 15 minutes:

{
  "agent_id": "YOUR_AGENT_ID",
  "bind_code": "A1B2C3D4",
  "expires_in_seconds": 900,
  "instructions": "Open Telegram, message the bot, and send: /bind A1B2C3D4"
}

Send the code to the bot

In the bot chat, send /bind A1B2C3D4. The bot replies:

✅ Bound. Review requests for agent YOUR_AGENT_ID will now come to this chat.

Verify, then test

Confirm the routing switched to you:

curl -s https://api.clawinabox.xyz/v1/operators/YOUR_AGENT_ID
# -> {"agent_id":"YOUR_AGENT_ID","routing":"caller"}

"routing":"caller" means reviews now go to your Telegram; "routing":"operator" means it's still falling back to the operator. Fire a test request that trips review:

curl -X POST https://api.clawinabox.xyz/v1/guard/check \
  -H "Content-Type: application/json" \
  -H "X-Agent-Secret: $AGENT_SECRET" \
  -d '{"agent_id":"YOUR_AGENT_ID","amount":150}'

An approval message with Approve / Deny buttons appears on your phone. Tapping a button takes effect immediately.

Notes. Bind codes last 15 minutes — request a new one if it expires. There's no separate unbind endpoint: run register + /bind again and the newest binding overwrites the previous one. Each agent_id routes to a single chat at a time; agents that aren't bound continue to route to the service operator.
Choosing an agent_id. An unclaimed id is self-declared and retains the legacy behavior: anyone who knows it shares its daily budget and can request a bind code, so use an unguessable value and do not reuse it across environments. For a durable identity, claim the id through x402; after claim, operator registration requires its X-Agent-Secret.

Self-hosting

Node.js >= 18. Run npm install. MySQL is required for claimed identity, recovery, durable operational reads and the full PERSISTENCE=on behavior.

cd service
npm install

GUARD_SECRET=$(openssl rand -hex 32) \
PERSISTENCE=on \
DB_HOST=127.0.0.1 DB_USER=claw DB_PASSWORD=<password> DB_NAME=claw \
PORT=8787 node server.js

node test-all.js
Persistence modeBehavior
offLegacy in-memory behavior; durable identity features are disabled.
shadowMemory remains authoritative while supported mutations are dual-written asynchronously.
onRequires a connected, hydrated database; durable state is loaded at boot and identity features are enabled.
Fail closed. Claim, secret rotation, strict mode, recovery, metrics, owner spend and the operator feed return 503 feature_disabled when durable storage is unavailable.

Full source is on GitHub under Apache-2.0. The agent-facing API doc is also served live at /skill.md.