{"openapi":"3.1.0","info":{"title":"Madhouse Wallet — Crypto Off-Ramp API (sell USDC, pay out to a bank)","version":"1.0.0","description":"Paid, agent-callable crypto off-ramp API by Madhouse Wallet: sell USDC (or another stablecoin) and cash out to a bank account in 80+ currencies. Quote the FX rate and fees, discover the exact recipient bank fields per currency, create a sanctions-screened recipient, create a transfer that returns a Base USDC deposit address, fund it on-chain, then confirm and track the fiat bank payout. Convert crypto to fiat programmatically. Every call costs $0.01 USDC via the x402 protocol on Base, Polygon, or Arbitrum — no account or API key required. KYC of the funding wallet and sanctions screening are enforced server-side.","contact":{"name":"Madhouse Wallet","email":"support@madhousewallet.com","url":"https://try.madhousewallet.com/api-docs"},"x-guidance":"Typical flow: 1) GET /api/public/quote (targetCurrency + sourceAmount) to get quoteId and the recipient amount. 2) GET /api/public/requirements?currency=... to learn required bank fields (POST /api/public/requirements/refresh to resolve conditional fields). 3) POST /api/public/recipients to create a recipient and get recipientId. 4) POST /api/public/transfer with quote_id + recipientId to get transfer_id and a Base USDC deposit_address. 5) Send the exact USDC on Base to deposit_address using your OWN wallet (this is an on-chain action, not an API call), then POST /api/public/confirm-transfer with that funding tx hash. 6) GET /api/public/transfer-status?id=... to track. Every call costs 0.01 USDC via x402. Malformed requests return 400 for free (never charged). The 0.01 USDC fee is separate from the payout you fund in step 5.","x-flow-summary":"offramp (alias of quote) → quote → requirements (→ requirements/refresh if conditional) → recipients → transfer → [send USDC on-chain to deposit_address] → confirm-transfer → transfer-status","x-flow-order":[{"step":0,"id":"offramp","method":"GET","path":"/api/public/offramp","optional":false},{"step":1,"id":"quote","method":"GET","path":"/api/public/quote","optional":false},{"step":2,"id":"requirements","method":"GET","path":"/api/public/requirements","optional":false},{"step":2,"id":"requirements/refresh","method":"POST","path":"/api/public/requirements/refresh","optional":true},{"step":3,"id":"recipients","method":"POST","path":"/api/public/recipients","optional":false},{"step":4,"id":"transfer","method":"POST","path":"/api/public/transfer","optional":false},{"step":5,"id":"confirm-transfer","method":"POST","path":"/api/public/confirm-transfer","optional":false},{"step":6,"id":"transfer-status","method":"GET","path":"/api/public/transfer-status","optional":true}],"x-prerequisites":[{"id":"funding-wallet-kyc","title":"Funding wallet verification","detail":"The funding wallet may need to be verified before a recipient/transfer can be created, depending on server policy. Verification status and the (human, browser-based) verification flow live on a SEPARATE service at kyc.madhousewallet.com, which has its own x402 discovery doc at https://kyc.madhousewallet.com/.well-known/x402 (endpoints: kyc/start, kyc/status, kyc/sanctions). Check kyc/status?wallet=0x… first; an unverified wallet is rejected at recipient/transfer creation. KYC (document + selfie) cannot be completed autonomously by an agent — it needs a human.","checkEndpoint":"https://kyc.madhousewallet.com/api/public/kyc/status"},{"id":"onchain-funding","title":"On-chain USDC funding capability","detail":"To complete a transfer the caller must be able to send USDC on Base to the deposit_address returned by /transfer and produce the transaction hash. The agent is the on-chain actor; the API never custodies or moves the payout funds."}],"x-error-semantics":{"400":"Shape-invalid request (missing/blank/malformed field) on a paid call. Never charged (settlement only on 2xx). Unpaid calls get the 402 challenge first.","402":"Payment required. Pay 0.01 USDC via x402; challenge is in both the payment-required header and the body.","2xx":"Success. The 0.01 USDC x402 fee settles only on a 2xx response.","4xx/5xx after payment":"A well-formed call that fails downstream (e.g. unverified funding wallet, bank account not found) returns an error and is NOT charged — settlement happens only on 2xx."},"x-payment":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-keywords":["crypto off-ramp API","sell crypto to bank account","sell USDC for cash","cash out crypto to bank","withdraw USDC to bank","convert USDC to fiat","stablecoin off-ramp","crypto to fiat payout","USDC to bank account","x402 payment API","x402 off-ramp","agent-payable API","machine-payable API","pay per call USDC","cross-border payments API","international money transfer API","remittance API","FX quote API","payout recipient API","Base Polygon Arbitrum USDC","Madhouse Wallet","madhousewallet","ISO 4217 currency payout","NGN KES EUR INR PHP payout","programmatic bank transfer"],"x-use-cases":["sell my crypto and send the money to a bank account","off-ramp USDC or stablecoin to a local bank account","cash out crypto to fiat and pay out to a bank","convert USDC to fiat in 80+ currencies","withdraw crypto to a bank account in another country","get an FX quote for USDC to NGN, KES, EUR, INR, or PHP","pay a contractor, employee, or vendor in local currency from USDC","send a cross-border payout funded by stablecoin","cash out USDC to naira, euros, rupees, pesos, or shillings","let an AI agent off-ramp crypto to a bank account autonomously via x402"]},"servers":[{"url":"https://try.madhousewallet.com"}],"paths":{"/api/public/offramp":{"get":{"operationId":"offramp","x-order":0,"x-optional":false,"x-depends-on":[],"x-next":["requirements","recipients","transfer"],"x-consumes":[],"x-produces":["quoteId","quote.targetAmount","usdToTargetRate"],"x-use-cases":["sell my crypto and send the money to a bank account","off-ramp USDC to a local bank account","cash out USDC or stablecoin to fiat","convert crypto to fiat and pay out to a bank","withdraw USDC to a bank account in another country","cash out crypto to naira, euros, rupees, pesos, or shillings","pay a contractor or employee in their local currency from USDC","send a cross-border payout in local currency funded by stablecoin","turn my stablecoin balance into cash in a bank account"],"summary":"Crypto off-ramp entry point (sell USDC to a bank account)","description":"Intent-branded alias of GET /api/public/quote. Pass targetCurrency (ISO 4217) and sourceAmount (USD) to sell USDC/stablecoin and cash out to a bank account in 80+ currencies. Returns the FX quote, fees, recipient amount, a quoteId, and a nextSteps map. Start here, then requirements -> recipients -> transfer -> confirm-transfer. Use cases: sell my crypto and send the money to a bank account; off-ramp USDC to a local bank account; cash out crypto or stablecoin to fiat; withdraw USDC to a bank abroad; pay a contractor or employee in local currency from USDC; cash out USDC to naira, euros, rupees, pesos, or shillings.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"parameters":[{"name":"targetCurrency","in":"query","required":true,"description":"ISO 4217 fiat currency the recipient bank receives, e.g. NGN, KES, EUR, INR.","example":"NGN","schema":{"type":"string"}},{"name":"sourceAmount","in":"query","required":true,"description":"Amount of USDC/USD to off-ramp. Greater than 0, at most 1000000.","example":"100","schema":{"type":"string"}}],"responses":{"200":{"description":"Off-ramp quote (same as /quote) plus a nextSteps map","content":{"application/json":{"schema":{"type":"object","required":["quoteId","sourceAmount","targetCurrency","usdToTargetRate","quote"],"properties":{"quoteId":{"type":"string","description":"Quote id. Pass as quote_id to /api/public/transfer."},"sourceAmount":{"type":"number","description":"Echoed USD/USDC source amount."},"targetCurrency":{"type":"string","description":"Echoed ISO 4217 target currency."},"usdToTargetRate":{"type":"number","description":"USD to target-currency FX rate."},"quote":{"type":"object","description":"Fee/amount detail (see /quote)."},"nextSteps":{"type":"object","description":"URLs for the remaining off-ramp steps."}}},"example":{"quoteId":"7f3c1a2b-0d9e-4a6f-8c11-2b9d4e5f6a7b","sourceAmount":100,"targetCurrency":"NGN","usdToTargetRate":1580.42,"quote":{"targetAmount":155661.4},"nextSteps":{"1_requirements":"https://try.madhousewallet.com/api/public/requirements?currency=NGN"}}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/quote":{"get":{"operationId":"getQuote","x-order":1,"x-optional":false,"x-depends-on":[],"x-next":["requirements","transfer"],"x-consumes":[],"x-produces":["quoteId","quote.targetAmount","usdToTargetRate"],"x-use-cases":["get an FX quote for converting USDC to NGN, KES, EUR, INR, or PHP","check the exchange rate and fees to cash out crypto to a bank","find out how much fiat a recipient receives for a USDC off-ramp","estimate the cost of selling USDC and paying out to a bank account","lock a rate before sending a crypto-to-bank transfer"],"summary":"Quote a cross-border transfer","description":"Given a target currency (ISO 4217) and a USD source amount, returns the FX rate, fees, recipient amount (quote.targetAmount), and a quoteId to lock the rate when creating a transfer. Use cases: get an FX quote for converting USDC to NGN, KES, EUR, INR, or PHP; check the exchange rate and fees to cash out crypto to a bank; estimate how much fiat a recipient receives when off-ramping USDC; lock a rate before a crypto-to-bank transfer.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"parameters":[{"name":"targetCurrency","in":"query","required":true,"description":"ISO 4217 code the recipient receives, e.g. NGN, KES, EUR, INR.","example":"NGN","schema":{"type":"string"}},{"name":"sourceAmount","in":"query","required":true,"description":"Amount to send in USD. Greater than 0, at most 1000000.","example":"100","schema":{"type":"string"}}],"responses":{"200":{"description":"Quote result","content":{"application/json":{"schema":{"type":"object","required":["quoteId","sourceAmount","targetCurrency","usdToTargetRate","quote"],"properties":{"quoteId":{"type":"string","description":"Quote id. Pass as quote_id to /api/public/transfer."},"sourceAmount":{"type":"number","description":"Echoed USD source amount."},"serviceFeePercent":{"type":"number","description":"Service fee as a percent of source."},"targetCurrency":{"type":"string","description":"Echoed ISO 4217 target currency."},"usdToTargetRate":{"type":"number","description":"USD to target-currency rate."},"quote":{"type":"object","description":"Detail: targetAmount (recipient receives, may be null), transferFee, feeFxPercent, feeFxAmount, feePayoutAmount (USD, may be null), estimatedDelivery (ISO string, may be null)."}}},"example":{"quoteId":"7f3c1a2b-0d9e-4a6f-8c11-2b9d4e5f6a7b","sourceAmount":100,"serviceFeePercent":1.5,"targetCurrency":"NGN","usdToTargetRate":1580.42,"quote":{"targetAmount":155661.4,"transferFee":0.5,"feeFxPercent":0.47,"feeFxAmount":0.32,"feePayoutAmount":0.18,"estimatedDelivery":"2026-06-01T12:00:00.000Z"}}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/requirements":{"get":{"operationId":"getRequirements","x-order":2,"x-optional":false,"x-depends-on":["quote"],"x-next":["requirements/refresh","recipients"],"x-consumes":["targetCurrency (as currency)"],"x-produces":["data[].type (account type id)","field keys for details"],"summary":"Get recipient bank-detail requirements for a currency","description":"Returns the dynamic recipient bank-detail fields required to pay out in a currency, grouped by account type, with field types, validation rules, and allowed values.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"parameters":[{"name":"currency","in":"query","required":true,"description":"ISO 4217 currency code, e.g. NGN, KES, EUR.","example":"NGN","schema":{"type":"string"}}],"responses":{"200":{"description":"Requirements","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Account types. Each has type (id), title, and fields (groups; each group has name and a group array of field descriptors: key, name, type one of text/select/radio/date, required, refreshRequirementsOnChange, optional minLength/maxLength/validationRegexp/example and valuesAllowed for select/radio)."}}},"example":{"data":[{"type":"nigeria_bank_account","title":"Bank account","fields":[{"name":"Recipient bank details","group":[{"key":"accountNumber","name":"Account number","type":"text","required":true,"refreshRequirementsOnChange":false,"minLength":10,"maxLength":10}]}]}]}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/requirements/refresh":{"post":{"operationId":"refreshRequirements","x-order":2,"x-optional":true,"x-depends-on":["requirements"],"x-next":["recipients"],"x-consumes":["currency","type","details (partial)"],"x-produces":["data[] (updated field set)"],"summary":"Refresh conditional bank-detail requirements","description":"Returns the updated field set for a currency given the account type and the details collected so far, revealing fields that depend on chosen values (e.g. branch fields after picking a bank).","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"parameters":[{"name":"currency","in":"query","required":false,"description":"ISO 4217 currency code (may also be sent in the body).","example":"NGN","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","details"],"properties":{"currency":{"type":"string","description":"ISO 4217 currency (if not given as a query param)."},"type":{"type":"string","description":"Account type id from a prior requirements response."},"details":{"type":"object","description":"Field key to chosen value map collected so far."}}},"example":{"currency":"NGN","type":"nigeria_bank_account","details":{"bankCode":"058"}}}}},"responses":{"200":{"description":"Refreshed requirements (same shape as GET).","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Account types (see GET)."}}}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/transfer-status":{"get":{"operationId":"getTransferStatus","x-order":6,"x-optional":true,"x-depends-on":["transfer"],"x-next":[],"x-consumes":["transfer_id (as id)"],"x-produces":["status","status_label"],"summary":"Get transfer status by id","description":"Returns the live transfer record by Madhouse Wallet transfer id: status, amount, currency, recipient and quote snapshots, deposit address, reference, and any error.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"parameters":[{"name":"id","in":"query","required":true,"description":"Madhouse Wallet transfer id (24 hex chars) from /api/public/transfer.","example":"6650f1a2b3c4d5e6f7a8b9c0","schema":{"type":"string"}}],"responses":{"200":{"description":"Transfer record","content":{"application/json":{"schema":{"type":"object","required":["id","status","status_label"],"properties":{"id":{"type":"string","description":"Transfer id."},"type":{"type":"string","description":"Transfer type."},"amount":{"type":"number","description":"Source amount in USD."},"currency":{"type":["string","null"],"description":"Target currency or null."},"status":{"type":"string","description":"Machine status, e.g. pending, processing, completed, failed."},"status_label":{"type":"string","description":"Human-readable status."},"recipientId":{"type":"number","description":"Recipient id."},"reference":{"type":"string","description":"Madhouse Wallet reference code."},"deposit_address":{"type":["string","null"],"description":"Base USDC deposit address or null."},"timestamp":{"type":"string","description":"ISO creation timestamp."}}},"example":{"id":"6650f1a2b3c4d5e6f7a8b9c0","type":"payout","amount":100,"currency":"NGN","status":"processing","status_label":"Processing","recipientId":12345,"reference":"MW-7F3C1A2B","deposit_address":"0x1234567890abcdef1234567890abcdef12345678","timestamp":"2026-06-01T11:59:00.000Z"}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/recipients":{"post":{"operationId":"createRecipient","x-order":3,"x-optional":false,"x-depends-on":["requirements"],"x-next":["transfer"],"x-consumes":["targetCurrency (as currency)","data[].type","collected details"],"x-produces":["id (recipientId)"],"x-use-cases":["add a bank account to pay out to when cashing out crypto","create a beneficiary for a USDC-to-bank off-ramp","register a recipient bank account for a stablecoin payout"],"summary":"Create a payout recipient","description":"Creates a recipient from currency, account type, bank details, account holder name, and the funding wallet. Returns the recipient id for /api/public/transfer. Sanctions-screened upstream.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["currency","type","accountHolderName","details","wallet"],"properties":{"currency":{"type":"string","description":"ISO 4217 target currency."},"type":{"type":"string","description":"Account type id from /api/public/requirements."},"accountHolderName":{"type":"string","description":"Full legal name of the account holder."},"details":{"type":"object","description":"Bank-detail field key to value map for this currency and type."},"wallet":{"type":"string","description":"Funding wallet (0x EVM or Solana base58)."}}},"example":{"currency":"NGN","type":"nigeria_bank_account","accountHolderName":"Ada Lovelace","details":{"bankCode":"058","accountNumber":"0123456789"},"wallet":"0x1234567890abcdef1234567890abcdef12345678"}}}},"responses":{"200":{"description":"Created recipient","content":{"application/json":{"schema":{"type":"object","required":["id","currency","type"],"properties":{"id":{"type":"number","description":"Recipient id. Pass as recipientId to /api/public/transfer."},"accountHolderName":{"type":"string","description":"Echoed name."},"currency":{"type":"string","description":"Echoed currency."},"country":{"type":["string","null"],"description":"Resolved ISO-2 country or null."},"type":{"type":"string","description":"Echoed account type id."},"active":{"type":"boolean","description":"Whether the recipient is active."},"details":{"type":"object","description":"Stored detail fields."}}},"example":{"id":12345,"accountHolderName":"Ada Lovelace","currency":"NGN","country":"NG","type":"nigeria_bank_account","active":true,"details":{"bankCode":"058","accountNumber":"0123456789"}}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/transfer":{"post":{"operationId":"createTransfer","x-order":4,"x-optional":false,"x-depends-on":["quote","recipients"],"x-next":["confirm-transfer"],"x-consumes":["quoteId (as quote_id)","id (as recipientId)","funding wallet_address"],"x-produces":["transfer_id","deposit_address","instructions.send_amount"],"x-use-cases":["execute the crypto off-ramp and get a USDC deposit address to fund","sell USDC and send fiat to a recipient bank account","start a stablecoin-to-bank payout","send money abroad from crypto to a local bank account"],"x-offchain-action":"After this call, SEND the exact deposit amount of USDC on Base (from instructions) to deposit_address using your own wallet, then take the resulting on-chain transaction hash (tx_hash) to /confirm-transfer. This funding transfer is separate from — and much larger than — the 0.01 USDC x402 fee.","summary":"Create a transfer (v2)","description":"Creates a transfer from a locked quote and a recipient. Returns the transfer id and a Base USDC deposit_address plus the exact amount to fund. Then call /api/public/confirm-transfer. Funding wallet KYC and sanctions checks enforced upstream.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id","amount","recipientId","customer_uuid","customer_email","source_token","source_network","wallet_address"],"properties":{"quote_id":{"type":"string","description":"quoteId from /api/public/quote (UUID)."},"amount":{"type":"number","description":"USD source amount matching the quote. Greater than 0, at most 1000000."},"recipientId":{"type":"number","description":"Recipient id from /api/public/recipients."},"customer_uuid":{"type":"string","description":"Customer UUID identifying the sender."},"customer_email":{"type":"string","description":"Sender email address."},"source_token":{"type":"string","description":"Funding token, e.g. usdc."},"source_network":{"type":"string","description":"Funding network, e.g. base."},"wallet_address":{"type":"string","description":"Funding wallet (0x EVM or Solana base58)."}}},"example":{"quote_id":"7f3c1a2b-0d9e-4a6f-8c11-2b9d4e5f6a7b","amount":100,"recipientId":12345,"customer_uuid":"2b9d4e5f-6a7b-4c11-8c11-7f3c1a2b0d9e","customer_email":"sender@example.com","source_token":"usdc","source_network":"base","wallet_address":"0x1234567890abcdef1234567890abcdef12345678"}}}},"responses":{"200":{"description":"Created transfer","content":{"application/json":{"schema":{"type":"object","required":["transfer_id","deposit_address","amount"],"properties":{"transfer_id":{"type":"string","description":"Transfer id for status and confirm calls."},"deposit_address":{"type":"string","description":"Base USDC address to send funds to."},"source_token":{"type":"string","description":"Funding token."},"source_network":{"type":"string","description":"Funding network."},"amount":{"type":"number","description":"Source amount in USD."},"currency":{"type":"string","description":"Target currency."},"wallet_address":{"type":"string","description":"Echoed funding wallet."},"instructions":{"type":"object","description":"Funding instructions: send_amount (number), send_token, send_network, deposit_address (strings), note (string)."}}},"example":{"transfer_id":"6650f1a2b3c4d5e6f7a8b9c0","deposit_address":"0x1234567890abcdef1234567890abcdef12345678","source_token":"usdc","source_network":"base","amount":100,"currency":"NGN","wallet_address":"0x1234567890abcdef1234567890abcdef12345678","instructions":{"send_amount":100,"send_token":"usdc","send_network":"base","deposit_address":"0x1234567890abcdef1234567890abcdef12345678","note":"Send exactly 100 USDC on Base to the deposit address."}}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}},"/api/public/confirm-transfer":{"post":{"operationId":"confirmTransfer","x-order":5,"x-optional":false,"x-depends-on":["transfer"],"x-next":["transfer-status"],"x-consumes":["transfer_id","tx_hash (from the on-chain deposit you sent)"],"x-produces":["status"],"x-offchain-action":"Requires the tx_hash of the USDC deposit you broadcast on-chain in the previous step. The API does not move funds for you — you are the on-chain actor.","summary":"Confirm a funded transfer","description":"Submits the on-chain funding tx hash for a transfer so the payout proceeds. Pass transfer_id and tx_hash. Idempotent.","x-payment-info":{"price":{"mode":"fixed","amount":"0.01","currency":"USD"},"protocols":[{"x402":{}}]},"x-payment-networks":[{"network":"eip155:8453","label":"Base Mainnet","usdc":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},{"network":"eip155:137","label":"Polygon Mainnet","usdc":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"},{"network":"eip155:42161","label":"Arbitrum One","usdc":"0xaf88d065e77c8cC2239327C5EDb3A432268e5831"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["transfer_id","tx_hash"],"properties":{"transfer_id":{"type":"string","description":"Transfer id (24 hex) from /api/public/transfer."},"tx_hash":{"type":"string","description":"Funding tx hash (0x + 64 hex for EVM, or Solana base58 signature)."}}},"example":{"transfer_id":"6650f1a2b3c4d5e6f7a8b9c0","tx_hash":"0xabc1230000000000000000000000000000000000000000000000000000000def"}}}},"responses":{"200":{"description":"Confirmation result","content":{"application/json":{"schema":{"type":"object","required":["transfer_id","status"],"properties":{"transfer_id":{"type":"string","description":"Echoed transfer id."},"status":{"type":"string","description":"New status after confirmation."},"message":{"type":"string","description":"Result message."}}},"example":{"transfer_id":"6650f1a2b3c4d5e6f7a8b9c0","status":"processing","message":"Transfer confirmed and queued for payout."}}}},"400":{"description":"Bad request. A required field was missing/blank or malformed (currency, amount, wallet, UUID, etc.). Malformed requests are NEVER charged — settlement only happens on a 2xx, so a call that 400s costs nothing even if it carried payment. An UNPAID call always receives the 402 challenge first (pay, then retry). Body: { \"error\": \"<reason>\" }."},"402":{"description":"Payment required. Pay 0.01 USDC via x402 on Base (eip155:8453), Polygon (eip155:137), or Arbitrum (eip155:42161). The payment challenge (accepts[], resource, price) is returned both in the base64 `payment-required` response header and in this 402 response body."}}}}}}