MW Payouts API

Version 1.0.0 · Base URL: https://business.madhousewallet.com

API Key

Enter your API key to enable the "Try It" panels. Your key is only held in this tab and never stored. Create a key →

Authentication

All requests require a Bearer token in the Authorization header. Get a key from the Developers page.

Authorization: Bearer mw_live_<keyId>_<secret>
Security: API keys are for server-side use only. Never embed them in client-side code, browser requests, or public repositories.

Rate Limits

Rate limits apply per API key. Exceeded requests return HTTP 429.

EndpointLimit
GET /api/payouts/deposit-options60 req/min
GET|POST /api/payouts/account-requirements500 req/min
GET /api/payouts/quote20 req/min
GET|POST|PATCH|DELETE /api/payouts/recipients30 req/min
POST /api/payouts/transfer20 req/min
POST /api/payouts/transfer/v220 req/min
POST /api/payouts/transfer/v2/confirm-transfer20 req/min
GET /api/payouts/transfer/:id30 req/min

Headers on every response: X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset (Unix s)

Payout Workflow

  1. Get deposit optionsGET /api/payouts/deposit-options. Returns supported source tokens and networks (e.g. USDC on Base, USDT on Tron or Ethereum). Pick the token and network the sender will use.
  2. Discover fieldsGET /api/payouts/account-requirements?currency=EUR. If any field has refreshRequirementsOnChange: true, POST back with current values when that field changes.
  3. Create a recipientPOST /api/payouts/recipients. Save the returned id.
  4. Get a quoteGET /api/payouts/quote. Valid for 5 minutes. Returns quoteId.

Initiate transfer

You send USDC to the deposit address. The rest of the pipeline runs automatically.

  1. InitiatePOST /api/payouts/transfer with quote_id, recipientId, amount, customer_uuid. Returns deposit_address + transfer_id.
  2. Send USDC — from wallet_address to deposit_address on source_network.
  3. Automatic — the platform detects the deposit, converts to the target currency, and routes the payout to your recipient.
  4. Track — poll GET /api/payouts/transfer/:transfer_id to monitor progress.

Recipients

GETPOST/api/payouts/account-requirements500 req/min

Get required fields for a recipient currency

Call this before creating a recipient. Returns the account types (e.g. iban, sort_code, aba) available for the target currency and the fields required for each. Use the POST variant to refresh the field list when a refreshRequirementsOnChange field changes (e.g. switching legalType from PRIVATE to BUSINESS).

Dynamic requirements loop:

  1. GET with currency → render initial form fields
  2. User changes a field where refreshRequirementsOnChange: true
  3. POST with current type + details → re-render updated fields
  4. Repeat until form is complete, then POST /api/payouts/recipients

Example Success Response — GET (200)

{
  "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}$"
            }
          ]
        }
      ]
    }
  ]
}

cURL — GET initial requirements

curl "https://business.madhousewallet.com/api/payouts/account-requirements?currency=EUR" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

cURL — POST refresh after legalType change

curl -X POST "https://business.madhousewallet.com/api/payouts/account-requirements?currency=EUR" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"type":"iban","details":{"legalType":"BUSINESS"}}'

Response Codes

200Requirements returned
400Missing or invalid currency / type
401Invalid API key
429Rate limit exceeded (500 req/min)
500Internal server error
GET/api/payouts/recipients30 req/min

List recipients

Returns all active payout recipients belonging to your account. The id from each recipient is used as the recipientId in POST /api/payouts/transfer.

Example Success Response (200)

{
  "data": [
    {
      "id": 12345678,
      "accountHolderName": "Jane Doe",
      "currency": "EUR",
      "country": "DE",
      "type": "iban",
      "active": true,
      "details": {
        "iban": "DE89370400440532013000",
        "legalType": "PRIVATE"
      }
    }
  ]
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/recipients" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200List of your active recipients
401Invalid API key
429Rate limit exceeded (30 req/min)
500Internal server error
POST/api/payouts/recipients30 req/min

Create a recipient

Creates a new payout recipient linked to your account. The required type and details fields vary by currency — use the account requirements API or the dashboard to determine the correct structure.

Wallet verification: wallet is conditionally required — only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet (EVM or SVM); unverified wallets are rejected with 403 (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, you may omit it.

Email transfer recipients: Pass type: "wise_email_recipient" (with currency: "EUR") to create an email-based recipient. No account requirements lookup needed — provide only details: { "email": "recipient@example.com" }. Email recipients carry no transfer fee.

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.

Compliance screening: The recipient is screened against a sanctions database before creation using 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 so sparse sanctions data still catches matches.

Request Body Fields

FieldRequiredTypeDescription
currencyrequiredstringISO 4217 currency code for this recipient (e.g. EUR, GBP, USD). Must be EUR for wise_email_recipient.
typerequiredstringAccount type for the currency (e.g. iban, sort_code, aba). Use wise_email_recipient for email-based EUR transfers.
accountHolderNamerequiredstringFull legal name of the account holder
detailsrequiredobjectCurrency/type-specific bank account details. For wise_email_recipient: { "email": "recipient@example.com" }. For all other types: fields from the account requirements API.
walletconditionalstringConditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars); unverified wallets are rejected with 403. May be omitted if KYC is not enabled for your account.

Details By Currency / Type

EUR (IBAN)

{
  "currency": "EUR",
  "type": "iban",
  "accountHolderName": "Jane Doe",
  "details": {
    "legalType": "PRIVATE",
    "iban": "DE89370400440532013000"
  },
  "wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

EUR (Email Transfer)

{
  "currency": "EUR",
  "type": "wise_email_recipient",
  "accountHolderName": "Jane Doe",
  "details": {
    "email": "jane@example.com"
  },
  "wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

GBP (Sort code)

{
  "currency": "GBP",
  "type": "sort_code",
  "accountHolderName": "Jane Doe",
  "details": {
    "legalType": "PRIVATE",
    "sortCode": "231470",
    "accountNumber": "28821822"
  },
  "wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

USD (ACH)

{
  "currency": "USD",
  "type": "aba",
  "accountHolderName": "Jane Doe",
  "details": {
    "legalType": "PRIVATE",
    "abartn": "110000000",
    "accountNumber": "000123456789",
    "accountType": "CHECKING",
    "address": {
      "country": "US",
      "city": "New York",
      "firstLine": "123 Main St",
      "postCode": "10001",
      "state": "NY"
    }
  },
  "wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

PHP

{
  "currency": "PHP",
  "type": "philippines",
  "accountHolderName": "Juan Dela Cruz",
  "details": {
    "legalType": "PRIVATE",
    "bankCode": "BPIPH",
    "accountNumber": "1234567890"
  },
  "wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

Example Success Response (201)

{
  "id": 12345678,
  "accountHolderName": "Jane Doe",
  "currency": "EUR",
  "country": "DE",
  "type": "iban",
  "active": true,
  "details": {
    "iban": "DE89370400440532013000",
    "legalType": "PRIVATE"
  }
}

cURL Example (IBAN)

curl -X POST "https://business.madhousewallet.com/api/payouts/recipients" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"currency":"EUR","type":"iban","accountHolderName":"Jane Doe","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'

cURL Example (Email Transfer)

curl -X POST "https://business.madhousewallet.com/api/payouts/recipients" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"currency":"EUR","type":"wise_email_recipient","accountHolderName":"Jane Doe","details":{"email":"jane@example.com"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'

Response Codes

201Recipient created — returns full recipient object
400Missing required field, or wallet invalid/missing when your account requires KYC
401Invalid API key
403accountHolderName failed compliance screening, or wallet not KYC-verified (verify at kyc.madhousewallet.com)
429Rate limit exceeded (30 req/min)
500Internal server error
503Compliance screening or wallet verification service unavailable
GET/api/payouts/recipients/:id30 req/min

Get a recipient

Returns a single recipient by ID. Returns 403 if the recipient does not belong to your account.

Path Parameters

ParameterRequiredTypeDescription
idrequiredintegerRecipient account ID

Example Success Response (200)

{
  "id": 12345678,
  "accountHolderName": "Jane Doe",
  "currency": "EUR",
  "country": "DE",
  "type": "iban",
  "active": true,
  "details": {
    "iban": "DE89370400440532013000",
    "legalType": "PRIVATE"
  }
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/recipients/12345678" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200Recipient returned
401Invalid API key
403Recipient exists but belongs to another account
404Recipient not found
429Rate limit exceeded (30 req/min)
PATCH/api/payouts/recipients/:id30 req/min

Update a recipient

Updates the accountHolderName and/or details of an existing recipient. At least one of accountHolderName / details must be provided. wallet is conditionally required (see below).

Wallet verification: wallet is conditionally required — only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet (EVM or SVM); unverified wallets are rejected with 403 (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, you may omit it.

The platform may return a new recipient id after an update. Always use the id from the response for future calls.

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.

Compliance screening: When accountHolderName is included in the request, the recipient is re-screened using 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.

Request Body Fields

FieldRequiredTypeDescription
walletconditionalstringConditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars); unverified wallets are rejected with 403. May be omitted if KYC is not enabled for your account.
accountHolderNameoptionalstringUpdated legal name of the account holder
detailsoptionalobjectUpdated account details — include all required fields for the account type, not just changed ones

At least one of accountHolderName / details must be provided. wallet is required only if your account requires KYC verification.

Example Success Response (200)

{
  "id": 12345679,
  "accountHolderName": "Jane Smith",
  "currency": "EUR",
  "country": "DE",
  "type": "iban",
  "active": true,
  "details": {
    "iban": "DE89370400440532013000",
    "legalType": "PRIVATE"
  }
}

cURL Example

curl -X PATCH "https://business.madhousewallet.com/api/payouts/recipients/12345678" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"accountHolderName":"Jane Smith","wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'

Response Codes

200Recipient updated — returns updated recipient object
400No update fields provided, invalid value, or wallet invalid/missing when your account requires KYC
401Invalid API key
403Recipient belongs to another account, accountHolderName failed screening, or wallet not KYC-verified (verify at kyc.madhousewallet.com)
404Recipient not found
429Rate limit exceeded (30 req/min)
500Internal server error
503Compliance screening or wallet verification service unavailable
DELETE/api/payouts/recipients/:id30 req/min

Delete a recipient

Permanently deactivates a recipient. This cannot be undone. Any future payouts to this recipient will fail — create a new recipient if needed.

If any transfers for this recipient are still in progress, they are immediately marked as failed before deletion proceeds. The transfers_failed field in the response indicates how many were cancelled. Associated deposit wallets are cleaned up automatically.

Wallet verification: wallet is a conditionally required query-string parameter (DELETE carries no body) — 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, you may omit it.

Parameters

ParameterInRequiredTypeDescription
idpathrequiredintegerRecipient account ID to delete
walletqueryconditionalstringConditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars). May be omitted if KYC is not enabled for your account.

Example Success Response (200)

{
  "deleted": true,
  "id": "12345678",
  "transfers_failed": 0
}

cURL Example

curl -X DELETE "https://business.madhousewallet.com/api/payouts/recipients/12345678?wallet=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200Recipient deleted — transfers_failed indicates how many in-progress transfers were cancelled
400wallet query param invalid, or missing when your account requires KYC
401Invalid API key
403Recipient belongs to another account, or wallet not KYC-verified (verify at kyc.madhousewallet.com)
404Recipient not found
429Rate limit exceeded (30 req/min)
500Internal server error
503Wallet verification service unavailable

Payouts

GET/api/payouts/deposit-options60 req/min

Get supported source tokens and networks

Returns the list of source tokens and networks supported for deposits. Use the token and network values as source_token and source_network in POST /api/payouts/transfer. No parameters required.

Response Fields (per option)

FieldTypeDescription
tokenstringSource token identifier — pass as source_token (e.g. usdc, usdt)
networkstringSource network identifier — pass as source_network (e.g. base, ethereum, polygon, solana, tron)
labelstringHuman-readable label (e.g. USDC on Base)
tokenLabelstringShort token display label (e.g. USDC)
networkLabelstringShort network display label (e.g. Base)
statusstring"available" or "unavailable". Unavailable options are temporarily disabled — do not use their token/network values in POST /api/payouts/transfer.

Example Success Response (200)

{
  "options": [
    {
      "token": "usdc",
      "network": "arbitrum",
      "label": "USDC on Arbitrum",
      "tokenLabel": "USDC",
      "networkLabel": "Arbitrum",
      "status": "available"
    },
    {
      "token": "usdc",
      "network": "avalanche",
      "label": "USDC on Avalanche",
      "tokenLabel": "USDC",
      "networkLabel": "Avalanche",
      "status": "available"
    },
    {
      "token": "usdc",
      "network": "base",
      "label": "USDC on Base",
      "tokenLabel": "USDC",
      "networkLabel": "Base",
      "status": "available"
    },
    {
      "token": "usdc",
      "network": "ethereum",
      "label": "USDC on Ethereum",
      "tokenLabel": "USDC",
      "networkLabel": "Ethereum",
      "status": "available"
    },
    {
      "token": "usdc",
      "network": "polygon",
      "label": "USDC on Polygon",
      "tokenLabel": "USDC",
      "networkLabel": "Polygon",
      "status": "available"
    },
    {
      "token": "usdc",
      "network": "solana",
      "label": "USDC on Solana",
      "tokenLabel": "USDC",
      "networkLabel": "Solana",
      "status": "unavailable"
    },
    {
      "token": "usdt",
      "network": "ethereum",
      "label": "USDT on Ethereum",
      "tokenLabel": "USDT",
      "networkLabel": "Ethereum",
      "status": "available"
    },
    {
      "token": "usdt",
      "network": "tron",
      "label": "USDT on Tron",
      "tokenLabel": "USDT",
      "networkLabel": "Tron",
      "status": "unavailable"
    }
  ]
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/deposit-options" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200List of supported deposit options
401Invalid API key
429Rate limit exceeded (60 req/min)
GET/api/payouts/amount-limits60 req/min

Get transfer amount limits

Returns the minimum and maximum USD amounts your account is permitted to submit to GET /api/payouts/quote and POST /api/payouts/transfer. A null value means no limit is configured for that bound. Both endpoints return a 400 error if the submitted amount falls outside these limits.

Response Fields

FieldTypeDescription
min_amountnumber | nullMinimum USD amount allowed per request. null = no minimum configured.
max_amountnumber | nullMaximum USD amount allowed per request. null = no maximum configured.

Example Success Response (200)

{
  "min_amount": 10,
  "max_amount": 50000
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/amount-limits" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200Amount limits for your account
401Invalid API key
429Rate limit exceeded (60 req/min)
GET/api/payouts/quote20 req/min

Get exchange rate quote

Returns a live USD → foreign currency quote including fees and estimated delivery time. The quoted amount is cached server-side for 5 minutes. The response includes a quoteId — pass it as quote_id to POST /api/payouts/transfer or POST /api/payouts/transfer/v2 within the 5-minute window.

v2-offloader accounts: Pass v2_offloader=true when quoting for the v2 transfer pipeline. serviceFeePercent will reflect the combined platform fee (your configured fee + 20 bips for the offloader network). serviceFeeFixed will be 0. providerCharge is absent. Fee fields are display-only — the platform deducts the correct amounts at settlement.

Query Parameters

ParameterRequiredTypeDescription
targetCurrencyrequiredstringISO 4217 target currency. 81 supported currencies: 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.
sourceAmountrequirednumberUSD amount to convert (max 2 decimal places).
v2_offloaderoptionalstringPass true to receive fee fields calculated for the v2 transfer pipeline. When set, serviceFeePercent = (your fee bips + 20) / 100, serviceFeeFixed = 0, and providerCharge is absent. Use this when quoting before calling POST /api/payouts/transfer/v2.

Example Success Response — v1 account (200)

{
  "quoteId": "878c1cc4-025a-4ce2-a19d-920119ea6722",
  "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"
  }
}

Example Success Response — v2-offloader account, v2_offloader=true (200)

{
  "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"
  }
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/quote?targetCurrency=EUR&sourceAmount=1000" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200Quote returned and cached for 5 minutes
400Invalid parameters or unsupported currency
401Invalid API key
429Rate limit exceeded (20 req/min)
500Internal server error
POST/api/payouts/transfer20 req/minExternal wallet

Initiate a stablecoin-to-fiat payout

Creates a payout and returns a unique deposit_address on the network you specify via source_network. Send exactly amount of source_token from wallet_address to that address — the platform detects the deposit and automatically routes the fiat payout to your recipient.

Account provisioning: your account must be explicitly enabled for this transfer method. If it is not provisioned, requests are rejected with 403 ("This transfer method is not enabled for your account.") — contact support to enable it.

Wallet verification: wallet_address must be a KYC-verified wallet. Unverified wallets are rejected with 403 — verify your wallet at kyc.madhousewallet.com.

Compliance screening: Every wallet_address is screened against a third-party risk database before the transfer is created. Wallets that fail return 403. The screener supports EVM addresses only, so wallet_address is currently restricted to the EVM format — Solana and Tron addresses are temporarily not accepted on this endpoint.

POST /api/payouts/send (internal)

Browser-only. MW signs and broadcasts from your Safe wallet via passkey confirmation.

POST /api/payouts/transfer ← you are here

API key endpoint. You send USDC to the deposit address. The rest of the pipeline runs identically.

Workflow

  1. (Optional) Call GET /api/payouts/deposit-options to list supported tokens and networks
  2. Call GET /api/payouts/quote → save the returned quoteId
  3. Call POST /api/payouts/transfer with source_token + source_network → receive deposit_address and transfer_id
  4. Send exactly amount of source_token from wallet_address to deposit_address on source_network
  5. The platform detects the deposit, converts to the target currency, and routes funds to your recipient
  6. MW initiates the payout to your recipientId automatically — email confirmation sent

Request Body Fields

FieldRequiredTypeDescription
quote_idrequiredstringUUID returned by GET /api/payouts/quote (valid for 5 min, single-use)
amountrequirednumberUSD amount — must match the quote ±$0.01 (max 2 decimal places)
recipientIdrequiredintegerRecipient account ID from GET /api/payouts/recipients
customer_uuidrequiredstring (UUID)Your own UUID for this transfer — must be unique per transfer; duplicate UUIDs are rejected with 409
customer_emailrequiredstringEmail of the end user for transfer status notifications.
source_tokenrequiredstringToken to send. Supported values: usdc, usdt. See GET /api/payouts/deposit-options for supported token/network combinations.
source_networkrequiredstringNetwork to send from: arbitrum, avalanche, base, ethereum, polygon, solana, tron. The deposit address returned will be on this network.
wallet_addressrequiredstringThe address on source_network that will send USDC/USDT to the deposit address. Currently restricted to EVM format (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.
keep_recipientoptionalbooleanWhen true, the recipient account is not automatically deleted after the transfer reaches a terminal state (completed or failed). Use for saved/reusable recipients you manage yourself. Defaults to false — recipients are deleted automatically.

Example Success Response (200)

{
  "transfer": {
    "id": "507f1f77bcf86cd799439011",
    "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": "0xDe123456789aBcDEf1234567890aBcDeF1234567",
    "error": null,
    "refund_tx_hash": null,
    "reference": "507f1f77bcf86cd799439011",
    "timestamp": "2026-03-28T10:00:00.000Z"
  }
}

cURL Example

curl -X POST "https://business.madhousewallet.com/api/payouts/transfer" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"quote_id":"281901c4-19c6-462f-9c81-37fc6bbde340","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}'

Response Codes

200Transfer created — send USDC to deposit_address to trigger the off-ramp
400Validation error (expired/mismatched quote, amount too small or invalid)
401Invalid API key
403This transfer method is not enabled for your account, recipient not owned by your account, no deployed wallet, failed compliance screening, or wallet_address not KYC-verified (verify at kyc.madhousewallet.com)
409A transfer is already in progress — wait for it to complete
429Rate limit exceeded (20 req/min)
500Internal server error
503Compliance screening or wallet verification service unavailable
POST/api/payouts/transfer/v220 req/minv2 — USDC only

Initiate a v2 transfer — no expiry

Creates a v2 transfer and returns a hardcoded deposit address for USDC on the network you specify. Unlike v1, there is no expiry window — you have unlimited time to send funds. After sending USDC on-chain, call POST /api/payouts/transfer/v2/confirm-transfer with the transaction hash to trigger the payout pipeline.

USDC only. For USDT transfers, use POST /api/payouts/transfer.

Wallet compliance screening: when 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 and Solana addresses are accepted.

KYC verification: 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 neither compliance screening nor KYC is enabled for your account, you may omit it.

Wallet compliance screening

When compliance screening is enabled for your account (the default), the wallet_address you provide is screened before the transfer is created — it must be an EVM address and a wallet that fails screening is rejected with 403. If screening is temporarily unavailable, the request fails closed with 503 — retry after a short delay. If screening is disabled for your account, vet sending wallets yourself: only submit wallets that you own or have independently verified.

POST /api/payouts/transfer (v1)

  • Supports USDC + USDT
  • Automated wallet compliance scan
  • Transfer expires after 10 min
  • Platform creates deposit address via provider

POST /api/payouts/transfer/v2 ← you are here

  • USDC only
  • Wallet compliance screening when enabled for your account (default) — EVM addresses only; KYC verification applies when enabled
  • No expiry — send funds any time
  • Fixed deposit address — must call confirm-transfer with tx_hash

Workflow

  1. Call GET /api/payouts/quote → save the returned quoteId
  2. Call POST /api/payouts/transfer/v2 with source_token + source_network → receive deposit_address and transfer_id
  3. Send exactly amount USDC from wallet_address to deposit_address on source_network
  4. Call POST /api/payouts/transfer/v2/confirm-transfer with transfer_id and the on-chain tx_hash
  5. The platform matches the deposit to your transfer and automatically initiates the fiat payout

Request Body Fields

FieldRequiredTypeDescription
quote_idrequiredstring (UUID)UUID returned by GET /api/payouts/quote (valid for 5 min, single-use)
amountrequirednumberUSD amount — must match the quote ±$0.01 (max 2 decimal places)
recipientIdrequiredintegerRecipient account ID from GET /api/payouts/recipients
customer_uuidrequiredstring (UUID)Your own UUID for this transfer — must be unique per transfer; duplicate UUIDs are rejected with 409
source_tokenrequiredstringMust be usdc. USDT is not supported on v2.
source_networkrequiredstringNetwork to send from: base, solana, polygon, arbitrum, avalanche, ethereum
wallet_addressconditionalstringThe address that will send USDC to the deposit address. Conditionally required — required when 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 and Solana (base58, 32–44 chars) addresses are accepted; if KYC is enabled it must be a KYC-verified wallet.
keep_recipientoptionalbooleanWhen true, the recipient is not automatically deleted after the transfer reaches a terminal state. Defaults to false.
customer_emailoptionalstring (email)Email address to receive payout status notifications (sent and failed). If omitted, notifications go to the authenticated account's email.

Example Success Response (200)

{
  "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."
  }
}

cURL Example

curl -X POST "https://business.madhousewallet.com/api/payouts/transfer/v2" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"quote_id":"281901c4-19c6-462f-9c81-37fc6bbde340","amount":500,"recipientId":12345678,"customer_uuid":"550e8400-e29b-41d4-a716-446655440000","source_token":"usdc","source_network":"base","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","customer_email":"customer@example.com"}'

Response Codes

200Transfer created — deposit address returned
400Validation error (USDT on v2, unsupported network, expired/mismatched quote, or wallet_address missing/non-EVM when compliance screening is enabled)
401Invalid API key
403Recipient not owned by your account, no deployed wallet, wallet_address failed compliance screening, or wallet_address not KYC-verified (verify at kyc.madhousewallet.com)
409Duplicate customer_uuid
429Rate limit exceeded (20 req/min)
500Internal server error
503Compliance screening or wallet verification service unavailable (or screening not configured)
POST/api/payouts/transfer/v2/confirm-transfer20 req/minv2 only

Link a transaction hash to a v2 transfer

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 tx_hash. The platform uses the hash to identify and match your deposit to this transfer, then automatically initiates the fiat payout.

Replay attack prevention

Each tx_hash may only be used once across the entire platform — even across different accounts. A hash already associated with any transfer is rejected with 409. Never reuse a transaction hash.

Request Body Fields

FieldRequiredTypeDescription
transfer_idrequiredstringThe transfer_id returned by POST /api/payouts/transfer/v2
tx_hashrequiredstringOn-chain transaction hash. EVM: 0x + 64 hex chars (32 bytes). Solana: base58 signature (85–88 chars).

Idempotency & Status Handling

ScenarioHTTPBehaviour
Same transfer_id + same tx_hash (re-call)200Idempotent — returns current status without changes
Same transfer_id + different tx_hash409Rejected — overwriting is not allowed
tx_hash already used on another transfer409Rejected — each hash is single-use globally
Deposit already detected (status beyond pending_match)200Returns current status — no changes needed
Transfer already in terminal state200Returns terminal status with informational message

Example Success Response (200)

{
  "transfer_id": "507f1f77bcf86cd799439011",
  "status": "pending_match",
  "message": "tx_hash recorded — the system will match your deposit and initiate the payout automatically"
}

cURL Example

curl -X POST "https://business.madhousewallet.com/api/payouts/transfer/v2/confirm-transfer" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>" \
  -H "Content-Type: application/json" \
  -d '{"transfer_id":"507f1f77bcf86cd799439011","tx_hash":"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"}'

Response Codes

200tx_hash recorded (status: pending_match) or deposit already detected
400Invalid transfer_id/tx_hash format, or not a v2 transfer
401Invalid API key
404Transfer not found or not owned by your account
409tx_hash already used on another transfer, or a different hash already recorded for this transfer
429Rate limit exceeded (20 req/min)
GET/api/payouts/transfer/:transfer_id30 req/min

Get transfer status

Returns the current status and details of a payout transfer using the transfer_id returned by POST /api/payouts/transfer. Lookup is global by transfer_id — any valid API key can retrieve any transfer (including dashboard-created transfers or those created with a different key) as long as you know its unguessable 24-character ID.

Status Values

ready_to_processWaiting for your USDC deposit to arrive
processingDeposit received — funds being converted and routed
transfer_createdConversion complete — outgoing transfer to recipient created
completedOutgoing payment sent to recipient
failedTransfer failed — check the error field
refundedTransfer could not be completed — USDC returned to your wallet (see refund_tx_hash)

Example Success Response (200)

{
  "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"
  }
}

cURL Example

curl "https://business.madhousewallet.com/api/payouts/transfer/507f1f77bcf86cd799439011" \
  -H "Authorization: Bearer mw_live_<keyId>_<secret>"

Response Codes

200Transfer found — returns transfer object
400Invalid transfer ID
401Invalid API key
404Transfer not found or belongs to another account
429Rate limit exceeded (30 req/min)

Machine-Readable Spec

The full OpenAPI 3.0 specification is available for code generators and API clients.

Download openapi.json