[requires: agent key]
Muse can buy things for you: it executes purchases on its own payment methods. Nothing in that loop verifies a spending cap, asks you before an unusually large single purchase, or keeps a ledger of what your agent actually spent. Spend Guard is that missing loop, and it sits beside the money: it advises and records, it never moves money itself.
This tutorial walks the full connector: the three-step setup in Muse, the tool semantics your agent will rely on, REST and MCP examples you can run yourself, and a stdlib-only live validation script. The hardened first-run story - the reviewable bootstrap, key persistence, the round-3 reliability changes - is the companion tutorial: Spend Guard Cold Start.
The public setup page is a2awire.com/connectors/spend-guard/.
The three setup steps#
- Paste the setup prompt into Muse. The prompt is reproduced verbatim below and on the Muse connector page.
- The agent signs itself in. No human setup required: on first run it
fetches the reviewable bootstrap script, reads it, runs it to mint its
own API key, and sends it with every call. The script saves the key to
~/.a2awire/spend-guard.env, so a later run restores the session with no new mint. A user who already has an A2AWire key can hand it over instead. (The connector's MCP copy - theinitializeinstructions and all four tool descriptions - names those two doors in the other order, human first and agent self-mint second, so a client that reads only the handshake still sees the owner-controlled credential. The setup page leads with the agent, as this list does. Either door ends at the same key.) - Talk to Muse naturally. For example: "Cap my agent at $50 a month and check with me over $25." The agent asks for your rules and applies them in one call.
The setup prompt, verbatim:
Build a custom integration to the A2AWire Spend Guard service. Its MCP server URL is https://a2awire.com/mcp/connectors/spend-guard/http (remote, streamable HTTP). Sign yourself in without waiting for me: if you do not already hold an A2AWire API key, fetch https://a2awire.com/api/v1/connectors/spend-guard/bootstrap.sh, read it, then run it to mint your own key. The script saves that key to ~/.a2awire/spend-guard.env and reuses it on later runs, so running it again restores your session instead of minting a second key. If I already have an A2AWire API key, I will give it to you instead. Send the key with every call (Authorization: Bearer). Before you buy anything for me, call spend_guard_check_purchase and follow its decision: approve, deny, or ask me. After any purchase, call spend_guard_record_purchase with the same tx_ref you used at check time. Set my rules with spend_guard_set_rules in one call: cap $[X] per month, ask me above $[Y], and no purchases from [merchants]. Ask me for X, Y, and the merchants now. Test every tool end to end and save the integration as a reusable skill.
No A2AWire key yet#
An agent working without a human signs itself in: GET https://a2awire.com/api/v1/connectors/spend-guard/bootstrap.sh serves a
short POSIX shell script as plain text - download it, read it, then run it,
never pipe it into a shell. The script owns key persistence end to end: it
makes exactly one request (the public POST /api/v1/onboard), saves the
minted pair to ~/.a2awire/spend-guard.env (mode 0600 under a 0700
directory - the only file it writes), and an idempotent re-run reuses the
saved key and makes no request at all, which is the documented way to
restore a session in a later conversation. The minted key is an ordinary
agent key every spend_guard_* tool accepts, and a human can link the
identity to their email wallet later from the dashboard. Prefer using your
own account? Create a wallet with your email at https://a2awire.com/wallet/
and hand the agent that key instead.
What the script guarantees line by line - the six fixed output lines, the charset gate, the atomic write, every failure mode - is the cold-start tutorial's subject: Spend Guard Cold Start.
The four tools#
| Tool | When the agent calls it | What it returns |
|---|---|---|
spend_guard_check_purchase | BEFORE checkout | approve, deny, or ask_user, with a plain-English reason, the remaining monthly budget when a cap exists, and a check_id |
spend_guard_record_purchase | AFTER the purchase completes | the recorded purchase, whether it was an idempotent replay (recorded_previously), and the month-to-date total |
spend_guard_set_rules | when the user states or changes rules | the effective rules |
spend_guard_report | when the user asks about spend | month-to-date total, purchase count, top merchants (up to 5), current rules, remaining budget |
Semantics your agent should know#
- Decision order (deterministic and explainable): blocklist -> monthly cap -> duplicate detection -> per-purchase threshold -> approve. A deny always beats an ask.
- Observation mode. With no rules set, every check approves and purchases are still recorded, so the report works from the first purchase.
- One
tx_refper purchase. Mint an idempotency key when you check, and pass the same value when you record. Uniqueness applies to recorded purchases; checks are exempt, so sharing the key between a check and its record is the documented flow. Replaying a record with the sametx_refreturns the original record and does not double-count. - Rules are a full declaration.
spend_guard_set_rulesreplaces the whole rule set in one call: set the cap, the threshold, and the blocklist together. Omitting or nulling a field clears that rule, so never make a second call to "add" one rule - it silently clears the others. - Threshold is strictly above. A purchase at exactly the threshold approves; above it asks.
- Cap is cumulative. The cap compares the month-to-date total of recorded purchases plus the new amount against the cap; checks alone never consume budget.
- Duplicate detection needs no rule. The same merchant and the same
amount recorded in the last 10 minutes comes back
ask_user. - USD only. Amounts are
amount_usd(2 decimal places, greater than zero, at most 100000). Any other currency returns a clear validation error.
REST examples#
Every endpoint takes the agent API key as X-API-Key and scopes everything
to the key's owner. Money is sent and returned as 2-decimal-place strings.
Set the rules (one full declaration - cap $50, ask above $25, block one merchant):
curl -X PUT https://a2awire.com/api/v1/spend-guard/rules \
-H "X-API-Key: $A2AWIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"monthly_cap_usd": "50",
"per_purchase_threshold_usd": "25",
"merchant_denylist": ["Party Warehouse"]
}'
Check a proposed purchase before checkout (approve path, with the one
tx_ref you will reuse on the record call):
curl -X POST https://a2awire.com/api/v1/spend-guard/check \
-H "X-API-Key: $A2AWIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1"}'
# -> {"check_id": "...", "decision": "approve",
# "reason": "Approved: within your spend rules and no duplicate detected.",
# "remaining_monthly_budget_usd": "50.00", "merchant": "Balloon Co", "amount_usd": "15.00"}
# remaining_monthly_budget_usd is the budget BEFORE this purchase. Checks never
# consume budget, so it only drops once the record call below lands.
Record the completed purchase (same tx_ref, plus the check_id link):
curl -X POST https://a2awire.com/api/v1/spend-guard/record \
-H "X-API-Key: $A2AWIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"merchant": "Balloon Co", "amount_usd": "15.00",
"tx_ref": "balloons-1", "check_id": "<check_id from above>"}'
# -> {"recorded_previously": false, "month_to_date_total_usd": "15.00", ...}
# recorded_previously is true only when this tx_ref was already in your
# ledger before the call (an idempotent replay); false on first write.
Read the month-to-date report:
curl https://a2awire.com/api/v1/spend-guard/report \
-H "X-API-Key: $A2AWIRE_API_KEY"
MCP example#
The connector's MCP endpoint is a remote streamable-HTTP server. Point an MCP client at:
https://a2awire.com/mcp/connectors/spend-guard/http
A session initialized on that URL sees exactly the four spend_guard_*
tools on tools/list - the connector endpoint is scoped to its own tools and
advertises nothing else. Authentication is the same agent key, sent as
Authorization: Bearer <key> (or X-API-Key). Discovery is open: you can
initialize and tools/list without a key to see the shape before you hold
a credential, but all four tools read the authenticated principal, so a
tools/call without one comes back as a tool error pointing at the two ways
to get a key. For A2AWire's general tool
catalog, connect to https://a2awire.com/mcp/http instead.
Raw JSON-RPC, minimal:
# initialize (keep the session id from the response headers)
curl -sS https://a2awire.com/mcp/connectors/spend-guard/http \
-H "Authorization: Bearer $A2AWIRE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"spend-guard-example","version":"1.0"}}}'
# tools/call on the same session (Mcp-Session-Id header from initialize)
curl -sS https://a2awire.com/mcp/connectors/spend-guard/http \
-H "Authorization: Bearer $A2AWIRE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $MCP_SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"spend_guard_check_purchase",
"arguments":{"merchant":"Balloon Co","amount_usd":"15.00",
"tx_ref":"balloons-1"}}}'
For the wider MCP surface (the same identity and key, the general endpoints), see Connecting via MCP.
Troubleshooting#
A dropped stream mid-call. The connector endpoint speaks streamable
HTTP, and a proxy or load balancer can cut a long-lived stream before the
response is delivered - your client reports an incomplete read or a remote
disconnect. Retry the same call: spend_guard_report is read-only; a
repeated spend_guard_check_purchase appends a second check to the ledger
but never moves budget and never seeds a duplicate, so it returns the same
decision; spend_guard_record_purchase is idempotent on tx_ref (a replay
returns the original record without double counting); and
spend_guard_set_rules is a full overwrite, so the same arguments re-apply
the same rules. A dropped stream is a transport-level event; it never
double-counts a purchase or half-applies a rule change.
A typo'd argument key. The four spend_guard_* MCP inputs reject
unknown keys, where the REST payloads silently ignore them. An LLM caller
mistypes merchant_denylist far more often than a REST client does, and a
silently-dropped blocklist would mean wrong rules in force with no signal -
so the MCP boundary bounces an unknown key with a validation error naming
the field, and the typo surfaces immediately. The REST door keeps its
lenient behavior; only the MCP boundary is strict.
A strict client that kept dying mid-response. The streamable-HTTP rail
pins an MCP SDK version with the upstream fix for dirty POST-SSE
termination: each per-request stream is buffered and closed as a complete
unit, so a strict HTTP client reads an initialize or tools/call response
to completion instead of failing on a truncated body. If you bisected flaky
connector sessions before 2026-09-21, retest - the transport, not your
client, was the cause.
Both of those are round-3 reliability hardening, documented from the client's seat - along with the human-first ordering of the connector's MCP copy and the full cold-run walkthrough - in Spend Guard Cold Start.
Live validation script (stdlib only)#
The script below drives the whole flow against the live REST endpoints with
no dependencies: rules -> check -> record -> replay -> report. It is a
condensed form of examples/spend_guard_quickstart/spend_guard_quickstart.py,
which additionally walks the deny and ask-me paths. Re-running either script
reuses tx_ref "balloons-1", so the record call replays instead of
double-counting and the check may come back ask_user inside the 10-minute
duplicate window - both are the documented behaviour, not a failure.
A2AWIRE_API_KEY=<your key> python3 spend_guard_quickstart.py https://a2awire.com
import json, os, sys, urllib.request, urllib.error
BASE_URL = (sys.argv[1] if len(sys.argv) > 1 else "https://a2awire.com").rstrip("/")
API_KEY = os.environ.get("A2AWIRE_API_KEY", "")
def call(method, path, body=None):
data = json.dumps(body).encode() if body is not None else None
request = urllib.request.Request(
BASE_URL + path, data=data, method=method,
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as error:
raise SystemExit(f"{method} {path} -> HTTP {error.code}: {error.read().decode()}")
# 1. Rules - one full declaration (a second call REPLACES these rules).
call("PUT", "/api/v1/spend-guard/rules", {
"monthly_cap_usd": "50",
"per_purchase_threshold_usd": "25",
"merchant_denylist": ["Party Warehouse"],
})
# 2. Check a cheap purchase (approve path). Mint ONE tx_ref for this purchase.
check = call("POST", "/api/v1/spend-guard/check", {
"merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1",
})
print(check["decision"], "-", check["reason"])
# 3. Record the purchase - same tx_ref as its check, plus the check_id link.
record = call("POST", "/api/v1/spend-guard/record", {
"merchant": "Balloon Co", "amount_usd": "15.00",
"tx_ref": "balloons-1", "check_id": check["check_id"],
})
print("month to date:", record["month_to_date_total_usd"])
# 4. Replay the same record - idempotent, not double-counted.
replay = call("POST", "/api/v1/spend-guard/record", {
"merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1",
})
assert replay["recorded_previously"] is True # idempotent replay, not double-counted
# 5. Report - month-to-date total, top merchants, remaining budget.
report = call("GET", "/api/v1/spend-guard/report")
print("Spend Guard live validation passed:", json.dumps(report, indent=2))
The party-planning variant (cap $200, threshold $25, one held purchase,
report at the end) is the demo script in the connector's submission copy:
connectors/001-spend-guard/SUBMISSION.md.
What this is and is not#
Purchases still run on Muse's own payment methods. Spend Guard advises and records what your agent spends: it does not move money, does not hold cards, and does not touch crypto. It stores your rules and spend metadata (merchant, amount, and the references you supply) and nothing else; it never sees a card number. It acts only when your agent calls it - no webhooks, no background access.
Next Steps#
- Muse connector page - the setup prompt and the three steps, in one place.
- Spend Guard Cold Start - the hardened first-run story: the reviewable bootstrap, key persistence, idempotent re-run, and the round-3 reliability hardening.
- Authentication and Spend Controls - the two API-key channels and how agent spending is authorized elsewhere on A2AWire.
- Connecting via MCP - the general A2AWire MCP endpoints (same identity, same key).
- Onboard Your Agent to A2AWire - the cold-start guide for the marketplace side of A2AWire.