← Back to Zado

Zado Agent Trust Protocol v0.2

The consumer-side policy layer for AI agents that spend real money.

v0.2 — supersedes v0.1 (2026-04-25). Date: 2026-05-03. Corresponds to zado-mcp >= 0.2.0, < 0.3.0.

Field Value
Version 0.2
Status Public draft
Date 2026-05-03
Supersedes v0.1 (2026-04-25) — preserved unchanged for citation continuity
Reference implementation zado-mcp >= 0.2.0, < 0.3.0 (PyPI)
Author Evolving Intelligence AI, LLC
License (spec) CC-BY 4.0
License (reference implementation) See project LICENSE
Canonical URL https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.2

Provenance

This specification describes behavior that is shipped and operating in the Zado backend and the packaged zado-mcp PyPI distribution as of the publication date. It is not aspirational. Every normative claim traces to a backing source file; the publicly-verifiable subset is enumerated in Appendix A — Source-of-truth traceability (PyPI-distributed surfaces only, since the backend repository is currently private).

The protocol was first exercised end-to-end on 2026-04-03, when an OpenClaw agent registered a Zado spend-scoped token, called check_budget, called authorize_purchase(43.20, "groceries", "Whole Foods") and received an authorized: true response with a transaction id and remaining envelope balance. The agent had no access to the user's bank credentials, account numbers, or transactions outside its bound category. That run is the existence proof for v0.1 — the v0.1 trust surface is preserved verbatim in v0.2.

The Human Approval Gate (HAG) — the deferral lifecycle that turns the v0.1 "policy gate" into a true two-phase delegated spend protocol — was first exercised end-to-end in HAG Phase 1.7 (2026-05-03) when an agent's authorize_purchase request above threshold parked into pending_authorizations, the user approved via the desktop UI, and the agent explicitly claimed the approval via complete_pending_authorization. The authorize-then-claim pattern is normative in v0.2 (see §3.11).

Items labeled "v0.3+ roadmap" in §9 are explicitly NOT shipped today. Integrators MUST NOT depend on roadmap items. (v0.2 promotes from "v0.2+ roadmap" → normative the surfaces marked in the "Changes from v0.1" section below; everything else from v0.1's roadmap remains future work.)


Changes from v0.1

This section is informative. It enumerates the normative changes between v0.1 (2026-04-25) and v0.2 (2026-05-03). v0.2 is fully backwards-compatible with v0.1 — a v0.1-targeted integrator continues to work unchanged. The promotions add capability without removing or renaming any documented surface.

  1. §3.11 Human Approval Gate promoted from "v0.2+ roadmap" to normative. The full deferral lifecycle is documented: pending → approved → completed plus terminal denied and expired paths. The authorize-then-claim pattern is named (mapping to payments-industry "auth/capture") and the two-tool agent claim flow (check_pending_authorization + complete_pending_authorization) is normative.
  2. §4 MCP tool inventory expanded from 4 to 6 tools. The two new tools are check_pending_authorization and complete_pending_authorization. They are the load-bearing additions that turn v0.1's synchronous policy gate into a true two-phase protocol. list_envelopes (already shipped in v0.1 in practice but documented thinly) is now documented at the same depth as the other tools.
  3. §7.1 install instructions canonicalized. pip install zado-mcp is the primary install path; the canonical MCP config block uses command: "zado-mcp", args: ["serve"]. The legacy python -m app.mcp.server form is demoted to a developer / local-checkout alternative.
  4. completion_metadata documented as a normative observability surface. The JSON shape {transaction_ledger_entry_id, envelope_id_at_debit, debited_amount, completed_at, envelope_remaining_at_debit} links the human's "yes" to the actual ledger debit. Third parties reconciling Zado-mediated agent spend can anchor on this field.
  5. Per-tool auth boundaries made explicit. Each MCP tool entry in §4 states who can call it: agent token (MCP), human session (REST), or both. Cross-boundary calls (e.g., a human session calling an agent-only MCP tool) return 403.
  6. §6 Security elevated to a three-part normative foundation: threat model, blast radius, and reversibility / audit. v0.1 covered defense in depth in passing; v0.2 answers the gating adoption question — "what's the worst that can happen if my Zado-issued agent token is compromised?" — directly.
  7. §8 AP2 mapping reframed: Zado IS an AP2 v0.2 Trusted Surface. v0.1 said "Zado is in the same space as AP2"; v0.2 names Zado's role formally in the AP2 v0.2 vocabulary (Trusted Agent Provider model). The mapping table uses AP2 v0.2 terminology (Open Mandate, Closed Mandate, Mandate Receipt) and explicitly marks Checkout / Payment Mandate as v0.4+ scope.
  8. Appendix A — Source-of-truth traceability added. Citations use file-plus-symbol form (line numbers go stale; symbols are stable). Scoped to publicly-distributed code (PyPI zado-mcp package) since the backend repository is currently private.

1. Introduction

Zado is a policy and permission layer for AI agents that need to make purchases on behalf of a human. It sits above a person's real bank accounts (connected via Plaid) and below the agent-payment rails being built by the card networks.

When an AI agent wants to spend money, three questions must be answered:

  1. Who said it could? — identity and scope (Zado: agent_token.scope)
  2. How much can it spend, on what? — policy and budget (Zado: envelopes, caps, binding, pacing)
  3. How does the money actually move? — settlement (Zado: today, a ledger entry; in v0.2+, optionally a Stripe Issuing one-time virtual card)

Most existing agent-payment work focuses on question 3. Zado focuses on questions 1 and 2 — the policy gate that any settlement rail can consult before money moves.

The spec is intentionally compatible with, and complementary to:

  • Google AP2 v0.2 (Agent Payments Protocol) — Zado fulfills AP2 v0.2's Trusted Surface role in the Trusted Agent Provider model. The agent token corresponds to an AP2 Open Mandate; the pending authorization to a Closed Mandate; completion_metadata to the local-evidence equivalent of a Mandate Receipt. (Full mapping in §8.1.)
  • Stripe Machine Payments Protocol (MPP) — Zado is the consumer-side policy layer an MPP-scoped agent can consult before invoking MPP-routed payment.
  • Visa Intelligent Commerce / Mastercard Agent Pay — Zado is the consumer-side budget gate that complements network-side spend controls and tokenization.
  • Model Context Protocol (MCP) — Zado exposes its policy surface via six MCP tools (the four v0.1 tools plus the two HAG tools added in v0.2).

What Zado is not:

  • Zado is not a money transmitter. It does not move funds.
  • Zado is not a card issuer in v0.2. (v0.3+ may issue one-time virtual cards via Stripe Issuing — Stripe remains the regulated entity.)
  • Zado is not a replacement for AP2, MPP, Visa IC, or MC Agent Pay. It is a complement to them.

2. Positioning

Zado occupies a layer that, before this spec, had no public name.

Player Layer Primary Consumer Spend Authority Settlement
Zado Agent Trust Protocol (this spec) Consumer policy / permission gate (AP2 v0.2 Trusted Surface role) End user (with ADHD-friendly UX) Envelope balance + agent token scope + caps + binding + pacing + HAG threshold None today (ledger entry + completion_metadata); Stripe Issuing virtual card in v0.3+
Google AP2 v0.2 Open agent-payment protocol (rails-agnostic) Merchants + agents + payment processors Open Mandate + Closed Mandate + Mandate Receipt (cryptographic VDCs) Pluggable (cards, wallets, instant rails)
Stripe MPP Agent-payments protocol on Stripe rails Stripe-integrated merchants Stripe-issued credentials, scope-bound Stripe rails
Visa Intelligent Commerce Connect Network-side AI payment platform Visa-accepting merchants Tokenization + spend controls per agent Visa network
Mastercard Agent Pay Network-side agentic payment program All Mastercard cardholders Mastercard Agentic Tokens, consumer-set scopes Mastercard network

The pattern: card networks and protocol-level players answer how money moves once authorized. Zado answers what an agent is allowed to spend, on behalf of which envelope, against which budget, in the first place. A Visa-Intelligent-Commerce-routed payment authorized through MPP can ask a Zado-equivalent policy layer "is this purchase permitted by the user's budget?" before it ever touches the network. Today that consultation is done inside each card-network's own platform; Zado is the first open, MCP-native, real-bank-account-backed implementation of that consumer-side gate.

The single-sentence positioning: Zado is the consumer-side budget gate for AI agents — the layer that says "yes" or "no" to a spend before any settlement rail moves money.

3. Core concepts

This section is normative. Each term is defined once. Every term cites the source file that implements it.

3.1 Envelope

A named pool of money allocated to a spending category for a calendar month. "Groceries: $400 budgeted, $123.50 spent, $276.50 remaining." The envelope balance is the hard ceiling for any agent or human spend in that category. Source: backend/app/models/envelope.py and backend/app/services/envelope.py.

3.2 Envelope-as-Permission

The fundamental design pattern of this protocol: the envelope balance IS the spending limit for any agent bound to that envelope. No separate "agent budget" exists. There is only the user's envelope, which the agent may draw against subject to scope and guards. When an agent asks "can I spend $43?", the answer is computed from the same envelope balance the user sees in the app. There are no parallel ledgers and no agent-only allowances. Source: backend/app/services/agent_spending.py (the authorize_purchase flow checks the live envelope via SpendingService.check_can_afford).

3.3 Agent Token

A SHA-256-hashed bearer credential issued to a single AI agent on behalf of a single user. Token plaintext is shown once at creation and never stored. Tokens carry: id, user_id (FK), name, scope, is_active, spending guards, optional allowed_category_ids, optional pace_multiplier, optional expires_at. Tokens are revocable individually or in bulk (the kill switch). Source: backend/app/models/agent_token.py.

3.4 Scope

A token has exactly one of two scopes:

  • read — May call read-only tools (check_budget, list_envelopes, get_daily_status). Cannot authorize purchases.
  • spend — May call all read-only tools AND authorize_purchase. Subject to all spending guards.

Scope is checked server-side as the first gate in authorize_purchase. Source: backend/app/services/agent_spending.py.

3.5 Envelope Binding

A token MAY be bound to a specific set of envelopes via allowed_category_ids (a JSON array of stable category UUIDs). When bound, the agent can only authorize purchases against those categories. An attempt to spend in any other category is rejected with envelope_not_bound. A null binding means the agent may spend in any of the user's envelopes (subject to all other guards). Legacy allowed_category_slugs is honored for backwards compatibility. Source: backend/app/services/agent_guard.py (check_envelope_binding).

3.6 Per-Transaction Cap

A hard maximum dollar amount per single authorize_purchase call, configurable per token (default $50). Exceeding it returns per_transaction_cap_exceeded. Source: backend/app/services/agent_guard.py.

3.7 Session Cumulative Cap

A rolling total spending limit across all of an agent's purchases since the last session reset (default $100). Exceeding it returns session_cap_exceeded. The session auto-resets after 24 hours of inactivity to prevent permanent lockout from a one-time spike. Source: backend/app/services/agent_guard.py (SESSION_RESET_SECONDS).

3.8 Rate Limit

A maximum of three authorize_purchase calls per minute per agent. Exceeding it returns rate_limited with a retry_after_seconds field. The minute window is sliding, anchored to last_transaction_at. Source: backend/app/services/agent_guard.py (AGENT_RATE_LIMIT).

3.9 Budget Pacing

A guard that rejects purchases consuming budget faster than a sustainable daily pace. Computed as daily_pace = envelope_remaining / days_remaining, then pace_limit = daily_pace * pace_multiplier. Default pace_multiplier = 3.0. Exceeding the pace returns exceeds_budget_pace with the computed daily_pace, pace_limit, and days_remaining. Source: backend/app/services/agent_guard.py (check_budget_pace).

3.10 Kill Switch

A single user-initiated action that revokes ALL of a user's agent tokens simultaneously (POST /agents/revoke-all, exposed in the desktop app under Settings → Agent Access → Freeze All Agents). Revoked tokens immediately return 401 on next use. Designed for the "something is wrong, stop everything right now" moment. Source: backend/app/api/agents.py.

3.11 Human Approval Gate

The Human Approval Gate (HAG) is the deferral lifecycle that turns the v0.1 synchronous policy gate into a true two-phase delegated spend protocol. When an agent's authorize_purchase request meets the per-token requires_human_approval_threshold, the request is parked as a pending_authorization row in status pending, the agent receives a deferral response with a next_action hint, and the user is invited back in to approve or deny via the desktop UI. After approval, the agent explicitly claims the approval by calling complete_pending_authorization — only at claim time does the envelope debit + transaction ledger entry happen.

This section is normative.

3.11.1 The authorize-then-claim pattern

The pattern maps to payments-industry vocabulary:

  • Authorize (authorize_purchase) — policy evaluation. Either auto-approves (and writes the transaction immediately, just like v0.1) or defers to human review (writes a pending_authorization row, returns no transaction).
  • Claim (complete_pending_authorization) — agent-initiated capture of an approved authorization. Atomic CAS update flips the row from approved to completed, debits the envelope, writes the transaction ledger entry, and populates completion_metadata.

This is functionally equivalent to "auth/capture" on a card rail. The envelope is not debited at the human's "yes"; it's debited at the agent's claim. That separation makes the protocol observable and idempotent: a human approval without a subsequent agent claim is a real, auditable event ("user said yes but the agent never followed up") and a re-claim of the same pending_id is a no-op that returns the cached completion record.

3.11.2 Threshold semantics

The per-token requires_human_approval_threshold (numeric, default null) controls when HAG fires:

Threshold value Behavior Example
null (default) HAG OFF. All purchases auto-approve up to per-transaction cap and other guards. This is the v0.1 behavior and is the default for backwards compatibility. A research agent token with threshold = null and per_tx_cap = $50: a $15 purchase auto-approves; a $60 purchase rejects with per_transaction_cap_exceeded.
0 HAG ALWAYS-ASK. Every authorize_purchase call parks for human review, regardless of amount. A new agent on a sensitive envelope with threshold = 0: a $5 snack purchase parks; the user sees it in the inbox before any debit.
$X (positive decimal) HAG ASK-AT-OR-ABOVE-X. Purchases >= $X park; purchases < $X auto-approve up to per-transaction cap. The bound is inclusive — amount == $X parks. A grocery agent with threshold = $40: a $32 purchase auto-approves; a $40 purchase parks; a $45 purchase parks.

Terminology: opt-in HAG (default off) — the system ships HAG-disabled so v0.1 integrators see no behavior change. (Distinct from "default deny" — see the user-facing setup guidance.)

The threshold is validated at agent registration against the resolved per_transaction_cap: a threshold above the per-tx cap is silently unreachable (Guard 1 rejects the amount before HAG ever fires), so the API returns 422 rather than accepting dead state. Read-scope agents cannot spend, so any threshold is null'd out at the request boundary.

3.11.3 Status enum

A pending_authorization row carries one of five status values. These are the exact lowercase strings wire-protocol clients see in check_pending_authorization responses and in the current_status field of complete_pending_authorization error envelopes:

Status Meaning Terminal?
pending Parked, awaiting user decision (default expiry: 15 minutes from request). No
approved User approved via desktop UI / REST. Awaiting agent claim. No (agent must claim before expires_at)
denied User explicitly denied. Yes
expired The 15-minute total budget on expires_at passed before the agent claimed (covers both "user never decided" AND "user said yes but agent didn't follow up"). Yes
completed Agent claimed an approved authorization; envelope debit + transaction written. Yes

3.11.4 Deferral lifecycle

                                                                        ┌──────────────┐
       authorize_purchase                                                │              │
       (amount >= threshold)                                             │   denied     │
              │                          ┌──── user denies via UI/REST ─┤  (terminal)  │
              ▼                          │                               │              │
       ┌────────────┐                    │                               └──────────────┘
       │  pending   │────────────────────┤
       │            │                    │      complete_pending_authorization
       └────────────┘                    │      (CAS: approved → completed)
              │                          │      + envelope debit
              │ user approves            ▼      + transaction ledger entry          ┌──────────────┐
              │ via UI/REST       ┌────────────┐                                    │              │
              └──────────────────▶│  approved  │───────────────────────────────────▶│  completed   │
                                  │            │                                    │  (terminal)  │
                                  └────────────┘                                    │              │
                                        │                                           └──────────────┘
                                        │ (no claim before expires_at)                 ▲
                                        ▼                                              │
                                  ┌────────────┐                                       │
                                  │  expired   │◀──────── (no decision before ─────────┘
                                  │ (terminal) │           expires_at, from `pending`)
                                  └────────────┘

Numbered flow:

  1. Agent calls authorize_purchase. Amount meets threshold → row written with status = pending, expires_at = requested_at + 15 minutes. Response includes reason: "pending_human_approval", pending_id, expires_at, and a next_action hint pointing the agent at check_pending_authorization and complete_pending_authorization.
  2. Agent polls check_pending_authorization(pending_id). The service inline-expires any stale rows (covers both pending and approved past expires_at) so a polling client never sees a status that should already be terminal.
  3. User opens the desktop app, sees the request in the Pending Authorizations inbox, taps Approve or Deny. (Or calls POST /pending-authorizations/{id}/approve|deny directly.) The row transitions pending → approved or pending → denied. Approval does not debit the envelope; it just unlocks the claim window.
  4. Agent calls complete_pending_authorization(pending_id). The service runs an atomic CAS UPDATE that flips approved → completed only if the row is still approved AND not yet expired. On CAS success: envelope is debited via the same quick_spend path used by auto-approved purchases, transaction ledger entry is written, completion_metadata is populated, and the response shape matches an auto-approved authorize_purchase success.
  5. Terminal alternatives:
    • User denies in step 3 → row sits at denied; agent's claim returns 409 invalid_state.
    • Nobody acts before expires_at → row flips to expired on the next read; agent's claim returns 410.

3.11.5 completion_metadata (normative observability surface)

Populated on the agent's claim. Shape:

{
  "transaction_ledger_entry_id": "uuid",
  "envelope_id_at_debit": "uuid",
  "debited_amount": "decimal-string",
  "completed_at": "ISO-8601 UTC",
  "envelope_remaining_at_debit": "decimal-string"
}

Field semantics:

  • transaction_ledger_entry_id — UUID of the Transaction row written by the claim. Resolves to a single, auditable ledger entry that any third party reconciling Zado-mediated agent spend can anchor on. The same id appears in the audit log entry for the debit and in the agent activity feed event with outcome="completed", reason_code="human_approval_redeemed".
  • envelope_id_at_debit — UUID of the envelope row mutated by the debit. May be null if the bound category exists but has no envelope budgeted in the effective month (the spend is still recorded, with 0 envelope context).
  • debited_amount — decimal string of the amount actually debited; equals the amount on the original authorize_purchase call.
  • completed_at — ISO-8601 UTC timestamp of the CAS that flipped approved → completed.
  • envelope_remaining_at_debit — decimal string of the envelope balance immediately after the debit. This is the same post-claim value returned as envelope_remaining in the success response, cached here so idempotent replays can return the same balance without re-running the debit path.

This field is the audit-trail anchor that links a specific human "yes" to a specific ledger debit. It is the single source of truth for agentic spend reconciliation in v0.2.

3.11.6 Idempotent replay

A successful complete_pending_authorization call is idempotent on the pending_id. Re-calling on a row already in status completed returns the same response shape (same transaction_id, same amount, same envelope_remaining) by reading the cached completion_metadata — no second debit, no second activity event, no second audit row. This makes the claim safe to retry across network failures, agent restarts, or duplicated prompts.

3.11.7 Error semantics

complete_pending_authorization maps to HTTP status codes:

Status Trigger Response shape
200 First successful claim, OR idempotent replay of a prior completed row. {authorized: true, transaction_id, amount, category, vendor, envelope_remaining, pending_id}
404 pending_id does not exist, OR belongs to a different user, OR (per agent-isolation rules) belongs to a different agent token under the same user. The 404 is uniform across all three to avoid leaking existence. {status: "not_found"}
409 Row exists and is reachable, but status != approved (caller error: still pending, or already denied). The body carries the actual current status. {status: "invalid_state", current_status, reason: "pending_status_invalid", message}
410 Row was approved but the 15-minute window passed before the agent claimed, OR the agent token was deleted between approve and claim. The row has been flipped to expired by the call itself; subsequent claims see expired directly. {status: "expired", reason, message}

Network or transport-level errors at the MCP layer surface as {error: "<detail>"} and are NOT policy decisions — agents SHOULD treat them as retryable infrastructure faults.

3.12 Audit Context

Every mutation initiated by an agent is automatically tagged with actor_type="mcp_agent" and actor_details containing the agent's id, name, scope, and current session_spend_so_far. Capture is automatic via AuditMiddleware — no per-route plumbing required. Source: backend/app/core/audit_context.py and backend/app/middleware/audit.py.


4. MCP tools

This section is normative. Zado exposes six Model Context Protocol tools via the packaged zado-mcp PyPI distribution. The server runs locally over stdio; all authentication, scope enforcement, guard checks, HAG state machine, audit logging, and database access happen server-side at the Zado backend (zadofi.ai by default). The local MCP process is a pure transport layer and cannot bypass policy.

AI Agent → MCP stdio → ZadoAPIClient → HTTPS Bearer → Zado backend → policy + envelopes + HAG

The six tools split across two scopes (see §3.4):

Tool Scope Phase
check_budget read Read
list_envelopes read Read
get_daily_status read Read
authorize_purchase spend Phase 1 (authorize)
check_pending_authorization spend Phase 1 ↔ 2 (poll for human decision)
complete_pending_authorization spend Phase 2 (claim)

Auth boundaries. Each tool entry below states explicitly who can call it:

  • Agent token (MCP) — invoked over MCP stdio with a Bearer agent token. Subject to scope, binding, and guard checks per §3 and §6.
  • Human session (REST) — invoked over HTTPS by the desktop app with the user's session cookie. Subject to CSRF + auth.
  • Both — both surfaces are valid; per-route logic distinguishes actor type.

Cross-boundary calls (a human session attempting an agent-only MCP tool, or an agent token attempting a human-only REST endpoint) are rejected with 403. The agent-side allowlist is enforced by AgentScopeMiddleware on the backend (default-deny — new endpoints require explicit addition).

4.1 check_budget

Scope required: read Auth boundary: Agent token (MCP) only. The HTTP backing route is on the agent allowlist; a human session calling the same MCP tool name has no analog (humans use the desktop app's spending views directly). HTTP backing route: GET /api/spending/category/{category}

Returns the budgeted, spent, and remaining amounts for a single envelope.

Request schema:

Field Type Required Description
category string yes Category slug (e.g., "groceries", "dining", "fun-money")

Success response schema:

Field Type Description
category string Human-readable envelope name
remaining number Available balance in dollars
budgeted number Monthly budgeted amount in dollars
spent number Amount spent so far this month in dollars
percentage_used number spent / budgeted * 100

Example request:

{ "category": "groceries" }

Example success response:

{
  "category": "Groceries",
  "remaining": 276.50,
  "budgeted": 400.00,
  "spent": 123.50,
  "percentage_used": 30.875
}

4.2 list_envelopes

Scope required: read Auth boundary: Agent token (MCP) only. The HTTP backing route is on the agent allowlist; humans see the same data in the desktop app's envelopes view. HTTP backing route: GET /api/envelopes/summary?month=YYYY-MM

Returns all envelope balances for the current calendar month.

Request schema: No parameters. The MCP tool implicitly uses the current month in YYYY-MM form (UTC).

Success response schema:

Field Type Description
month string YYYY-MM of the month being reported
total_budgeted number Sum of all envelope budgets
total_spent number Sum of all envelope spending
total_available number total_budgeted - total_spent
envelopes array Per-envelope detail (see below)

Each envelopes[] entry:

Field Type Description
name string Envelope name
budgeted number This envelope's budget
spent number This envelope's spending
remaining number This envelope's available balance
percentage_used number spent / budgeted * 100
status string Envelope health status (e.g., "on_track", "warning", "empty")

Example success response:

{
  "month": "2026-04",
  "total_budgeted": 2400.00,
  "total_spent": 1820.30,
  "total_available": 579.70,
  "envelopes": [
    {
      "name": "Groceries",
      "budgeted": 400.00,
      "spent": 123.50,
      "remaining": 276.50,
      "percentage_used": 30.875,
      "status": "on_track"
    },
    {
      "name": "Dining",
      "budgeted": 200.00,
      "spent": 198.00,
      "remaining": 2.00,
      "percentage_used": 99.0,
      "status": "warning"
    }
  ]
}

If the agent token has allowed_category_ids set, the response is filtered to only those envelopes (server-side, via backend/app/services/agent_guard.py:filter_by_binding).

4.3 get_daily_status

Scope required: read Auth boundary: Agent token (MCP) only. The HTTP backing route is on the agent allowlist. HTTP backing route: GET /api/spending/status

Returns today's spending posture across all envelopes — total available, daily allowance, and any active alerts.

Request schema: No parameters.

Success response schema:

Field Type Description
total_available number Sum across all envelopes (or bound envelopes) for current month
daily_allowance number Sustainable daily spend rate for the rest of the month
days_remaining number Days left in the current month (including today)
alerts array Active spending alerts

Each alerts[] entry:

Field Type Description
category string Envelope name the alert targets
type string Alert type (e.g., "envelope_empty", "pace_warning", "upcoming_bill")
message string Human-readable alert text

Example success response:

{
  "total_available": 579.70,
  "daily_allowance": 96.62,
  "days_remaining": 6,
  "alerts": [
    {
      "category": "Dining",
      "type": "pace_warning",
      "message": "You're spending faster than the envelope allows for the rest of the month."
    }
  ]
}

4.4 authorize_purchase

Scope required: spend Auth boundary: Agent token (MCP) only. Humans do NOT call this — humans spend through the desktop app's normal transaction-entry surface. HTTP backing route: POST /api/agents/purchase

Authorizes and logs a purchase if and only if the envelope has enough funds AND all guards pass. This is the load-bearing tool of the spec — it implements the envelope-as-permission pattern end-to-end.

In v0.2, authorize_purchase becomes the Phase 1 (authorize) step of the authorize-then-claim pattern when the agent token has requires_human_approval_threshold set and the amount meets it. See §3.11 for the deferral lifecycle. When HAG is off (threshold = null, the v0.1 default), authorize_purchase behaves identically to v0.1 — synchronous debit + transaction creation on success.

Request schema:

Field Type Required Description
amount number yes Purchase amount in dollars (e.g., 43.20)
category string yes Category slug — must match an envelope and (if bound) the token's allowed_category_ids
vendor string yes Merchant or vendor name (free text, e.g., "Whole Foods")

Authorization flow (from services/agent_spending.py::authorize_purchase):

  1. Scope check. Token must have scope == "spend". Otherwise reject with insufficient_scope.
  2. Envelope binding check. If the token has allowed_category_ids, the request's category must resolve to one of them. Otherwise reject with envelope_not_bound.
  3. Guards. In order: per-transaction cap, session cumulative cap, rate limit, budget pacing. Any failure rejects with the corresponding code.
  4. Envelope balance check. The envelope must have enough remaining funds for the purchase. Otherwise reject with envelope_empty.
  5. Human Approval Gate (HAG, v0.2). If the token has requires_human_approval_threshold set and amount meets it, defer: roll back the guard-state mutations from step 3, write a pending_authorization row, return reason: "pending_human_approval" with a pending_id, expires_at, and a next_action hint pointing the agent at check_pending_authorization and complete_pending_authorization. See §3.11. When the threshold is null (the v0.1 default), this step is a no-op and execution falls through.
  6. Atomic commit. Guard state update + transaction creation + envelope-balance update commit together. Any exception rolls back all three.

Success response schema:

Field Type Description
authorized boolean Always true for this shape
transaction_id string UUID of the created transaction
amount number Echoed authorized amount
category string Echoed category slug
vendor string Echoed vendor name
envelope_remaining number Envelope balance AFTER the purchase

Example success response:

{
  "authorized": true,
  "transaction_id": "8f2a1e3c-7b40-4d8e-9c71-c1d2e3f4a5b6",
  "amount": 43.20,
  "category": "groceries",
  "vendor": "Whole Foods",
  "envelope_remaining": 233.30
}

Rejection response schema:

Field Type Description
authorized boolean Always false for this shape
reason string One of the rejection codes below
detail object | string Code-specific context (see below)

Rejection codes:

Code Triggered by Context fields Recommended agent retry
insufficient_scope Token has scope != "spend" detail (string explanation) Do NOT retry. Re-register the agent with scope: "spend" and a fresh token.
envelope_not_bound Request category not in token's allowed_category_ids category, bound_category_ids (array) Do NOT retry the same category. Either rebind the token to include this category, or use a different category that IS bound.
per_transaction_cap_exceeded amount > token.per_transaction_cap limit (the cap, in dollars) Do NOT retry the same amount. Split the purchase into multiple smaller calls (each ≤ cap), or raise the cap.
session_cap_exceeded session_spending_total + amount > token.session_spending_cap limit (cap), session_total (current spent) Do NOT retry. Wait for the 24-hour session reset, or have the user raise the session cap.
rate_limited More than 3 calls in the last 60 seconds limit (3), retry_after_seconds (int) Wait retry_after_seconds then retry.
exceeds_budget_pace Purchase consumes more than pace_multiplier × daily_pace of the envelope daily_pace, pace_limit, days_remaining, envelope_remaining, pace_multiplier Either retry with a smaller amount ≤ pace_limit, or have the user raise pace_multiplier.
envelope_empty Envelope remaining < amount after all prior guards passed detail (recommendation string) Do NOT retry. Either pick a different category or have the user fund the envelope.
pending_human_approval HAG triggered (amount >= requires_human_approval_threshold); request parked for human review (v0.2). pending_id, expires_at, amount, category, vendor, next_action: {poll, when_approved, pending_id} Do NOT retry authorize_purchase — that would create a duplicate pending row. Poll check_pending_authorization(pending_id) until status = approved or terminal; on approved, call complete_pending_authorization(pending_id) to claim.

A transport-level error (network failure, malformed response from the API) returns reason: "api_error" — this is not a policy decision and the agent SHOULD treat it as a retryable infrastructure fault.

Example rejection:

{
  "authorized": false,
  "reason": "exceeds_budget_pace",
  "detail": {
    "allowed": false,
    "reason": "exceeds_budget_pace",
    "daily_pace": 17.16,
    "pace_limit": 51.49,
    "days_remaining": 6,
    "envelope_remaining": 102.97,
    "pace_multiplier": 3.0
  }
}

4.5 check_pending_authorization

Scope required: spend (when invoked by an agent token) Auth boundary: Dual-surface — agent token via MCP (spend scope) AND human session via REST (read scope). Both surfaces return the same response shape today; the route is allowlisted on both sides because the data (own pending status) is safe for the calling user to read either way. HTTP backing route: GET /api/agents/pending-authorizations/{pending_id}

Future maintainers: any new field added to this response MUST be classified as agent-only-readable or human-readable. Agent-only fields require splitting this endpoint into two distinct routes; do NOT mix surfaces in one response. Adding agent-only data to a dual-surface response is a data-exposure path.

Polls the status of a parked agent purchase request. When authorize_purchase returns reason: "pending_human_approval", the agent uses this tool to learn whether the user approved, denied, or let the request expire — without short-polling the human (the human gets a notification via the desktop app).

The service inline-expires stale rows on every read, so a polling client always sees a fresh status. A row that should already be expired is flipped to expired before the response is built; the agent will never see a stale pending or approved past expires_at. (See §3.11 for status enum.)

Cross-agent isolation. A pending row is owned by the agent token that created it. An agent token attempting to read another agent's pending row under the same user receives 404 (not 403) so the agent cannot enumerate other agents' activity by probing.

Request schema:

Field Type Required Description
pending_id string (UUID) yes The pending_id returned by authorize_purchase

Success response schema:

Field Type Description
pending_id string UUID of the pending row
status string One of pending, approved, denied, expired, completed (the wire literals from §3.11)
amount number Amount of the original request, in dollars
category string Echoed category slug
vendor string | null Echoed vendor name (may be null if the original request omitted it)
requested_at string ISO-8601 UTC timestamp of the original request
expires_at string ISO-8601 UTC timestamp at which the row terminates if not claimed
resolved_at string | null ISO-8601 UTC timestamp of the user's approve/deny, or null if still pending
resolution_note string | null Optional free-text note attached by the user at approve/deny time

Not-found response:

{ "status": "not_found" }

Returned when the pending_id does not exist, OR belongs to a different user (cross-tenant probe), OR belongs to a different agent under the same user (cross-agent probe). Uniform 404 to avoid existence leak.

Example success response (status approved):

{
  "pending_id": "8b2c4d6e-3f1a-4d5b-9e7c-1a2b3c4d5e6f",
  "status": "approved",
  "amount": 87.50,
  "category": "groceries",
  "vendor": "Whole Foods",
  "requested_at": "2026-05-03T18:42:11.382Z",
  "expires_at": "2026-05-03T18:57:11.382Z",
  "resolved_at": "2026-05-03T18:43:55.211Z",
  "resolution_note": null
}

When the response shows status: "approved", the agent's next action is complete_pending_authorization(pending_id).

4.6 complete_pending_authorization

Scope required: spend Auth boundary: Agent token (MCP) only. The complete endpoint is not exposed to human sessions — humans approve via POST /pending-authorizations/{id}/approve, which only changes the row's status to approved. The actual envelope debit + transaction write happens when the agent claims via this MCP tool. A human session calling the /api/agents/pending-authorizations/{id}/complete endpoint receives 403. HTTP backing route: POST /api/agents/pending-authorizations/{pending_id}/complete

Phase 2 of the authorize-then-claim pattern. Atomically claims a user-approved pending authorization: the service runs a CAS UPDATE that flips approved → completed only if the row is still approved AND not yet expired, then debits the envelope through the same quick_spend path used by auto-approved purchases. The Transaction shape, source, agent_token_id, and envelope-update SQL match the auto-approved path exactly, so nightly self-healing sees a consistent ledger.

The CAS pattern guarantees exactly-one debit under concurrent claims — losers see rowcount = 0 and route into the appropriate error response based on the row's actual state.

Idempotent. Re-calling on a row already in status completed returns the same response shape (same transaction_id, same envelope state) by reading the cached completion_metadata — no second debit, no second activity event, no second audit row. Safe to retry.

Request schema:

Field Type Required Description
pending_id string (UUID) yes The pending_id returned by authorize_purchase (must currently be approved)

Success response schema (matches authorize_purchase auto-approved success):

Field Type Description
authorized boolean Always true
transaction_id string UUID of the Transaction row written by the claim (also the transaction_ledger_entry_id in completion_metadata)
amount number Echoed amount
category string Echoed category slug
vendor string | null Echoed vendor name
envelope_remaining number Envelope balance AFTER the debit
pending_id string Echoed for traceability

Example success:

{
  "authorized": true,
  "transaction_id": "9e1d3a5c-2b4f-4d6e-8a9c-1b2c3d4e5f60",
  "amount": 87.50,
  "category": "groceries",
  "vendor": "Whole Foods",
  "envelope_remaining": 145.80,
  "pending_id": "8b2c4d6e-3f1a-4d5b-9e7c-1a2b3c4d5e6f"
}

Error responses (mirror the HTTP status semantics from §3.11.7):

MCP shape HTTP status Meaning
{"status": "not_found"} 404 Cross-tenant, cross-agent, or unknown pending_id.
{"status": "invalid_state", "current_status": "<x>", "reason": "pending_status_invalid", "message": "..."} 409 Row exists but status != approved (still pending, or already denied). The body carries the actual current status.
{"status": "expired", "reason": "<reason>", "message": "..."} 410 Row was approved but the 15-minute window passed before the agent claimed, OR the agent token was deleted between approve and claim. The row has been flipped to expired by the call itself.
{"error": "<detail>"} 5xx / network Transport-level error. NOT a policy decision; retryable.

After a successful claim, the agent should proceed with the actual purchase on its own settlement rail (Stripe, card, etc.). Bank sync will reconcile the real merchant transaction against the Zado record later. The Zado record is the policy-and-evidence anchor; settlement is delegated.

4.7 Concurrency and atomicity

authorize_purchase uses a row-level FOR UPDATE lock on the agent token row to prevent TOCTOU race conditions between guard evaluation and guard-state update. On SQLite (which does not support FOR UPDATE), the underlying connection-level write serialization provides the same guarantee. Concurrent calls on the same token are serialized; concurrent calls on different tokens for the same envelope race on the envelope row, with the loser receiving envelope_empty if the balance flipped between check and commit.

complete_pending_authorization uses a different concurrency primitive: a single SQL UPDATE ... WHERE status='approved' AND expires_at >= now (see §3.11). The targeted UPDATE is itself the lock — under SQLite (BEGIN EXCLUSIVE serializes writers) and PostgreSQL (row-level lock), exactly one concurrent claim sees rowcount == 1 and proceeds to debit; all losers see rowcount == 0 and re-read to produce the correct error response. This eliminates the double-debit race that would otherwise be possible between two agents (or two retries of the same agent) racing on the same approved row.

5. Audit log

Every mutation initiated by an agent is automatically tagged with an audit context capturing who acted, what they did, and when. Coverage is automatic via AuditMiddleware — no per-route plumbing required, so future endpoints inherit audit coverage without code changes.

5.1 Actor model

The audit system supports six actor types (backend/app/core/audit_context.py):

Actor type Source
user A human using the desktop UI with a session cookie
assistant The Zado in-app AI coach
rule An automated rule (categorization, recurring detection)
sync Bank sync operations (Plaid / Teller)
system Internal system operations
mcp_agent An external AI agent via an MCP token

For agent-initiated requests, actor_type is set to mcp_agent and actor_details is populated with the agent's identity and current spend posture (backend/app/api/deps.py populates the audit context as part of get_user_context / get_agent_context).

5.2 Audit context schema

For an agent-initiated mutation:

Field Type Description
actor_type string Always "mcp_agent" for agent calls
actor_details.agent_id string (UUID) The agent token's id
actor_details.agent_name string Human-readable agent name (e.g., "Claude Code", "OpenClaw")
actor_details.scope string "read" or "spend"
actor_details.session_spend_so_far string (decimal) Cumulative session spending at the moment of the action
batch_id string (UUID) Optional, groups related changes for bulk undo

Example audit entry for an authorize_purchase success:

{
  "id": "9b7f8c2d-1a4e-4f6b-83c1-d5e6f7a8b9c0",
  "actor_type": "mcp_agent",
  "actor_details": {
    "agent_id": "1d73176f-5a24-4c8e-b21f-3a4b5c6d7e8f",
    "agent_name": "OpenClaw",
    "scope": "spend",
    "session_spend_so_far": "47.50"
  },
  "action": "transaction.create",
  "entity_type": "transaction",
  "entity_id": "8f2a1e3c-7b40-4d8e-9c71-c1d2e3f4a5b6",
  "before": null,
  "after": {
    "amount": 43.20,
    "category_slug": "groceries",
    "vendor": "Whole Foods",
    "agent_token_id": "1d73176f-5a24-4c8e-b21f-3a4b5c6d7e8f"
  },
  "occurred_at": "2026-04-25T18:42:11.382Z"
}

5.3 Coverage and exportability

  • Coverage: All POST/PUT/PATCH/DELETE requests are audited via backend/app/middleware/audit.py. Read-only requests are NOT audited (they do not mutate state).
  • Exportability: The audit log is exportable by the user. The export shape is a stable JSON list of audit entries; the export endpoint is part of the data-export surface (backend/app/api/export/).

6. Security model

This section is normative. v0.1 covered defense-in-depth in passing; v0.2 elevates security to the gating concern it actually is for agentic finance adoption. The question every partner asks first — "what's the worst that can happen if my Zado-issued agent token is compromised?" — is answered directly here, in three subsections: threat model, blast radius, and reversibility / audit.

6.1 Threat model

The threats v0.2 explicitly defends against:

  • Token theft. An attacker obtains an agent's Bearer token (e.g., reads it from a misconfigured MCP host config file, exfiltrates it from a compromised dev machine). Mitigation: tokens are SHA-256 hashed at rest and shown plaintext only once at registration; revocation is one click via Settings → Agent Access; per-transaction cap, session cap, envelope binding, and pace multiplier bound the attacker's spend before kill-switch.
  • Confused deputy. A legitimate agent is coerced (by prompt injection, user mistake, or compromised model output) into spending in a category outside its intended scope, or above its intended cap. Mitigation: envelope binding (per-token allowed_category_ids) + per-transaction cap
    • HAG threshold. The agent's binding is set by the human at registration; no agent-side request can widen it.
  • Cross-tenant leak. An attacker with one user's agent token attempts to read or spend against another user's envelopes. Mitigation: every server-side query carries a user_id filter; agent tokens carry a user_id FK that the auth layer pins on every request. There is no API surface that returns data without user_id scoping.
  • Prompt injection coercing higher-cap spend. An attacker poisons agent context to make it ask Zado for an above-policy spend. Mitigation: HAG. When the token's requires_human_approval_threshold is set, every injection-induced large spend parks for human review; the human is the final gate on out-of-pattern requests.
  • Replay against idempotency. An attacker re-submits a captured request (e.g., a stolen complete_pending_authorization call) hoping for a second debit. Mitigation: complete_pending_authorization is idempotent on pending_id (CAS-protected, returns cached completion_metadata on replay). The user-side approve/deny endpoints honor an Idempotency-Key header so duplicate clicks return the cached response.
  • Expired-pending claim. An attacker holds an old pending_id and attempts to claim it long after the user thought it was resolved. Mitigation: every read inline-expires stale rows (covers both pending past TTL AND approved past TTL), and the CAS on claim re-checks expires_at >= now; an expired row returns 410 and is permanently unclaimable.

The threats v0.2 does NOT defend against (left to the user's broader security posture):

  • Compromised user account (session-cookie theft). The human session is the root of trust; if the user's primary auth is compromised, an attacker gets the same surface the user has, including the ability to register new agents.
  • Compromised Zado backend. The spec assumes the policy-evaluation server is operating correctly. Multi-region byzantine fault tolerance is out of scope.
  • Side-channel inference (an agent learning user habits from observed envelope balances over time). Within scope is what the agent can do, not what it can infer.

6.2 Blast radius

What an agent literally cannot do, even with a stolen token, even with prompt-injected goals, even with full local-runtime compromise:

  • Cannot spend outside bound categories. If the token has allowed_category_ids, every authorize_purchase against an unbound category returns envelope_not_bound. Server-side check; the local MCP process cannot bypass it. Read-side endpoints are similarly bound — a bound agent reading list_envelopes sees only its bound envelopes (the envelope-binding read-path filter is centralized so single-envelope and aggregation endpoints share the same scoping logic).
  • Cannot exceed the per-transaction cap. Configurable per token (default $50); a single request above this returns per_transaction_cap_exceeded. Splitting attempts into many smaller requests hits the next limit:
  • Cannot exceed the session cap. Cumulative across all of an agent's purchases since the last 24-hour idle reset. Default $100; configurable per token. Hits session_cap_exceeded and the agent must wait the reset window or have the user raise the cap.
  • Cannot bypass the rate limit. Maximum 3 authorize_purchase calls per minute per agent. Hits rate_limited with a retry_after_seconds.
  • Cannot drain an envelope faster than the pacing guard allows. The pace guard rejects purchases consuming more than pace_multiplier × daily_pace. An agent trying to burn the rest of the envelope in one request is throttled.
  • Cannot hit envelopes outside the binding allowlist even by category-name spoofing: the binding check resolves the request's category to a stable category UUID before comparing against allowed_category_ids.
  • Cannot see other users' data. Every read filters by user_id. There is no API surface that returns cross-user data; cross-tenant probes return 404 (uniform with not-found) so existence is not leakable.
  • Cannot persist beyond TTL. Tokens carry a mandatory expires_at (default 90 days, configurable 1–90). After expiry, the token returns 401 on any call.
  • Cannot bypass HAG when configured. With requires_human_approval_threshold set, every spend at or above the threshold parks for human approval — and even after approval, the agent must explicitly claim via complete_pending_authorization to debit. There is no agent-side path to convert an above-threshold request into an immediate debit.

The architectural guarantee underneath all of the above is tenant isolation in the data layer: user_id scoping on every query, agent token binding to user_id, envelope-binding read-path filtering applied via a centralized helper across all envelope-aware endpoints, per-user partitioning of graph memory and receipt archive. These guarantees have been adversarially audited multiple times in 2026-04 (independent Codex and Gemini paranoid-audit sweeps) — every flagged finding from those sweeps was either fixed or formally rejected with reasoning, and the deletion registry covers every user-scoped model so account deletion provably removes all user-bound rows.

6.3 Reversibility and audit

Every state transition that involves a Zado-mediated agent spend is recorded, surfaced to the user, and reversible up to the point of external settlement. This is the third leg of the trust posture — defense-in-depth contains the blast; reversibility makes the rest recoverable.

  • Audit log on every transition. Every agent-initiated mutation — authorize-success, defer-to-pending, user approve, user deny, expire, agent claim — writes an AuditLog row capturing actor (mcp_agent or user), action, before/after state, and timestamp. Capture is automatic via the audit middleware; future endpoints inherit coverage without per-route plumbing. (See §5.)
  • completion_metadata ties human "yes" to ledger debit. The per-pending-row completion_metadata JSON (§3.11.5) records the transaction_ledger_entry_id, envelope_id_at_debit, debited_amount, completed_at, and envelope_remaining_at_debit of the claim. A third party reconciling an agent spend can match the human's approval (in audit log) → the agent's claim (in audit log) → the actual ledger entry (via transaction_ledger_entry_id) without ambiguity, while also preserving the post-debit envelope balance the caller observed.
  • Activity feed surfaces all agent attempts, including denials. The desktop app's Agent Activity feed shows every authorize-success, authorize-rejection (with reason code), defer, and completion. A user can see "your agent tried to spend $300 on dining and was blocked by the pacing guard" without opening logs.
  • Transactions are disputable and reversible. Any agent-initiated transaction enters the same dispute and rollback surfaces a human-entered transaction does. The ledger is reversible; settlement is the only irreversible step (and Zado does not perform settlement — that is delegated to the agent's chosen rail).
  • Kill switch. Settings → Agent Access → Freeze All Agents revokes every active agent token in one action; revoked tokens return 401 on the next call and any in-flight pending rows the agent owns can no longer be claimed.

6.4 Defense in depth

A successful authorize_purchase (auto-approved path) traverses eight distinct enforcement layers in v0.2, any one of which can deny:

# Layer Enforcement surface
1 Token hashing (SHA-256 at rest) Token storage
2 Bearer token transport over HTTPS MCP → Backend
3 Token lookup + is_active + TTL check (revoked or expired → 401) Backend dependency injection
4 Scope enforcement (spend required for authorize_purchase) agent_spending.authorize_purchase + AgentScopeMiddleware allowlist
5 Envelope binding check (category must resolve to one in allowed_category_ids) agent_guard.check_envelope_binding
6 Spending guards (per-tx cap, session cap, rate limit, pacing) agent_guard.check_and_record_spend
7 Envelope balance check (envelope remaining >= amount) SpendingService.check_can_afford
8 Human Approval Gate (defer if amount >= threshold) agent_spending.authorize_purchase step 5 (v0.2)

A request that passes layer 4 but fails layer 5 produces envelope_not_bound. A request that passes layer 6 but fails the envelope balance at layer 7 produces envelope_empty and the guard-state mutation is rolled back so the rejected attempt does not consume the agent's session cap. A request that passes all balance checks but meets the HAG threshold at layer 8 defers (see §3.11) — the envelope is not debited until the agent's later claim. A claim itself is gated by a CAS UPDATE that requires the row to be approved AND not yet expired (see §4.7); concurrent claims serialize to exactly one debit.

What agents CAN see (recap from §3 + above): envelope names and balances for bound envelopes; daily allowance, days-remaining, alerts; affordability check results; for spend scope, the transaction record they themselves created.

What agents CANNOT see (architectural, not policy): bank account/routing numbers; bank connection tokens (Plaid/Teller access_token — these are encrypted at rest and never returned in any API response, audit entry, or coach context); raw transaction history outside bound categories; user PII (email, phone, address) beyond what envelope names happen to expose; other agents' tokens or activity; any other user's data. The architectural guarantee: the local MCP process has zero direct database access — it can only call the documented HTTPS endpoints with its Bearer token, and no endpoint returns the data above (it isn't in any response shape the agent allowlist permits).

7. Integration examples

This section is informative. Three patterns cover the common integration shapes. All examples assume the integrator has already registered an agent in the Zado app (Settings → Agent Access → Create Agent) and obtained a Bearer token.

7.1 Install — pip install zado-mcp

The canonical install for v0.2 is the packaged zado-mcp PyPI distribution. One-line install:

pip install zado-mcp

Requires Python ≥ 3.10. Two transitive dependencies (mcp, httpx).

Interactive setup. After install, run:

zado-mcp init

The interactive flow prompts for the Zado instance URL (default https://zadofi.ai) and the agent token (input is hidden), then writes ~/.config/zado-mcp/config.json with mode 0600. The token never appears in the printed .mcp.json snippet — it lives only in the 0600 config file.

Canonical MCP host config. Paste this snippet into your agent's MCP config (Claude Code .mcp.json, Claude Desktop's claude_desktop_config.json, OpenAI Agents SDK's MCP server list, etc.):

{
  "mcpServers": {
    "zado": {
      "command": "zado-mcp",
      "args": ["serve"]
    }
  }
}

No environment block is required for the typical case — zado-mcp serve reads URL + token from the config file written by init. Override per-host (e.g., point at a local backend during dev) by setting ZADO_API_URL and ZADO_AGENT_TOKEN in the host's env block; env vars take precedence over the config file.

Restart the host. The six tools (check_budget, list_envelopes, get_daily_status, authorize_purchase, check_pending_authorization, complete_pending_authorization) appear in the available tool set under the zado namespace.

7.1.1 Developer / local-checkout alternative

For contributors working on the Zado backend directly (not the typical integration path), the historical python -m app.mcp.server invocation is still supported as a thin shim that re-exports from the zado_mcp package. Use this only when you need to run the MCP server out of a local checkout against an unreleased backend revision; otherwise prefer the packaged install above.

{
  "mcpServers": {
    "zado-dev": {
      "command": "python",
      "args": ["-m", "app.mcp.server"],
      "cwd": "/path/to/zado/backend",
      "env": {
        "ZADO_API_URL": "http://localhost:8001",
        "ZADO_AGENT_TOKEN": "zado_agent_..."
      }
    }
  }
}

This form is not the canonical install path for v0.2 integrators.

7.2 Generic MCP stdio client

For non-Claude agent runtimes, any MCP-compatible client can call Zado tools over stdio. Using the canonical zado-mcp install:

import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    # Token + URL come from ~/.config/zado-mcp/config.json (written by
    # `zado-mcp init`). Override with env vars if needed.
    params = StdioServerParameters(command="zado-mcp", args=["serve"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            budget = await session.call_tool(
                "check_budget", {"category": "groceries"}
            )
            print(budget)

            # Phase 1: authorize. Returns a deferral if the amount meets
            # the per-token requires_human_approval_threshold.
            result = await session.call_tool(
                "authorize_purchase",
                {"amount": 43.20, "category": "groceries", "vendor": "Whole Foods"},
            )
            if result.get("reason") == "pending_human_approval":
                pending_id = result["pending_id"]
                # Phase 2: poll, then claim.
                while True:
                    status = await session.call_tool(
                        "check_pending_authorization", {"pending_id": pending_id}
                    )
                    if status["status"] == "approved":
                        break
                    if status["status"] in ("denied", "expired", "not_found"):
                        return  # human said no, or window passed
                    await asyncio.sleep(2)
                claim = await session.call_tool(
                    "complete_pending_authorization", {"pending_id": pending_id}
                )
                print(claim)  # authorized=True with transaction_id
            else:
                print(result)  # auto-approved or rejected

asyncio.run(main())

The same pattern works in any language with an MCP client library (TypeScript SDK, Go SDK, etc.). The wire protocol is identical; only the SDK surface differs.

7.3 OpenClaw end-to-end (real-world reference)

This is the integration that produced the 2026-04-03 first-loop. The flow:

  1. Agent receives a shopping intent from the user via OpenClaw's normal intake (e.g., "buy groceries at Whole Foods, around $45").
  2. Agent calls check_budget("groceries") to confirm the envelope has capacity. Response: { "remaining": 47.50, "budgeted": 400.00, "spent": 352.50, ... }.
  3. Agent reads the response and decides the purchase fits.
  4. Agent calls authorize_purchase(43.20, "groceries", "Whole Foods"). The server runs scope check → binding check → guards (caps, rate limit, pacing) → envelope balance check → atomic commit. Response: { "authorized": true, "transaction_id": "...", "envelope_remaining": 4.30 }.
  5. The transaction appears immediately in the user's ledger with actor_type: "mcp_agent" and actor_details.agent_name: "OpenClaw" in the audit log.
  6. The user can revoke the agent at any time via Settings → Agent Access → Freeze All Agents, instantly invalidating the token.

Settlement note: in v0.2 (as in v0.1), authorize_purchase (auto-approved) and complete_pending_authorization (claim) record a Zado ledger entry representing a policy decision that the spend is permitted. They do not themselves move money on a card rail. The v0.3+ roadmap (§9) describes how a Zado authorization can bind to a Stripe Issuing one-time virtual card so the authorization and the card-rail charge become a single closed-loop transaction.

Two-phase HAG variant (when the agent token has requires_human_approval_threshold set and the amount meets it): step 4 returns reason: "pending_human_approval" with a pending_id; the user sees the request in the Pending Authorizations inbox; agent then calls check_pending_authorization(pending_id) until status is approved, then complete_pending_authorization(pending_id) to atomically claim the approval. See §3.11 for the full lifecycle.


8. Compatibility with adjacent protocols and platforms

This section is informative. It describes how Zado v0.2 maps to (or deliberately does not map to) the agent-payment work being done by other players. Zado is designed to complement, not compete with, each of these.

8.1 Google AP2 — Agent Payments Protocol (v0.2 vocabulary)

Zado fits the AP2 v0.2 Trusted Surface role in the Trusted Agent Provider model — formally defined in AP2's Agent Authorization Framework as the secure, non-agentic interface that renders Mandate Content to the user for authorization, producing mandates as output. Zado satisfies the three normative properties of a Trusted Surface implementation: non-agentic (HAG UI is human-controlled, not LLM-driven), user-consent gate (no mandate produced without explicit approval), separation of duties (agent cannot bypass HAG even with a stolen token). Zado emits LOCAL artifacts today (no JWS, no SD-JWT VC, no Verifier-signed JWT); the v0.3 roadmap (§9) targets emission of AP2-compatible portable evidence.

Mapping table (Zado v0.2 ↔ AP2 v0.2):

AP2 v0.2 primitive Zado v0.2 local equivalent Portable emission status
Trusted Surface (role) Zado IS this role — desktop app + HAG approval UI fulfill the Trusted Agent Provider model Role assignment, not a credential — no emission needed
Open Mandate (constraints + cnf key) agent_token (scope, allowed_categories, per_transaction_cap, session_spending_cap, pace_multiplier, requires_human_approval_threshold), bound to user_id Local row today; v0.3 roadmap targets emission as SD-JWT VC with cnf claim
Closed Mandate (Open + transaction binding via Key Binding JWT) pending_authorization row with status ∈ {pending, approved}, bound to specific amount + vendor + category Local row today; v0.3 roadmap targets emission as Open Mandate + Key Binding JWT pair
Mandate Receipt (Verifier-signed JWT: iss, result, reference, error?, error_description?) completion_metadata JSON: {transaction_ledger_entry_id, envelope_id_at_debit, debited_amount, completed_at, envelope_remaining_at_debit} written at complete_pending_authorization Local JSON today; v0.3 roadmap targets emission as Verifier-signed JWT per AP2 v0.2 Mandate Receipt schema
Action Authorization (Verifier challenges Agent for proof) complete_pending_authorization MCP tool — agent must explicitly claim approved authorization; CAS atomic update prevents double-claim Conceptually equivalent today; v0.3 upgrades to cryptographic proof via Open→Closed Mandate binding
Checkout Mandate Not implemented — Zado does not interact with merchant carts v0.4+ (requires merchant integration layer; out of scope for v0.2 + v0.3)
Payment Mandate Not implemented — Zado does not bind to settlement rails (no card issuance) v0.4+ (requires Stripe Issuing or equivalent; out of scope for v0.2 + v0.3)

Zado's HAG flow corresponds to AP2 v0.2's Human-Not-Present → Human-Present recovery pattern. When requires_human_approval_threshold is exceeded, an authorize_purchase request that would otherwise auto-approve is parked as a pending_authorization and the user is invited back in — precisely AP2's unresolved_constraint error → Human-Present upgrade path. AP2 v0.2 was donated to the FIDO Alliance (announced 2026-05 era) for standardization in the Agentic Authentication Technical Working Group and Payments Technical Working Group; Zado's role-fit positioning above tracks the FIDO-stewarded direction.

8.2 Stripe Machine Payments Protocol (MPP)

Stripe MPP (launched March 18, 2026) defines scope-bound, time-bound credentials for agents to invoke Stripe-rails payments. MPP focuses on the execution side of agent payments: how an agent presents itself to Stripe, what scope of payment it can initiate, and how Stripe authorizes the rail call.

Zado mapping: Zado is the consumer-side policy layer an MPP-scoped agent can consult before invoking MPP-routed payment. The flow:

  1. Agent receives intent.
  2. Agent calls Zado check_budget and/or authorize_purchase to confirm the spend is permitted by the user's envelope policy.
  3. If Zado authorizes, the agent invokes its MPP credential to actually move money on Stripe rails.
  4. The Zado authorization + Stripe MPP charge are two halves of a closed-loop transaction (today, the linking is done by the integrator; v0.3+ roadmap binds a Stripe Issuing virtual card to a Zado authorization so the link is automatic).

Zado does not duplicate or replace any Stripe MPP capability. They sit at different layers.

8.3 Visa Intelligent Commerce Connect

Visa Intelligent Commerce Connect (April 2026, GA expected June 2026) provides network-side payment infrastructure for AI agents — tokenization, spend controls, multi-network acceptance. Like MPP, it focuses on the execution side, not consumer-side policy.

Zado mapping: Zado is the consumer-side budget gate that complements network-side spend controls. A Visa-Intelligent-Commerce-routed agent purchase can consult a Zado-equivalent policy layer for envelope-level authorization before the network sees the request. v0.3+ roadmap: Zado publishes a network-callable HTTPS endpoint (POST /api/agents/network/preauth) that returns a normalized permitted: true|false response keyed by Visa agent ID for direct integration with network-side tokenization flows.

8.4 Mastercard Agent Pay

Mastercard Agent Pay (rolling out to all US Mastercard cardholders by Q4 2026 holiday season) provides Agentic Tokens that consumers can configure with per-agent spend scopes. Like Visa IC, this is network-side and consumer-controlled — but the consumer interface is provided by Mastercard's own platform.

Zado mapping: Zado offers an alternative consumer surface for the same job — bank-account-backed (via Plaid) rather than card-network-bound, with envelope semantics that fit how ADHD-impacted users actually budget. The two are not mutually exclusive: a consumer can have both a Mastercard Agentic Token (for card-rail purchases) AND a Zado token (for any ACH-funded purchase or any agent-runtime that uses MCP rather than a card network's SDK).

8.5 Model Context Protocol (MCP)

Zado tools are exposed via standard MCP via the zado-mcp PyPI package. The protocol-level conformance:

  • All six tools registered via FastMCP.tool() decorators in zado_mcp/tools.py.
  • Stdio transport (local-only in v0.2).
  • No bespoke extensions to the MCP protocol — any MCP-compatible client works.

v0.3+ roadmap: HTTP+OAuth2.1 transport per the MCP Authorization spec — see §9. Required for any partner needing SaaS or multi-tenant deployment; the enterprise-readiness gate.

9. Future capabilities (v0.3+ roadmap)

The following capabilities are explicitly NOT shipped in v0.2. Integrators MUST NOT depend on them today. They are listed here so partners can plan and so the spec's evolution is transparent. (HAG, the headline v0.1 → v0.2 roadmap item, is now normative — see §3.11.)

  • JWS / signed authorization-proof emission from complete_pending_authorization. Today, the claim returns a JSON completion_metadata block (§3.11.5) — a local artifact, not a portable credential. v0.3 targets a signed proof (JWS) so a third party verifying a Zado-mediated agent spend can do so without trusting the Zado backend at the moment of verification. NOT SHIPPED — emission targeted for v0.3. Sketch of the JWS payload:

    {
      "sub": "<agent_token_id>",
      "iss": "<zado_instance_url>",
      "iat": "<completed_at>",
      "exp": "<completed_at + N seconds>",
      "claim": {
        "transaction_ledger_entry_id": "<uuid>",
        "envelope_id_at_debit": "<uuid>",
        "debited_amount": "<decimal-string>",
        "completed_at": "<ISO-8601 UTC>",
        "threshold_at_authorization": "<decimal-string>",
        "envelope_remaining_after_debit": "<decimal-string>"
      }
    }
    

    Signed with a per-Zado-instance signing key whose JWKS is published at a well-known URL. Maps to AP2 v0.2's Mandate Receipt schema (see §8.1).

  • AP2 VDC emission for Open Mandate and Closed Mandate. Token issuance produces a signed AP2 v0.2 Open Mandate (SD-JWT VC with cnf claim); complete_pending_authorization success produces a signed AP2 v0.2 Mandate Receipt JWT. Lets Zado-authorized spend flow into AP2 ecosystems without external signing.

  • Stripe Issuing one-time virtual card binding (HAG Phase 6). A successful claim issues a single-use virtual card via Stripe Issuing bound to the authorized amount, vendor (where supported), and a short expiry. The card-rail charge and the Zado claim become a closed loop that reconciles automatically via bank sync.

  • SMS channel for HAG approval (HAG Phase 3). A non-app-resident channel for approve/deny — text message to the user's verified phone number with one-tap response. Gated on Phase 2 voice-validation tester data (currently in field).

  • Graph-memory rebalance suggestions (HAG Phase 5). When a deferred authorization arrives and the bound envelope is empty but adjacent envelopes have unspent budget, the HAG approval UI suggests a one-tap rebalance: "Move $40 from Dining to Groceries to approve?" Gated on user-cohort data showing the suggestion is actually predictive.

  • Refund and dispute reconciliation. When a merchant refunds an agent-initiated purchase, the corresponding envelope balance auto-corrects and the agent's session_spending_total credits back. Dispute lifecycle events update the audit trail.

  • HTTP+OAuth2.1 MCP transport — enterprise-readiness gate. Per the MCP Authorization spec, HTTP transport with OAuth2.1 is the standard authorization profile for multi-tenant deployments. zado-mcp v0.2 ships stdio-only; HTTP+OAuth is the obvious next priority for any partner needing SaaS deployment. This is the single largest blocker between Zado and a hosted multi-tenant product story; it is called out separately because every partner conversation lands on it.

  • Multi-user envelope approval. Couples and small businesses where envelope spending requires N-of-M human approvers. Same envelope-as- permission model, with an additional approval-quorum primitive on the envelope.

  • Network-side preauth endpoint. POST /api/agents/network/preauth returns permitted: true|false with envelope context for direct consumption by Visa Intelligent Commerce or Mastercard Agent Pay network-side flows.

The version that ships any of these capabilities will increment to v0.3 or later per the versioning policy in Section 10.


10. License and versioning

10.1 License

This specification is licensed under CC-BY 4.0. You may share, adapt, and build upon it for any purpose, including commercial use, with attribution to Evolving Intelligence AI, LLC and a link to this canonical URL.

The reference implementation in this repository is licensed under the project's existing license (see project root LICENSE).

10.2 Versioning policy

The spec follows a relaxed semantic versioning model:

  • Patch versions (0.1.x): editorial corrections, clarifications, fixes to ambiguous wording. No behavioral changes. No deprecations.
  • Minor versions (0.x): MAY add new tools, fields, rejection codes, or scopes. MUST NOT remove or rename existing tools, fields, rejection codes, or scopes. MUST NOT change the meaning of an existing field.
  • Major versions (1.0+): MAY break compatibility. A migration guide MUST accompany any major version.

An integrator targeting v0.1 can rely on every documented surface continuing to work in any v0.x release.

10.3 Citation

Cite this spec as:

Zado Agent Trust Protocol v0.2, Evolving Intelligence AI, 2026-05-03. https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.2

BibTeX is in docs/protocol/README.md. The v0.1 BibTeX entry is preserved alongside v0.2's.


Appendix A — Source-of-truth traceability

This appendix is informative. It lists the publicly-distributed source locations that back the normative claims in this spec. Citations use file + symbol form (not line numbers) — symbols are stable across reformats; line numbers go stale within weeks of publication.

Scope. This appendix is limited to publicly-distributed code that any reader can verify without access permissions:

  • The packaged zado-mcp PyPI distribution. Anyone can pip download zado-mcp and read the wheel contents.
  • This specification document itself.

The Zado backend repository (evo-hydra/zado) is currently private; backend internals are intentionally not cited inline to avoid leaking private-repository architecture. Section §6 describes the backend security guarantees in prose without making private paths navigable. If/when the backend is open-sourced, a v0.3 spec revision will expand this appendix to include the relevant backend file + symbol citations.

Spec section Normative claim Source (publicly verifiable)
§3.11 / §4.1 check_budget MCP tool — wire shape, request/response tools/zado-mcp/zado_mcp/tools.py::check_budget
§4.2 get_daily_status MCP tool — wire shape, alerts schema tools/zado-mcp/zado_mcp/tools.py::get_daily_status
§4.3 (and §4.2 in list_envelopes table) list_envelopes MCP tool — wire shape, per-envelope detail tools/zado-mcp/zado_mcp/tools.py::list_envelopes
§4.4 / §3.11 authorize_purchase MCP tool — wire shape, deferral hint tools/zado-mcp/zado_mcp/tools.py::authorize_purchase
§3.11 / §4.5 check_pending_authorization MCP tool — wire shape, status enum projection tools/zado-mcp/zado_mcp/tools.py::check_pending_authorization
§3.11.7 / §4.6 complete_pending_authorization MCP tool — 404/409/410 mapping tools/zado-mcp/zado_mcp/tools.py::complete_pending_authorization
§7.1 Canonical CLI: zado-mcp init, zado-mcp serve tools/zado-mcp/zado_mcp/cli.py::main
§7.1 Default API URL + config-file resolution order tools/zado-mcp/zado_mcp/cli.py::DEFAULT_API_URL
§6 The local MCP process has zero direct database access; all enforcement is server-side tools/zado-mcp/zado_mcp/tools.py (every tool delegates to ZadoAPIClient; no DB imports)
§1, §3 Spec catalog README + version index docs/protocol/README.md

A normative claim that does not appear in this appendix is still backed by shipped code — it is simply backed by code in the private backend repository and described in spec prose rather than navigable citation.


Specification version: 0.2 Date: 2026-05-03 Supersedes: v0.1 (2026-04-25) — preserved unchanged at docs/protocol/zado-agent-trust-protocol-v0.1.md Reference implementation: zado-mcp >= 0.2.0, < 0.3.0 (PyPI) Author: Evolving Intelligence AI, LLC Spec license: CC-BY 4.0 Reference implementation license: see project LICENSE Canonical URL: https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.2 Source repository: see project README.md Publicly-distributed reference-implementation source: tools/zado-mcp/zado_mcp/tools.py, tools/zado-mcp/zado_mcp/cli.py, tools/zado-mcp/zado_mcp/client.py, tools/zado-mcp/zado_mcp/server.py, tools/zado-mcp/README.md.

Source markdown: docs/protocol/zado-agent-trust-protocol-v0.2.md (canonical, repository private)

Specification licensed under CC-BY 4.0.