Veto/docs
Meow Gateway

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:

AxisCheck
toolExact string match (e.g. meow.pay signed → cannot consume as meow.card.create)
counterparty_hashRecomputed from runtime beneficiary; must equal signed
railMust be in rail_allowlist; runtime pick is recorded in the receipt
amountRuntime ≤ amount_ceiling; currency exact match
invoice_hashRuntime ≥ signed (bind-once, consume-once)
expires_atnow < expires_at with a ±30s skew tolerance
nonceUnique 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

  1. 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.
  2. 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.
  3. 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