{"openapi":"3.0.3","info":{"title":"MW Payouts API","version":"1.0.0","description":"The MW Payouts API lets you programmatically retrieve exchange-rate quotes, calculate platform fees, and submit USDC-to-fiat payout transfers on behalf of your account.\n\nAll requests must be authenticated with an API key obtained from the **Developers** section of the dashboard. API keys are for **server-side use only** — never embed them in client-side code.","contact":{"name":"MW Support","email":"support@madhousewallet.com"}},"servers":[{"url":"https://business.madhousewallet.com","description":"Production"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Pass your API key as a Bearer token: `Authorization: Bearer mw_live_<keyId>_<secret>`"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"}},"required":["error"]},"FeeResponse":{"type":"object","properties":{"mwFee":{"type":"number","description":"Calculated MW platform fee in USD (display only — not deducted on-chain)","example":7.5},"mwFeePercentage":{"type":"number","description":"Fee as a percentage (e.g. 1.5 means 1.5%)","example":1.5}},"required":["mwFee","mwFeePercentage"]},"QuoteResponse":{"type":"object","properties":{"providerCharge":{"type":"number","description":"Provider processing fee in USD deducted from the source amount before conversion. Present only for v1 accounts; absent for v2-offloader accounts.","example":2},"serviceFeePercent":{"type":"number","description":"Combined platform service fee displayed as a percentage (e.g. 0.6 means 0.6%). 0 when not configured.","example":0.6},"serviceFeeFixed":{"type":"number","nullable":true,"description":"Fixed fee in USD (display only). Always 0 for v2-offloader accounts. Absent when not applicable.","example":0},"targetCurrency":{"type":"string","description":"ISO 4217 currency code of the payout destination (uppercased).","example":"EUR"},"usdToTargetRate":{"type":"number","description":"Exchange rate from 1 USD to the target currency.","example":0.9183},"quote":{"type":"object","description":"Settlement layer quote details — fees and estimated delivery.","properties":{"targetAmount":{"type":"number","nullable":true,"description":"Amount the recipient will receive in the target currency.","example":915.28},"transferFee":{"type":"number","nullable":true,"description":"Settlement layer transfer fee charged by the settlement provider (deducted from target amount). Equals feeFxAmount + feePayoutAmount when the breakdown is available.","example":0.58},"feeFxPercent":{"type":"number","nullable":true,"description":"FX conversion fee as a percentage (e.g. 0.72 means 0.72%). The FX component of transferFee is this percent applied to the converted amount. Null when a breakdown is not available (e.g. unsupported corridor or email recipient).","example":0.47},"feeFxAmount":{"type":"number","nullable":true,"description":"FX conversion fee component of transferFee, in USD. Null when a breakdown is not available.","example":0.34},"feePayoutAmount":{"type":"number","nullable":true,"description":"Flat payout fee component of transferFee, in USD. Null when a breakdown is not available.","example":0.24},"estimatedDelivery":{"type":"string","nullable":true,"description":"Estimated delivery timestamp (ISO 8601).","example":"2026-03-26T15:50:39Z"}}},"quoteId":{"type":"string","description":"UUID identifying this quote — pass to POST /api/payouts/transfer or POST /api/payouts/transfer/v2 as `quote_id` (API key callers only; absent for JWT callers). Valid for 5 minutes, single-use.","example":"3f7a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"},"sourceAmount":{"type":"number","description":"Original USD input amount (before any fee deductions).","example":1000}},"required":["serviceFeePercent","serviceFeeFixed","targetCurrency","usdToTargetRate","quote","quoteId","sourceAmount"]},"TransferRequest":{"type":"object","properties":{"quote_id":{"type":"string","description":"The `quoteId` returned by GET /api/payouts/quote (valid for 5 minutes; single-use)","example":"3f7a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"},"amount":{"type":"number","description":"USD amount to transfer (max 2 decimal places, must match the quoted amount ±$0.01)","example":100},"recipientId":{"type":"integer","description":"Recipient account ID (must belong to your account)","example":12345678},"customer_uuid":{"type":"string","format":"uuid","description":"Your own UUID for this transfer — stored for audit and support lookups. Must be globally unique across all transfers; a duplicate UUID is rejected with 409.","example":"550e8400-e29b-41d4-a716-446655440000"},"customer_email":{"type":"string","format":"email","description":"Email address of the end user. Used to send transfer status notifications.","example":"user@example.com"},"source_token":{"type":"string","description":"Token to send. Supported values: usdc, usdt. Use GET /api/payouts/deposit-options to retrieve the full list of supported token/network combinations.","example":"usdc"},"source_network":{"type":"string","description":"Network to send from: arbitrum, avalanche, base, ethereum, polygon, solana, tron.","example":"base"},"wallet_address":{"type":"string","description":"The address on `source_network` that will send USDC or USDT to the deposit address. **Currently restricted to EVM addresses only** (0x + 40 hex chars) because every transfer is pre-screened for compliance and the screener supports Ethereum-family addresses only. Solana and Tron addresses are temporarily rejected on this endpoint. The deposit address returned will be on the same network as `source_network`.","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"keep_recipient":{"type":"boolean","description":"When `true`, the recipient account is **not** automatically deleted after the transfer reaches a terminal state (completed or failed). Use this for saved/reusable recipients that you manage yourself. Defaults to `false` — recipients are deleted automatically after each transfer.","example":false}},"required":["quote_id","amount","recipientId","customer_uuid","customer_email","source_token","source_network","wallet_address"]},"Recipient":{"type":"object","description":"A payout recipient account","properties":{"id":{"type":"integer","description":"Recipient account ID","example":12345678},"accountHolderName":{"type":"string","description":"Name on the account","example":"Jane Doe"},"currency":{"type":"string","description":"ISO 4217 currency code","example":"EUR"},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code","example":"DE","nullable":true},"type":{"type":"string","description":"Account type (e.g. iban, sort_code, aba)","example":"iban"},"active":{"type":"boolean","description":"Whether the recipient is active","example":true},"details":{"type":"object","description":"Account-type-specific details (routing/account numbers, IBAN, etc.)"}},"required":["id","accountHolderName","currency","type","active","details"]},"RecipientSnapshot":{"type":"object","nullable":true,"description":"Snapshot of the recipient account captured at transfer creation time. Present even if the recipient has since been deleted.","properties":{"id":{"type":"integer","description":"Recipient account ID","example":12345678},"accountHolderName":{"type":"string","description":"Name on the account","example":"Jane Doe"},"currency":{"type":"string","description":"ISO 4217 currency code","example":"EUR"},"type":{"type":"string","description":"Account type (e.g. iban, sort_code, aba)","example":"iban"},"country":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code","example":"DE"},"details":{"type":"object","description":"Account-type-specific details (routing/account numbers, IBAN, etc.)"}},"required":["id","accountHolderName","currency","type","details"]},"CreateRecipientRequest":{"type":"object","properties":{"currency":{"type":"string","description":"ISO 4217 target currency code (e.g. EUR, GBP, USD)","example":"EUR"},"type":{"type":"string","description":"Account type returned by `GET /api/payouts/account-requirements` (e.g. `iban`, `sort_code`, `aba`). Use `wise_email_recipient` to create an email-based EUR transfer recipient — no account requirements lookup needed. `wise_email_recipient` is only valid when `currency` is `EUR`.","example":"iban"},"accountHolderName":{"type":"string","description":"Full legal name of the account holder","example":"Jane Doe"},"details":{"type":"object","description":"Account-type-specific fields. For standard types (e.g. `iban`, `sort_code`, `aba`): provide the fields returned by `GET /api/payouts/account-requirements`. When the account type requires an address, `details.address.country` must be a valid ISO 3166-1 alpha-2 code (e.g. `US`, `GB`, `DE`). For `wise_email_recipient`: provide only `{ \"email\": \"recipient@example.com\" }`.","example":{"legalType":"PRIVATE","iban":"DE89370400440532013000"}},"wallet":{"type":"string","description":"Your wallet address (EVM `0x` + 40 hex, or Solana base58 32–44 chars). Conditionally required: only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet — unverified wallets are rejected with `403` (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, this field may be omitted.","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}},"required":["currency","type","accountHolderName","details"]},"UpdateRecipientRequest":{"type":"object","description":"At least one of `accountHolderName` / `details` must be provided. `wallet` is conditionally required — only needed if your account requires KYC verification.","properties":{"accountHolderName":{"type":"string","description":"Updated full legal name of the account holder","example":"Jane Smith"},"details":{"type":"object","description":"Updated account details. Must include all required fields for the recipient's account type, not just the changed ones.","example":{"legalType":"PRIVATE","iban":"DE89370400440532013000"}},"wallet":{"type":"string","description":"Your wallet address (EVM `0x` + 40 hex, or Solana base58 32–44 chars). Conditionally required: only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet — unverified wallets are rejected with `403` (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, this field may be omitted.","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}}},"TransferResponse":{"type":"object","description":"Wrapper returned by both POST /api/payouts/transfer and GET /api/payouts/transfer/:id","properties":{"transfer":{"type":"object","properties":{"id":{"type":"string","description":"Unique transfer reference. Use this ID for GET /api/payouts/transfer/:id and support queries.","example":"507f1f77bcf86cd799439011"},"user_id":{"type":"string","description":"Authenticated user ID","example":"did:privy:abc123"},"type":{"type":"string","enum":["payout"],"example":"payout"},"amount":{"type":"number","description":"USD source amount","example":100},"currency":{"type":"string","nullable":true,"description":"Target payout currency (ISO 4217)","example":"EUR"},"status":{"type":"string","enum":["ready_to_process","processing","transfer_created","completed","failed","refunded"],"example":"ready_to_process"},"status_label":{"type":"string","description":"Human-readable status description","example":"Ready to Process"},"recipientId":{"type":"integer","description":"Recipient account ID","example":12345678},"recipient":{"$ref":"#/components/schemas/RecipientSnapshot","description":"Snapshot of the recipient account at transfer creation time"},"customerUuid":{"type":"string","nullable":true,"description":"Caller-supplied idempotency UUID","example":"550e8400-e29b-41d4-a716-446655440000"},"customerEmail":{"type":"string","nullable":true,"description":"Email associated with this transfer","example":"user@example.com"},"sourceToken":{"type":"string","nullable":true,"description":"Source token (e.g. usdc)","example":"usdc"},"sourceNetwork":{"type":"string","nullable":true,"description":"Source network (e.g. base)","example":"base"},"quote":{"type":"object","nullable":true,"description":"Exchange rate snapshot captured at quote time. `providerCharge` is absent when zero (e.g. for accounts without a provider processing fee). `serviceFeeFixed` is absent when no fixed EUR fee applies.","properties":{"sourceAmount":{"type":"number","description":"Original USD source amount","example":1000},"providerCharge":{"type":"number","description":"Provider processing fee in USD deducted before conversion (absent when zero)","example":2},"serviceFeePercent":{"type":"number","description":"Platform service fee percentage","example":1.5},"serviceFeeFixed":{"type":"number","nullable":true,"description":"Fixed EUR fee deducted after conversion (absent when zero)","example":0.5},"targetCurrency":{"type":"string","description":"ISO currency code of the payout destination","example":"EUR"},"usdToTargetRate":{"type":"number","description":"USD to target currency exchange rate","example":0.9183},"targetAmount":{"type":"number","nullable":true,"description":"Expected recipient amount in target currency","example":915.28},"transferFee":{"type":"number","nullable":true,"description":"Settlement layer transfer fee (deducted from target amount)","example":0.58},"estimatedDelivery":{"type":"string","nullable":true,"description":"Estimated delivery time (ISO 8601)","example":"2026-03-30T12:00:00Z"}}},"wallet_address":{"type":"string","nullable":true,"description":"The address (EVM, Solana, or Tron) that was specified as the sender at transfer creation time.","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"deposit_address":{"type":"string","nullable":true,"description":"Per-transfer USDC deposit address on the network specified by `source_network`. Send exactly `amount` USDC from `wallet_address` to this address to trigger the payout pipeline.","example":"0x1234567890AbCdEf1234567890AbCdEf12345678"},"error":{"type":"string","nullable":true,"description":"Error message when status is `failed`","example":null},"refund_tx_hash":{"type":"string","nullable":true,"description":"On-chain transaction hash (Base) of the USDC refund returned to the sender's wallet. Present only when `status` is `refunded`; `null` otherwise.","example":null},"reference":{"type":"string","description":"Internal reference string","example":"507f1f77bcf86cd799439011"},"timestamp":{"type":"string","format":"date-time","description":"Transfer creation time (ISO 8601)","example":"2026-03-28T10:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"Last update time (ISO 8601). Present on GET, absent on POST.","example":"2026-03-28T10:05:00.000Z"}},"required":["id","user_id","type","amount","currency","status","status_label","recipientId","reference","timestamp"]}},"required":["transfer"]}}},"paths":{"/api/payouts/deposit-options":{"get":{"operationId":"getDepositOptions","summary":"Get supported source tokens and networks","description":"Returns the list of source token/network combinations accepted for deposits. Use the `token` and `network` values as `source_token` and `source_network` in `POST /api/payouts/transfer`.\n\nEvery option includes a `status` field — `\"available\"` or `\"unavailable\"`. Unavailable options are temporarily disabled; do not use their `token`/`network` values in a transfer request.\n\n**Rate limit:** 60 requests/minute.","tags":["Payouts"],"responses":{"200":{"description":"Deposit options returned","content":{"application/json":{"schema":{"type":"object","properties":{"options":{"type":"array","items":{"type":"object","properties":{"token":{"type":"string","description":"Source token identifier — pass as source_token.","example":"usdc"},"network":{"type":"string","description":"Source network identifier — pass as source_network.","example":"base"},"label":{"type":"string","description":"Human-readable label.","example":"USDC on Base"},"tokenLabel":{"type":"string","description":"Short token display label.","example":"USDC"},"networkLabel":{"type":"string","description":"Short network display label.","example":"Base"},"status":{"type":"string","enum":["available","unavailable"],"description":"\"available\" — option can be used. \"unavailable\" — temporarily disabled; do not use in a transfer request.","example":"available"}},"required":["token","network","label","tokenLabel","networkLabel","status"]}}}},"example":{"options":[{"token":"usdc","network":"arbitrum","label":"USDC on Arbitrum","tokenLabel":"USDC","networkLabel":"Arbitrum","status":"available"},{"token":"usdc","network":"base","label":"USDC on Base","tokenLabel":"USDC","networkLabel":"Base","status":"available"},{"token":"usdc","network":"solana","label":"USDC on Solana","tokenLabel":"USDC","networkLabel":"Solana","status":"unavailable"},{"token":"usdt","network":"tron","label":"USDT on Tron","tokenLabel":"USDT","networkLabel":"Tron","status":"unavailable"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (60 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/account-requirements":{"get":{"operationId":"getAccountRequirements","summary":"Get required fields for a recipient currency","description":"Returns the list of account types and their required fields for a given target currency. Call this **before** creating a recipient to know which fields to collect.\n\nEach account type (e.g. `iban`, `sort_code`, `aba`) has a set of fields. Some fields have `refreshRequirementsOnChange: true` — when a user changes these, POST the current form state back to this endpoint to receive an updated field list (e.g. switching `legalType` from `PRIVATE` to `BUSINESS` may add a company name field).\n\n**Rate limit:** 500 requests/minute.","tags":["Recipients"],"parameters":[{"name":"currency","in":"query","required":true,"schema":{"type":"string","example":"EUR"},"description":"ISO 4217 target currency code. Supported currencies (81): AED, ALL, ARS, AUD, BAM, BDT, BHD, BMD, BOB, BRL, BWP, CAD, CHF, CLP, CNY, COP, CRC, CVE, CZK, DKK, DOP, EGP, EUR, GBP, GEL, GHS, GMD, GNF, GTQ, HKD, HNL, HUF, IDR, ILS, INR, ISK, JPY, KES, KGS, KHR, KRW, KWD, LAK, LKR, MAD, MNT, MOP, MUR, MXN, MYR, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PEN, PHP, PKR, PLN, PYG, QAR, RON, RSD, RWF, SAR, SCR, SEK, SGD, SRD, THB, TND, TRY, TZS, UAH, UGX, USD, UYU, VND, ZAR."}],"responses":{"200":{"description":"Account requirements returned","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"iban"},"title":{"type":"string","example":"IBAN"},"fields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"IBAN"},"group":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","example":"iban"},"name":{"type":"string","example":"IBAN"},"type":{"type":"string","enum":["text","select","radio","date"]},"required":{"type":"boolean"},"example":{"type":"string","nullable":true},"minLength":{"type":"integer","nullable":true},"maxLength":{"type":"integer","nullable":true},"validationRegexp":{"type":"string","nullable":true},"refreshRequirementsOnChange":{"type":"boolean"},"valuesAllowed":{"type":"array","nullable":true,"items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}}}}}}}}}}}}}}},"example":{"data":[{"type":"iban","title":"IBAN","fields":[{"name":"Legal type","group":[{"key":"legalType","name":"Legal type","type":"select","required":true,"refreshRequirementsOnChange":true,"valuesAllowed":[{"key":"PRIVATE","name":"Person"},{"key":"BUSINESS","name":"Business"}]}]},{"name":"IBAN","group":[{"key":"iban","name":"IBAN","type":"text","required":true,"refreshRequirementsOnChange":false,"example":"DE89370400440532013000","minLength":14,"maxLength":42,"validationRegexp":"^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$"}]}]}]}}}},"400":{"description":"Missing or invalid currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (500 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"refreshAccountRequirements","summary":"Refresh requirements after a field change","description":"When a field has `refreshRequirementsOnChange: true`, POST the current form state to this endpoint to receive an updated field list. For example, changing `legalType` from `PRIVATE` to `BUSINESS` may reveal a company name field.\n\nPass the same `currency` query parameter as the GET call. The body should contain the account `type` and all `details` collected so far.\n\n**Rate limit:** 500 requests/minute.","tags":["Recipients"],"parameters":[{"name":"currency","in":"query","required":true,"schema":{"type":"string","example":"EUR"},"description":"ISO 4217 target currency code"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"Account type returned from the GET call","example":"iban"},"details":{"type":"object","description":"Current form values collected so far","example":{"legalType":"BUSINESS"}}}}}}},"responses":{"200":{"description":"Updated requirements returned","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Missing currency or type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (500 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/fee":{"get":{"operationId":"getPayoutFee","summary":"Get platform fee for a given USD amount","description":"Returns the MW platform fee for a given USD amount based on your account's fee configuration. This is a lightweight call with no external API dependencies — suitable for real-time display as users type amounts.\n\n**Note:** The fee is for display only. It is not deducted from the USDC transferred on-chain.","tags":["Payouts"],"parameters":[{"name":"amount","in":"query","required":true,"schema":{"type":"number","minimum":0.01,"maximum":1000000},"description":"USD amount to calculate the fee for","example":500}],"responses":{"200":{"description":"Fee calculated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeeResponse"},"example":{"mwFee":7.5,"mwFeePercentage":1.5}}}},"400":{"description":"Invalid amount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (60 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/amount-limits":{"get":{"operationId":"getAmountLimits","summary":"Get the minimum and maximum transfer amounts for your account","description":"Returns the minimum and maximum USD amounts that your account is permitted to submit to `GET /api/payouts/quote` and `POST /api/payouts/transfer`.\n\nA `null` value means no limit is configured for that bound. Both endpoints return a `400` error if the submitted amount falls outside these limits.\n\n**Rate limit:** 60 requests/minute.","tags":["Payouts"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Amount limits for the authenticated account","content":{"application/json":{"schema":{"type":"object","required":["min_amount","max_amount"],"properties":{"min_amount":{"type":["number","null"],"description":"Minimum USD amount allowed per transfer. null = no minimum configured.","example":10},"max_amount":{"type":["number","null"],"description":"Maximum USD amount allowed per transfer. null = no maximum configured.","example":50000}}},"example":{"min_amount":10,"max_amount":50000}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (60 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/quote":{"get":{"operationId":"getPayoutQuote","summary":"Get an exchange-rate quote for a USD → foreign currency payout","description":"Returns a live exchange-rate quote for a USD → foreign currency payout, including fees and estimated delivery time.\n\nThe quoted `sourceAmount` is cached server-side for 5 minutes. The response includes a `quoteId` that you must pass to `POST /api/payouts/transfer` or `POST /api/payouts/transfer/v2` within that window.\n\n**v2-offloader accounts:** Pass `v2_offloader=true` to receive fees calculated for the v2 transfer pipeline. `serviceFeePercent` will reflect the combined platform fee (your configured fee + 20 bips for the offloader). `serviceFeeFixed` will be 0. `providerCharge` is absent. The fee fields are display-only — the platform deducts the correct amounts at settlement time.\n\n**Rate limit:** 20 requests/minute.","tags":["Payouts"],"parameters":[{"name":"targetCurrency","in":"query","required":true,"schema":{"type":"string"},"description":"ISO 4217 target currency code. Supported currencies (81): AED, ALL, ARS, AUD, BAM, BDT, BHD, BMD, BOB, BRL, BWP, CAD, CHF, CLP, CNY, COP, CRC, CVE, CZK, DKK, DOP, EGP, EUR, GBP, GEL, GHS, GMD, GNF, GTQ, HKD, HNL, HUF, IDR, ILS, INR, ISK, JPY, KES, KGS, KHR, KRW, KWD, LAK, LKR, MAD, MNT, MOP, MUR, MXN, MYR, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PEN, PHP, PKR, PLN, PYG, QAR, RON, RSD, RWF, SAR, SCR, SEK, SGD, SRD, THB, TND, TRY, TZS, UAH, UGX, USD, UYU, VND, ZAR.","example":"EUR"},{"name":"sourceAmount","in":"query","required":true,"schema":{"type":"number","minimum":0.01,"maximum":1000000},"description":"USD amount to convert","example":100},{"name":"v2_offloader","in":"query","required":false,"schema":{"type":"string","enum":["true"]},"description":"Pass `true` to receive fee fields calculated for the v2 transfer pipeline (POST /api/payouts/transfer/v2). When set, `serviceFeePercent` reflects your configured fee + 20 bips, `serviceFeeFixed` is 0, and `providerCharge` is absent. Ignored for non-API-key callers.","example":"true"}],"responses":{"200":{"description":"Quote generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"},"examples":{"v1_spherepay":{"summary":"v1 account","value":{"quoteId":"281901c4-19c6-462f-9c81-37fc6bbde340","sourceAmount":1000,"providerCharge":2,"serviceFeePercent":1.5,"targetCurrency":"EUR","usdToTargetRate":0.9183,"quote":{"targetAmount":915.28,"transferFee":0.58,"feeFxPercent":0,"feeFxAmount":0,"feePayoutAmount":0.58,"estimatedDelivery":"2026-03-26T15:52:37Z"}}},"v2_offloader":{"summary":"v2-offloader account (v2_offloader=true)","value":{"quoteId":"4d0c2a5d-940b-4759-861f-4d660e5ae492","sourceAmount":100,"serviceFeePercent":0.6,"serviceFeeFixed":0,"targetCurrency":"KES","usdToTargetRate":129.6,"quote":{"targetAmount":12274,"transferFee":3.98,"feeFxPercent":1.15,"feeFxAmount":1.83,"feePayoutAmount":2.15,"estimatedDelivery":"2026-05-26T03:24:37Z"}}}}}}},"400":{"description":"Invalid parameters or unsupported currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (20 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/transfer":{"post":{"operationId":"initiateTransfer","summary":"Initiate a USDC-to-fiat payout","description":"Creates a payout transfer and returns a unique deposit address on the network specified by `source_network`. Send USDC from `wallet_address` to that address — MW handles the rest of the conversion and delivery pipeline automatically.\n\n**Wallet verification:** Before any other processing, `wallet_address` must be a KYC-verified wallet. Unverified wallets are rejected with `403` — verify your wallet at kyc.madhousewallet.com.\n\n**Compliance screening:** The supplied `wallet_address` is also screened against a third-party risk database. Wallets that fail the screen are rejected with a `403`. The screener supports EVM addresses only, so `wallet_address` is currently restricted to the EVM format (0x + 40 hex chars). Solana and Tron addresses are temporarily not accepted on this endpoint.\n\n**Prerequisites:**\n- Your account must have a deployed Safe wallet.\n- Call `GET /api/payouts/quote` within the last 5 minutes — pass the returned `quoteId` as `quote_id`.\n- The `recipientId` must be a recipient belonging to your account.\n\n**Concurrent transfers:** Multiple transfers can be in flight simultaneously — each gets a unique deposit address and is settled independently.\n\n**Workflow:**\n1. `GET /api/payouts/quote` → save `quoteId`.\n2. `POST /api/payouts/transfer` with `quote_id`, `amount`, `recipientId`, `source_network`, `wallet_address` → receive `deposit_address` and `transfer_id`.\n3. Send exactly `amount` USDC from `wallet_address` to `deposit_address` on `source_network`.\n4. MW detects the deposit and automatically initiates the fiat payout to your recipient — email confirmation sent.\n\n**Rate limit:** 20 requests/minute.","tags":["Payouts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferRequest"},"example":{"quote_id":"3f7a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c","amount":1000,"recipientId":12345678,"customer_uuid":"550e8400-e29b-41d4-a716-446655440000","customer_email":"user@example.com","source_token":"usdc","source_network":"base","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","keep_recipient":false}}}},"responses":{"200":{"description":"Transfer created — send USDC to deposit_address to trigger the payout pipeline","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferResponse"},"example":{"transfer":{"id":"507f1f77bcf86cd799439011","user_id":"usr_550e8400e29b41d4a716446655440000","type":"payout","amount":1000,"currency":"EUR","status":"ready_to_process","status_label":"Ready to Process","recipientId":12345678,"recipient":{"id":12345678,"accountHolderName":"Jane Doe","currency":"EUR","type":"iban","country":"DE","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"}},"customerUuid":"550e8400-e29b-41d4-a716-446655440000","customerEmail":"user@example.com","sourceToken":"usdc","sourceNetwork":"base","quote":{"sourceAmount":1000,"providerCharge":2,"serviceFeePercent":1.5,"targetCurrency":"EUR","usdToTargetRate":0.9183,"targetAmount":915.28,"transferFee":0.58,"estimatedDelivery":"2026-03-30T12:00:00Z"},"wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","deposit_address":"0x1234567890AbCdEf1234567890AbCdEf12345678","error":null,"refund_tx_hash":null,"reference":"507f1f77bcf86cd799439011","timestamp":"2026-03-28T10:00:00.000Z"}}}}},"400":{"description":"Validation error (bad amount, missing/expired quote, mismatched quote_id or amount)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This transfer method is not enabled for your account, the recipient is not owned by your account, no deployed wallet, `wallet_address` failed compliance screening, or `wallet_address` is not KYC-verified — verify it at kyc.madhousewallet.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Duplicate customer_uuid — this UUID has already been used for a previous transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (20 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Compliance screening or wallet verification service unavailable — an upstream service is unreachable or misconfigured. Retry after a short delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/transfer/v2":{"post":{"operationId":"createTransferV2","summary":"Initiate a v2 USDC transfer (no expiry)","description":"Creates a v2 payout transfer and returns a hardcoded deposit address for the requested `source_token`/`source_network` combination. Unlike v1, the deposit address is fixed — no provider API call is made at initiation time.\n\n**v2 differences from v1:**\n- **USDC only** — USDT transfers must use `POST /api/payouts/transfer`.\n- **No expiry** — there is no time window to send funds. The transfer remains open until you send USDC and call `POST /api/payouts/transfer/v2/confirm-transfer`.\n- **Two-phase confirmation** — after sending USDC on-chain, you must call `POST /api/payouts/transfer/v2/confirm-transfer` with the `transfer_id` and `tx_hash` to trigger the payout pipeline. The platform matches the deposit to your transfer using the provided transaction hash.\n\n**Wallet compliance screening (conditional, per-account):** when compliance screening is enabled for your account (the default), `wallet_address` is **required**, must be an **EVM** address (0x + 40 hex chars), and is screened before any other processing. A wallet that fails screening is rejected with `403`. When screening is disabled for your account, `wallet_address` stays KYC-conditional (see below) and Solana addresses are accepted.\n\n**KYC wallet verification (conditional, per-account):** if your account requires KYC verification, `wallet_address` is required and must be a KYC-verified wallet or the request is rejected with `403` (verify at kyc.madhousewallet.com); if KYC is not enabled for your account, `wallet_address` may be omitted (unless compliance screening requires it).\n\n**Workflow:**\n1. `GET /api/payouts/quote` → save `quoteId`.\n2. `POST /api/payouts/transfer/v2` with `quote_id`, `amount`, `recipientId`, `source_network` (and `wallet_address` — required if compliance screening or KYC is enabled for your account) → receive `deposit_address` and `transfer_id`.\n3. Send exactly `amount` USDC to `deposit_address` on `source_network`.\n4. Call `POST /api/payouts/transfer/v2/confirm-transfer` with `transfer_id` and the on-chain `tx_hash`.\n5. The platform detects the deposit and automatically initiates the fiat payout — email confirmation sent.\n\n**Supported networks for v2 (USDC only):** `base`, `solana`, `polygon`, `arbitrum`, `avalanche`, `ethereum`.\n\n**Rate limit:** 20 requests/minute.","tags":["Payouts"],"security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id","amount","recipientId","source_token","source_network","customer_uuid"],"properties":{"quote_id":{"type":"string","format":"uuid","description":"UUID returned by `GET /api/payouts/quote`. Must be used within 5 minutes.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"wallet_address":{"type":"string","description":"The address on `source_network` that will send USDC to the deposit address. Conditionally required: required if compliance screening or KYC verification is enabled for your account; may be omitted otherwise (stored and returned as an empty string). When compliance screening is enabled (the default), it must be an EVM address (0x + 40 hex chars) and is screened before processing — Solana addresses are rejected. When screening is disabled, both EVM (0x + 40 hex chars) and Solana (base58, 32–44 chars) addresses are accepted; if KYC is enabled it must be a KYC-verified wallet.","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"amount":{"type":"number","description":"USD amount to transfer (max 2 decimal places, must match the quoted amount ±$0.01).","example":500},"recipientId":{"type":"integer","description":"Recipient account ID from `GET /api/payouts/recipients` or `POST /api/payouts/recipients`.","example":12345678},"source_token":{"type":"string","enum":["usdc"],"description":"Source token. Only `usdc` is accepted on v2. Use `POST /api/payouts/transfer` for USDT.","example":"usdc"},"source_network":{"type":"string","enum":["base","solana","polygon","arbitrum","avalanche","ethereum"],"description":"Network on which you will send USDC to the deposit address.","example":"base"},"customer_uuid":{"type":"string","format":"uuid","description":"Your own UUID for this transfer — stored for audit and duplicate prevention. A duplicate UUID is rejected with 409.","example":"550e8400-e29b-41d4-a716-446655440000"},"keep_recipient":{"type":"boolean","description":"When `true`, the recipient is not automatically deleted after the transfer reaches a terminal state. Defaults to `false`.","example":false},"customer_email":{"type":"string","format":"email","description":"Email address to receive payout status notifications (sent and failed). If omitted, notifications are sent to the authenticated account's email address.","example":"customer@example.com"}}},"example":{"quote_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","amount":500,"recipientId":12345678,"source_token":"usdc","source_network":"base","customer_uuid":"550e8400-e29b-41d4-a716-446655440000","customer_email":"customer@example.com"}}}},"responses":{"200":{"description":"Transfer created — deposit address returned","content":{"application/json":{"schema":{"type":"object","required":["transfer_id","deposit_address","source_token","source_network","amount","currency","wallet_address","instructions"],"properties":{"transfer_id":{"type":"string","description":"Unique transfer reference. Use this ID with `POST /api/payouts/transfer/v2/confirm-transfer`.","example":"507f1f77bcf86cd799439011"},"deposit_address":{"type":"string","description":"The address to send USDC to on `source_network`. This is a fixed offloader wallet address.","example":"0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e"},"source_token":{"type":"string","description":"Token to send (always `usdc` on v2).","example":"usdc"},"source_network":{"type":"string","description":"Network on which to send USDC.","example":"base"},"amount":{"type":"number","description":"USD amount to send.","example":500},"currency":{"type":"string","description":"Target payout currency.","example":"EUR"},"wallet_address":{"type":"string","description":"Your sender address as supplied in the request. Empty string when omitted (KYC not enabled for your account).","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"instructions":{"type":"object","description":"Human-readable instructions for completing the transfer.","properties":{"send_amount":{"type":"number","example":500},"send_token":{"type":"string","example":"USDC"},"send_network":{"type":"string","example":"base"},"deposit_address":{"type":"string","example":"0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e"},"note":{"type":"string","description":"Next step reminder.","example":"Send exactly 500 USDC on base to deposit_address, then call POST /api/payouts/transfer/v2/confirm-transfer with transfer_id and tx_hash to trigger the payout pipeline."}}}}},"example":{"transfer_id":"507f1f77bcf86cd799439011","deposit_address":"0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e","source_token":"usdc","source_network":"base","amount":500,"currency":"EUR","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","instructions":{"send_amount":500,"send_token":"USDC","send_network":"base","deposit_address":"0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e","note":"Send exactly 500 USDC on base to deposit_address, then call POST /api/payouts/transfer/v2/confirm-transfer with transfer_id and tx_hash to trigger the payout pipeline."}}}}},"400":{"description":"Validation error — invalid amount, missing/expired quote, mismatched amount, USDT not allowed on v2, unsupported network, or `wallet_address` missing/non-EVM when compliance screening is enabled for your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Recipient not owned by your account, no deployed wallet, `wallet_address` failed compliance screening, or `wallet_address` is not KYC-verified — verify it at kyc.madhousewallet.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Duplicate `customer_uuid` — this UUID has already been used for a previous transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (20 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Compliance screening or wallet verification service unavailable, or screening not configured — retry after a short delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/transfer/v2/confirm-transfer":{"post":{"operationId":"confirmTransferV2","summary":"Link a tx_hash to a v2 transfer to trigger the payout pipeline","description":"After sending USDC on-chain to the `deposit_address` returned by `POST /api/payouts/transfer/v2`, call this endpoint with the `transfer_id` and the on-chain transaction hash (`tx_hash`). The platform will use the hash to match the incoming deposit to your transfer and automatically initiate the fiat payout to your recipient.\n\n**Important:**\n- Each `tx_hash` may only be used **once** across the entire platform. A hash that has already been associated with any transfer (including your own) is rejected with `409`. This prevents replay attacks.\n- A second call with the same `transfer_id` and the same `tx_hash` is idempotent — it returns the current status without error.\n- A second call with the same `transfer_id` but a different `tx_hash` is rejected with `409` — overwriting is not allowed.\n- Once the deposit is detected (status moves past `pending_match`), this endpoint returns the current status without making any changes.\n\n**Rate limit:** 20 requests/minute.","tags":["Payouts"],"security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["transfer_id","tx_hash"],"properties":{"transfer_id":{"type":"string","description":"The `transfer_id` returned by `POST /api/payouts/transfer/v2`.","example":"507f1f77bcf86cd799439011"},"tx_hash":{"type":"string","description":"On-chain transaction hash of your USDC send. EVM format: `0x` followed by 64 hex chars (32 bytes). Solana format: base58 signature (85–88 chars).","example":"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"}}},"example":{"transfer_id":"507f1f77bcf86cd799439011","tx_hash":"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"}}}},"responses":{"200":{"description":"tx_hash recorded or already processing","content":{"application/json":{"schema":{"type":"object","required":["transfer_id","status","message"],"properties":{"transfer_id":{"type":"string","description":"The transfer ID.","example":"507f1f77bcf86cd799439011"},"status":{"type":"string","enum":["awaiting_deposit","pending_match","processing","deposit_sent","transfer_created","completed","failed","refunded"],"description":"Current transfer status after recording the hash.","example":"pending_match"},"message":{"type":"string","description":"Human-readable summary of the action taken.","example":"tx_hash recorded — the system will match your deposit and initiate the payout automatically"}}},"example":{"transfer_id":"507f1f77bcf86cd799439011","status":"pending_match","message":"tx_hash recorded — the system will match your deposit and initiate the payout automatically"}}}},"400":{"description":"Invalid `transfer_id` format, invalid `tx_hash` format, or transfer is not a v2 transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Transfer not found or not owned by your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This `tx_hash` has already been used for another transfer, or a different `tx_hash` has already been recorded for this transfer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (20 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/transfer/{transfer_id}":{"get":{"operationId":"getTransfer","summary":"Get the status and details of a transfer","description":"Returns the current status and details of a payout transfer by its `transfer_id` (the value returned by `POST /api/payouts/transfer`).\n\nLookup is global by `transfer_id`: any valid API key can retrieve any transfer — including transfers created from the dashboard or with a different API key — as long as you know its `transfer_id`. Transfer IDs are unguessable 24-character identifiers.\n\n**Status values:**\n- `ready_to_process` — waiting for your USDC deposit\n- `processing` — deposit received; funds are being converted\n- `transfer_created` — conversion complete; outgoing fiat transfer to recipient has been created\n- `completed` — outgoing payment sent to recipient\n- `failed` — transfer failed at some stage; check the `error` field\n\n**Rate limit:** 30 requests/minute.","tags":["Payouts"],"parameters":[{"name":"transfer_id","in":"path","required":true,"schema":{"type":"string","example":"507f1f77bcf86cd799439011"},"description":"The `transfer_id` returned by POST /api/payouts/transfer"}],"responses":{"200":{"description":"Transfer found","content":{"application/json":{"schema":{"type":"object","properties":{"transfer":{"type":"object","properties":{"id":{"type":"string","description":"Transfer ID","example":"507f1f77bcf86cd799439011"},"user_id":{"type":"string","description":"Your account user ID","example":"usr_550e8400e29b41d4a716446655440000"},"type":{"type":"string","enum":["payout"],"example":"payout"},"amount":{"type":"number","description":"USD amount sent","example":1000},"currency":{"type":"string","nullable":true,"description":"Target payout currency","example":"EUR"},"status":{"type":"string","enum":["ready_to_process","processing","transfer_created","completed","failed","refunded"],"description":"Current transfer status","example":"processing"},"status_label":{"type":"string","description":"Human-readable status description","example":"Deposit received — processing"},"recipientId":{"type":"integer","description":"Recipient account ID","example":12345678},"recipient":{"$ref":"#/components/schemas/RecipientSnapshot","description":"Snapshot of the recipient account at transfer creation time. Present even if the recipient has since been deleted."},"customerUuid":{"type":"string","nullable":true,"format":"uuid","description":"Your caller-supplied UUID for this transfer","example":"550e8400-e29b-41d4-a716-446655440000"},"customerEmail":{"type":"string","nullable":true,"format":"email","description":"Email address for transfer notifications","example":"user@example.com"},"sourceToken":{"type":"string","nullable":true,"description":"Source token used for the deposit (e.g. usdc)","example":"usdc"},"sourceNetwork":{"type":"string","nullable":true,"description":"Source network used for the deposit (e.g. base)","example":"base"},"quote":{"type":"object","nullable":true,"description":"Quote snapshot captured at transfer creation time","properties":{"sourceAmount":{"type":"number","description":"Original USD input amount","example":1000},"providerCharge":{"type":"number","description":"Provider processing fee in USD deducted from source amount before conversion (absent when zero)","example":2},"serviceFeePercent":{"type":"number","description":"Platform service fee as a percentage of the source amount","example":1.5},"serviceFeeFixed":{"type":"number","nullable":true,"description":"Fixed EUR fee deducted after conversion (absent when zero)","example":0.5},"targetCurrency":{"type":"string","description":"ISO currency code of the payout destination","example":"EUR"},"usdToTargetRate":{"type":"number","description":"USD to target currency exchange rate","example":0.9183},"targetAmount":{"type":"number","nullable":true,"description":"Expected recipient amount in target currency","example":915.28},"transferFee":{"type":"number","nullable":true,"description":"Transfer provider fee charged by the settlement layer (deducted from target amount)","example":0.58},"estimatedDelivery":{"type":"string","nullable":true,"format":"date-time","description":"Estimated delivery time (ISO 8601)","example":"2026-03-26T15:52:37Z"}}},"wallet_address":{"type":"string","nullable":true,"description":"The address (EVM, Solana, or Tron) specified as the sender at transfer creation time","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"error":{"type":"string","nullable":true,"description":"Error message when status is `failed`; null otherwise","example":null},"refund_tx_hash":{"type":"string","nullable":true,"description":"On-chain transaction hash (Base) of the USDC refund returned to the sender's wallet. Present only when `status` is `refunded`; null otherwise.","example":null},"reference":{"type":"string","description":"Payment reference string embedded in the transfer. This value appears in recipient bank statements as the remittance information.","example":"507f1f77bcf86cd799439011"},"timestamp":{"type":"string","format":"date-time","description":"ISO timestamp when the transfer was created","example":"2026-03-22T14:30:00.000Z"},"updated_at":{"type":"string","format":"date-time","description":"ISO timestamp of the last status update","example":"2026-03-22T14:35:00.000Z"}},"required":["id","type","amount","currency","status","status_label","recipientId","reference","timestamp","updated_at"]}},"required":["transfer"]},"example":{"transfer":{"id":"507f1f77bcf86cd799439011","type":"payout","amount":1000,"currency":"EUR","status":"processing","status_label":"Deposit received — processing","recipientId":12345678,"recipient":{"id":12345678,"accountHolderName":"Jane Doe","currency":"EUR","type":"iban","country":"DE","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"}},"customerUuid":"550e8400-e29b-41d4-a716-446655440000","customerEmail":"user@example.com","sourceToken":"usdc","sourceNetwork":"base","quote":{"sourceAmount":1000,"providerCharge":2,"serviceFeePercent":1.5,"targetCurrency":"EUR","usdToTargetRate":0.9183,"targetAmount":915.28,"transferFee":0.58,"estimatedDelivery":"2026-03-26T15:52:37Z"},"wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","error":null,"refund_tx_hash":null,"reference":"507f1f77bcf86cd799439011","timestamp":"2026-03-22T14:30:00.000Z","updated_at":"2026-03-22T14:32:00.000Z"}}}}},"400":{"description":"Invalid transfer ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Transfer not found or not owned by your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 requests/minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/recipients":{"get":{"operationId":"listRecipients","summary":"List your payout recipients","description":"Returns all active payout recipients belonging to your account.\n\n**Rate limit:** 30 requests/minute.","tags":["Recipients"],"responses":{"200":{"description":"Recipients returned","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Recipient"}}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createRecipient","summary":"Create a payout recipient","description":"Creates a new payout recipient linked to your account. Use `GET /api/payouts/account-requirements` first to determine the correct `type` and `details` fields for the target currency.\n\n**Wallet verification:** `wallet` is required and must be a KYC-verified wallet (EVM or SVM). Unverified wallets are rejected with `403` — verify your wallet at kyc.madhousewallet.com.\n\n**Email transfer recipients:** Pass `type: \"wise_email_recipient\"` to create an email-based EUR recipient. `currency` must be `EUR`. No account requirements lookup needed — provide only `details: { email: \"recipient@example.com\" }`. Email recipients carry no transfer fee.\n\n**Country format:** When `details.address` is present, `details.address.country` is required and must be a valid ISO 3166-1 alpha-2 code (two letters, e.g. `US`, `GB`, `DE`). Country names (`\"United States\"`) and ISO-3 codes (`\"USA\"`) are rejected with `400`. This ensures the sanctions screen can reliably match country against the sanctioned record's country list.\n\n**Compliance screening:** The recipient is screened against a sanctions database before creation. The screen matches on three signals: `accountHolderName` (case-folded + accent-stripped exact match), `details.address.country` (ISO 2-letter code), and `details.address` (street + city + postal-code token overlap). A request is rejected with `403` only if all available signals match the same sanctioned entity. When either the sanctioned record or the recipient lacks a country or address, that check falls through to name-only matching to ensure sparse sanctions data still catches matches.\n\n**Rate limit:** 30 requests/minute.","tags":["Recipients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecipientRequest"},"example":{"currency":"EUR","type":"iban","accountHolderName":"Jane Doe","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}}}},"responses":{"201":{"description":"Recipient created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipient"}}}},"400":{"description":"Missing required fields, or `wallet` is invalid / missing when your account requires KYC verification (must be a valid EVM or SVM address)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`accountHolderName` failed compliance screening (matches a sanctioned entity), or `wallet` is not KYC-verified — verify it at kyc.madhousewallet.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Compliance screening or wallet verification service unavailable. Retry after a short delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/payouts/recipients/{id}":{"get":{"operationId":"getRecipient","summary":"Get a single recipient","description":"Returns a single recipient by ID. The recipient must belong to your account.\n\n**Rate limit:** 30 requests/minute.","tags":["Recipients"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","example":12345678},"description":"Recipient account ID"}],"responses":{"200":{"description":"Recipient returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipient"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Recipient not owned by your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recipient not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateRecipient","summary":"Update a recipient","description":"Updates the `accountHolderName` and/or `details` of an existing recipient. The recipient must belong to your account. Provide at least one field to update.\n\n**Wallet verification:** `wallet` is required and must be a KYC-verified wallet (EVM or SVM). Unverified wallets are rejected with `403` — verify your wallet at kyc.madhousewallet.com.\n\nNote: MW may issue a new recipient `id` after an update — always use the `id` from the response for subsequent calls.\n\n**Country format:** If `details.address` is included in the update, `details.address.country` must be a valid ISO 3166-1 alpha-2 code (e.g. `US`, `GB`, `DE`). Updates that don't touch the address leave the existing stored country untouched.\n\n**Compliance screening:** If `accountHolderName` is included in the request, the new value is screened against the sanctions database. The screen uses the same name + country + address algorithm as `POST /api/payouts/recipients`. Country and address come from the new `details.address` when provided, otherwise from the existing recipient. Matches are rejected with `403` and the recipient is left unchanged.\n\n**Rate limit:** 30 requests/minute.","tags":["Recipients"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","example":12345678},"description":"Recipient account ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRecipientRequest"},"example":{"accountHolderName":"Jane Smith","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}}}},"responses":{"200":{"description":"Recipient updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipient"}}}},"400":{"description":"No update fields provided, invalid values, or `wallet` is invalid / missing when your account requires KYC verification (must be a valid EVM or SVM address)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Recipient not owned by your account, new `accountHolderName` failed compliance screening, or `wallet` is not KYC-verified — verify it at kyc.madhousewallet.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recipient not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Compliance screening or wallet verification service unavailable. Retry after a short delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteRecipient","summary":"Delete a recipient","description":"Deactivates a payout recipient. The recipient must belong to your account. Deleted recipients cannot be used in future transfers.\n\n`wallet` is a conditionally required query-string parameter (DELETE requests carry no body): it is only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet — unverified wallets are rejected with `403` (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, it may be omitted.\n\nIf any transfers for this recipient are still in progress (not yet completed or failed), they are immediately marked as **failed** before deletion proceeds. The `transfers_failed` field in the response indicates how many transfers were cancelled. Associated deposit wallets created by the platform are cleaned up automatically.\n\n**Rate limit:** 30 requests/minute.","tags":["Recipients"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","example":12345678},"description":"Recipient account ID"},{"name":"wallet","in":"query","required":false,"schema":{"type":"string","example":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"},"description":"Your wallet address (EVM `0x` + 40 hex, or Solana base58 32–44 chars). Conditionally required: only needed if your account requires KYC verification, in which case it must be a KYC-verified wallet. May be omitted if KYC is not enabled for your account."}],"responses":{"200":{"description":"Recipient deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","example":true},"id":{"type":"string","example":"12345678"},"transfers_failed":{"type":"integer","example":0,"description":"Number of in-progress transfers that were cancelled (marked failed) as a result of this deletion. `0` means no transfers were in flight."}}}}}},"400":{"description":"`wallet` query parameter is invalid, or missing when your account requires KYC verification (must be a valid EVM or SVM address)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Recipient not owned by your account, or `wallet` is not KYC-verified — verify it at kyc.madhousewallet.com","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recipient not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (30 req/min)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Wallet verification service unavailable. Retry after a short delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"tags":[{"name":"Payouts","description":"USDC-to-fiat payout operations"},{"name":"Recipients","description":"Payout recipient management"}]}