Claw-in-a-Box logoClaw-in-a-Box
Documentation
Base URL  https://api.clawinabox.xyz Health  GET /healthz live Auth  none (demo) 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 two 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.

Quickstart

Sixty seconds, copy-paste. Everything is a plain JSON POST — no SDK, no auth for the demo deployment.

# 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 \
  -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 \
  -d '{"subject":"my-agent","scopes":["read","write","pay"]}' | jq -r .token)

curl -s -X POST $BASE/v1/tokens/delegate \
  -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 -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.
Demo state (revocations, daily totals)In-memory — resets on restart; daily totals reset at UTC midnight.
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)

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.

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.

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.

Error codes

Verify and delegate failures use a stable error code:

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.

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.

Open a chat with the bot

In Telegram, search for the Claw-in-a-Box bot and press Start. This can't be skipped — Telegram won't let a bot message someone who has never messaged it first.

Request a one-time bind code

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

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" \
  -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. It's any string you like — it names the per-agent daily ledger and the approval route, and needs no registration before first use. Because it's self-declared, treat it like a capability, not a label: pick something unguessable (e.g. acme-payments-7f3a9c, not test), one id per agent, and don't reuse ids across environments — whoever knows the id shares its daily budget and can request a bind code for it.

Self-hosting

Zero dependencies, Node.js >= 18. No npm install. State (revocations, daily totals) is in-memory by design at this stage.

cd service
GUARD_SECRET=$(openssl rand -hex 32) PORT=8787 node server.js
node test.js   # 12-case smoke suite, exits 0 on success

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