Spend Capsule
A signed, single-use, TTL-bounded authorization that binds an agent's intent to an exact beneficiary, rail, amount, and policy.
A Spend Capsule is a JWS (Ed25519) wrapping a canonical JSON payload. It's what an agent presents to Meow via the gateway — without a valid capsule the call is denied before a single upstream byte flies.
Shape
{
"version": "veto.capsule/1",
"capsule_id": "cap_01hy2z...",
"issuer": "meow-gateway:self-host",
"entity_id": "ent_acme_llc",
"agent_id": "agent_finance_bot",
"tool": "meow.pay",
"rail_allowlist": ["ach", "wire"],
"counterparty_hash": "sha256:7ee7f2426cda71548a0fae87c291ff42469358bcb65ff2a0ffaf763d15bae5f4",
"amount_ceiling": { "currency": "USD", "amount": "5000.00" },
"invoice_hash": "sha256:9f2...",
"workflow_id": "wf_01hy2z...",
"policy_sha256": "ac3f...",
"issued_at": "2026-04-21T14:03:00Z",
"expires_at": "2026-04-21T14:18:00Z",
"nonce": "8b2d4e...",
"max_uses": 1
}Drift axes
Every field in the payload is a drift anchor. At consume time the gateway verifies the runtime request matches the signed payload byte-for-byte on all axes:
| Axis | Check |
|---|---|
tool | Exact string match (e.g. meow.pay signed → cannot consume as meow.card.create) |
counterparty_hash | Recomputed from runtime beneficiary; must equal signed |
rail | Must be in rail_allowlist; runtime pick is recorded in the receipt |
amount | Runtime ≤ amount_ceiling; currency exact match |
invoice_hash | Runtime ≥ signed (bind-once, consume-once) |
expires_at | now < expires_at with a ±30s skew tolerance |
nonce | Unique per (organization, entity); replay = deny |
Miss any axis → deny + chain a deny receipt with the specific mismatch code.
Why Ed25519 + JCS
Ed25519 — 32-byte pubkey, 64-byte sig, deterministic (no nonce reuse foot-guns), widely-implemented in every target language. The @veto/spend-capsule-protocol package exposes matching signCapsule / verifyCapsule helpers in TS and Python so cross-language auditors read the same JWS.
JCS (RFC 8785) — canonical JSON serialization. Two verifiers on two runtimes will serialize the same payload to the same bytes, so the hash is stable. JCS handles key sorting, number normalization, and UTF-8 encoding.
Lifetime
- Mint (POST /v1/capsules) — gateway validates inputs against the active policy pack, reserves a budget lease, claims any required HITL approval, signs the payload, writes a receipt.
- Consume (POST /meow/consume or /meow/mcp) — gateway verifies signature + all drift axes, atomically marks the capsule
consumed, chains a decision receipt, forwards to Meow. - Settle (webhook from Meow) — gateway transitions the workflow to
settled/failed.
A capsule is single-use. Multi-use was considered and removed — the consume path cannot atomically decrement a counter while also enforcing nonce replay. Re-minting is cheap; re-using is forbidden.
Idempotency
Pass an Idempotency-Key header on mint. Retries with the same key AND the same canonical request body return the exact original JWS. A retry with the same key but a different body returns 400 idempotency_key_reused_with_different_payload — prevents a "retry" from silently authorizing a different payment.
Where to go next
- Decision Receipt — the audit output of consume
- Policy Pack — YAML rules evaluated at mint
- Protocol reference — full JSON schema + golden vectors