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

