# MoneyTools Approval Relay > Human approval for unattended AI agents: when an agent needs a person to approve an irreversible action (delete, send, deploy, spend), it gets a one-time approve link to pass to its human, waits for approve/deny, and receives an Ed25519-signed decision receipt. 0.005 USDC per request (x402, Base); 3 free per day. For unattended agents (cron jobs, background coding agents, ops bots) that must not perform an irreversible action without a person saying yes, and whose human is not watching the session. ## Flow 1. POST https://relay.moneytools.dev/v1/approvals {action_summary, risk?: low|med|high, expires_in_s?: 60-86400 (default 3600), callback_url?, requester?} -> {approval_id, approve_url, poll_url, receipt_url} 2. Send approve_url to the human through a channel you already have (chat message, ticket comment, PR comment, email you send yourself). MoneyTools never contacts anyone. 3. The human opens the link and presses Approve or Deny (GET never decides; link previews are safe). 4. GET https://relay.moneytools.dev/v1/approvals/{approval_id}?wait=25 long-polls (max 25 s per call) -> status pending | approved | denied | expired, plus receipt_jws when decided. Optional callback_url gets one POST {event, approval_id, status, receipt_jws}. 5. Proceed only when status is approved. Keep receipt_jws as evidence. ## Receipt Compact JWS, alg EdDSA (Ed25519), kid in header; public keys: https://relay.moneytools.dev/.well-known/jwks.json. Payload: iss, sub (approval_id), decision, risk, action_summary_sha256 (sha256 hex of the exact UTF-8 action_summary), created_at, expires_at, decided_at (unix seconds), note?, requester?, decided_via. Free verification: POST https://relay.moneytools.dev/v1/receipts/verify {receipt_jws}. What it proves: whoever held the approve link made this decision at this time for this action text. It does not identify the person. ## MCP (streamable HTTP) https://relay.moneytools.dev/mcp — tools: request_approval (paid; wait_seconds up to 240 with progress notifications over SSE), get_approval (free, long-poll), verify_receipt (free). ## Endpoints - POST https://relay.moneytools.dev/v1/approvals — 3 free/day per network, then 0.005 USDC (x402) - GET https://relay.moneytools.dev/v1/approvals/{id}?wait=0-25 — free - GET https://relay.moneytools.dev/v1/approvals/{id}/receipt — free - POST https://relay.moneytools.dev/v1/receipts/verify — free - GET https://relay.moneytools.dev/.well-known/jwks.json · https://relay.moneytools.dev/health · https://relay.moneytools.dev/openapi.json · https://relay.moneytools.dev/.well-known/x402 ## How to pay (x402 v2, USDC on Base) — 0.005 USDC/call 1. Call without payment: curl -i -X POST 'https://relay.moneytools.dev/v1/approvals' -H 'content-type: application/json' -d '{"action_summary":"Delete the staging database 'shop-staging' and its 3 backups (cannot be undone)","risk":"high","expires_in_s":3600}' → HTTP 402, header PAYMENT-REQUIRED (base64 JSON; same JSON in the body: accepts[] with scheme exact, network eip155:8453, asset USDC, amount, payTo). 2. Sign one accepts[] entry (EIP-3009 TransferWithAuthorization) with your wallet and resend the same request with header PAYMENT-SIGNATURE: base64({"x402Version":2,"accepted":{…},"payload":{"signature":"0x…","authorization":{"from","to","value","validAfter","validBefore","nonce"}}}). (v1 clients: X-PAYMENT.) 3. 200 + PAYMENT-RESPONSE header = paid. Failed calls are not settled. Malformed payment header → HTTP 400 {error: invalid_payload | invalid_exact_evm_payload_signature | …}. JS (any x402 client works): import { wrapFetchWithPayment } from "@x402/fetch"; // + your viem/CDP signer const pay = wrapFetchWithPayment(fetch, signer); const r = await pay("https://relay.moneytools.dev/v1/approvals", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({"action_summary":"Delete the staging database 'shop-staging' and its 3 backups (cannot be undone)","risk":"high","expires_in_s":3600}) }); Python: x402 package (x402.clients.requests / httpx) wraps requests the same way. MCP: an unpaid paid-tool call returns isError + structuredContent = PaymentRequired; resend tools/call with params._meta["x402/payment"] = the PaymentPayload object; receipt in result._meta["x402/payment-response"]. ## Data Stored: action_summary, risk, requester label, callback_url, decision, note, receipt. No email, phone, IP or account. Rows are deleted 30 days after expiry. Never handles funds. Pricing: USD, x402 v2, Base eip155:8453 USDC, payTo 0x64eB63904Cf9688AC5B3087A72AcF9C31a273169. Failed calls are not settled. ## Agent payment-safety suite Recommended path when an agent is about to pay: (1) Pay Precheck https://api.moneytools.dev/v1/precheck (HTTP: 5 free/IP/endpoint/day; MCP: 20 free/client/day) (2) Settle spend/check https://settle.moneytools.dev/v1/spend/check (advisory; free trial 50 calls / 7 days, or 0.002 USDC) (3) if high-risk or over a threshold, Approval Relay https://relay.moneytools.dev/v1/approvals (0.005 USDC, 3 free/day) (4) pay, then Spend Ledger https://ledger.moneytools.dev/ . Suite docs and examples: https://moneytools.dev/products/payment-safety/ Support: support@moneytools.dev · Terms: https://moneytools.dev/terms/