Repository navigation
HTTP API Reference
Taken from the route code in server.py and dashboard/server.py at 9900817. Both use the stdlib http.server (ThreadingHTTPServer). JSON in, JSON out. No caller authentication.
python server.py [--port 8741] [--sandbox]. Binds 127.0.0.1. Default port 8741. Mock rail unless --sandbox is given.
Path ids match [\w-]+. Errors look like {"error": "<reason>"}. A missing or unparseable JSON body is treated as {}. Unmatched routes return 404 not_found.
| Method | Path | Purpose | Body | Success | Errors |
|---|---|---|---|---|---|
| POST | /api/permits |
Issue a permit (GRANTED) |
agent_id, cap_cents, allowlist, expiry_hours (default 1) |
201 {permit_id, receipt}
|
400 missing fields |
| GET | /api/permits/<permit_id> |
Permit state | — | 200: cap, allowlist, expiry, revoked, reserved/captured/remaining cents, in_flight count, parent_id
|
404 unknown_permit
|
| POST | /api/permits/<permit_id>/check |
Read-only gate check. No reservation, no receipt |
amount_cents, merchant_id
|
200 {allowed, reason}
|
400 non-positive or non-int amount |
| POST | /api/permits/<permit_id>/spend |
Spend attempt through the pipeline |
amount_cents, merchant_id, predicate (default delivery_hash), artifact_hash
|
200 {allowed, reason, escrow_id, receipts}. Sandbox: 202 {status: approval_required, operation_id, order_id, approval_url, reservation: held, next}
|
400 bad fields or amount |
| POST | /api/operations/<operation_id>/resume |
Resume a sandbox spend after payer approval (same order) | {} |
200 like spend, or 202 if still unapproved | 404 unknown_operation
|
| POST | /api/permits/<permit_id>/delegate |
Carve a sub-permit |
agent_id, cap_cents, allowlist, expiry_hours (default 1) |
201 {permit_id, parent_id, receipt}
|
404 unknown_parent; 400 block reason |
| POST | /api/permits/<permit_id>/tighten |
Narrow a live permit (cascades) | any of cap_cents, approval_threshold_cents, remove_merchants, expiry (ISO-8601), actor
|
200 {tightened, permit_id, changes, receipt}
|
400 widen, no-op or invalid; 404 |
| POST | /api/permits/<permit_id>/estop |
Revoke this permit and void its in-flight holds | {} |
200 {revoked, voided_escrows, receipt}
|
404 unknown_permit
|
| POST | /api/permits/<permit_id>/revoke-cascade |
Revoke the permit and its subtree, void holds, release carves | {} |
200 {revoked, cascaded_to, voided_escrows, receipt}
|
404 unknown_permit
|
| POST | /api/escrows/<escrow_id>/release |
Submit evidence → predicate → capture |
delivered_bytes_b64 or acceptance_signature
|
200 {released, reason, capture_id}
|
400 invalid base64 |
| POST | /api/escrows/<escrow_id>/reconcile |
Resolve an UNKNOWN escrow | {} |
200 {resolved, outcome, capture_id, receipt}
|
— |
| POST | /api/escrows/<escrow_id>/retry-cleanup |
Retry a stuck void | {} |
200 {cleanup_cleared}
|
— |
| GET | /api/ledger |
Full receipt chain + verification | — | 200 {chain: {ok, reason}, receipts: [...]}
|
— |
| GET | /debug/paypal |
Mock call counts (authorizations, captures, voids) | — | 200 | 404 in sandbox mode |
That is 14 routes: 3 GET and 11 POST.
-
Refusals return 200. A BLOCKED spend, a REFUSED release or a pending approval comes back as HTTP 200 with
allowed/released: falseand areason. -
Pending approval: the spend response has no top-level
approval_id. It is in theAPPROVAL_PENDINGreceipt payload insidereceipts. -
cap_centscoercion:/api/permitsand/delegaterunint(body["cap_cents"]). In a local probe at9900817,50.7was granted as50,"5000"as5000, andtrueas1, and a delegate with10.9carved10.0.5(which becomes0) raised inside the handler and the connection closed with no HTTP response./check,/spendand/tightenreject non-int values with 400. - No approval endpoints on this service, and the grant takes no threshold. See Principal Approvals.
-
/estopdoes not cascade, despite the module docstring. See E-Stop and Revocation. - Pending sandbox operations are held in memory (
PENDING_OPS).
Started in-process by the LLM demos (default port 8471; demo_approval.py uses 8474). It can also run standalone with empty state: python3 dashboard/server.py.
| Method | Path | Purpose |
|---|---|---|
| GET | / |
dashboard/index.html |
| GET | /api/state |
Permits (including effective_cap_cents), escrows, receipts, chain_ok, pending approvals (the page polls every second) |
| POST | /api/estop |
{permit_id} → flow.estop() (single permit, not a cascade) |
| POST | /api/revoke-cascade |
{permit_id} → flow.revoke_cascade() (whole subtree) |
| POST | /api/tighten |
{permit_id, cap_cents}: narrow the cap only. A non-int or non-positive cap_cents gives {ok: false}
|
| POST | /api/approvals/<approval_id>/approve |
Principal approves (actor dashboard) |
| POST | /api/approvals/<approval_id>/deny |
Principal denies |
python server.py &
curl -s -X POST localhost:8741/api/permits -d '{"agent_id":"a1","cap_cents":5000,"allowlist":["m1"],"expiry_hours":1}'
curl -s -X POST localhost:8741/api/permits/<permit_id>/check -d '{"amount_cents":3000,"merchant_id":"m1"}'
curl -s localhost:8741/api/ledgerPermit · payment authority for AI agents · MIT · Wiki facts verified against commit 9900817 on 2026-10-07 (America/Edmonton). Where this wiki and the code disagree, the code wins.
Concepts
- Overview and Problem
- The Four-Clause Gate
- Spend Pipeline
- Delegation and Sub-Permits
- Principal Approvals
- Tighten-Only Narrowing
- E-Stop and Revocation
- Claim Ledger
- UNKNOWN and Reconcile
Using it
Design and status
Reference