DevelopersChangelog

API Changelog

All notable changes to the MW Payouts API are documented here. View full API docs →

v1.3.23
  • Changed

    Raised the rate limit on GET and POST /api/payouts/account-requirements from 60 to 500 requests/minute per key.

v1.3.22
  • Changed

    GET /api/payouts/transfer/:id lookup is now global by transfer_id. Any valid API key can retrieve any transfer by its ID — including transfers created from the dashboard or with a different API key — instead of only transfers owned by the calling key. Transfer IDs are unguessable 24-character identifiers. A correct transfer_id that previously returned 404 (owned by another account) now returns the transfer.

v1.3.21
  • Changed

    The transfer reference is now the bare transfer ID (e.g. "507f1f77bcf86cd799439011") instead of "Recipient {recipientId} Ref {transferId}". This affects the reference field returned by POST /api/payouts/transfer and GET /api/payouts/transfer/:id, and the reference that appears on the recipient's outgoing transfer. The recipient is determined internally from the transfer record. This keeps the reference within the length limits enforced by some destination currencies. No request changes are required.

v1.3.20
  • Changed

    POST /api/payouts/transfer (v1) now requires your account to be explicitly provisioned for this transfer method. If your account is not provisioned, the request is rejected with 403 ("This transfer method is not enabled for your account."). Contact support to enable it. POST /api/payouts/transfer/v2 is unaffected.

v1.3.19
  • Added

    POST /api/payouts/transfer/v2 now performs wallet compliance screening when it is enabled for your account (the default). When screening is enabled, 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 ("Wallet address failed compliance screening"). Solana addresses are not accepted while screening is enabled. If the screening service is unavailable or not configured, the request fails closed with 503. When screening is disabled for your account, behavior is unchanged: wallet_address stays KYC-conditional and Solana addresses are accepted.

v1.3.18
  • Changed

    The wallet field is now conditionally required instead of always required. On POST/PATCH /api/payouts/recipients (wallet in the body), DELETE /api/payouts/recipients/:id (?wallet= query parameter), and POST /api/payouts/transfer/v2 (wallet_address in the body), the wallet is only required if your account requires KYC verification. If KYC is not enabled for your account, you may omit it entirely. When provided it must still be a valid EVM (0x + 40 hex) or SVM (base58 32–44 char) address, and when KYC is enabled it must be a KYC-verified wallet (otherwise 403). On POST /api/payouts/transfer/v2, an omitted wallet_address is stored and returned as an empty string. POST /api/payouts/transfer (v1) is unchanged — wallet_address remains required there.

v1.3.17
  • Added

    KYC wallet verification is now enforced on recipient management and transfer initiation. POST/PATCH /api/payouts/recipients now require a new wallet field in the request body, and DELETE /api/payouts/recipients/:id now requires a wallet query-string parameter (?wallet=). The wallet must be an EVM (0x + 40 hex) or SVM (base58 32–44 char) address. On POST /api/payouts/transfer and POST /api/payouts/transfer/v2 the existing wallet_address field is used — no new field is needed.

  • Changed

    These five endpoints now reject requests whose wallet is not KYC-verified with 403 ("Wallet not verified. Please verify your wallet at kyc.madhousewallet.com before continuing."). If the verification service is unreachable, the request fails closed with 503. Verify your wallet at kyc.madhousewallet.com.

v1.3.16
  • Removed

    BGN (Bulgarian Lev) is no longer a supported targetCurrency. Bulgaria adopted the euro, and the payout corridor is no longer available. Requests with targetCurrency=BGN now return 400 (previously they could error mid-quote). Use EUR for recipients in Bulgaria. The supported-currency count is now 81.

v1.3.15
  • Added

    GET /api/payouts/quote — the quote object now includes a breakdown of the transfer fee: feeFxPercent (FX conversion fee as a percentage), feeFxAmount (the FX fee component in USD), and feePayoutAmount (the flat payout fee component in USD). feeFxAmount + feePayoutAmount equals transferFee. All three are nullable and are null when a breakdown is not available (e.g. an unsupported corridor that falls back to the live settlement estimate, or an email recipient with no transfer fee). Existing fields are unchanged.

v1.3.14
  • Changed

    GET /api/payouts/quote — quote.targetAmount and quote.transferFee are now computed from our internal per-currency fee schedule instead of the settlement layer's estimate. Back-testing against actually-delivered amounts showed the previous estimate understated the recipient amount (mean error ~13%); the new values track delivery to within ~1%. Response shape is unchanged — only the accuracy of these two fields improves. The live exchange rate and estimatedDelivery are unaffected.

v1.3.13
  • Added

    GET /api/payouts/quote — new optional query parameter v2_offloader=true. When present, the response uses the fee structure for v2 transfer accounts: serviceFeePercent reflects the combined platform fee (your account's configured basis points plus the 20 bip processing cut), serviceFeeFixed is always 0, and the providerCharge and mwFeeEur fields are omitted from the response. The quoteId returned can be passed to POST /api/payouts/transfer/v2 as quote_id. This parameter has no effect for JWT (browser) callers and is silently ignored.

v1.3.12
  • Changed

    DELETE /api/payouts/recipients/:id — removed the 409 restriction that previously blocked deletion while a transfer was in progress. Recipients can now be deleted at any time. Any in-progress transfers for the recipient are automatically marked as failed before deletion proceeds. The response now includes a transfers_failed integer field indicating how many transfers were cancelled (0 if none were in flight). Associated deposit wallets created by the platform are cleaned up automatically.

v1.3.11
  • Changed

    POST /api/payouts/transfer/v2 — documentation updated with a compliance warning. This endpoint does not perform automated wallet screening on the wallet_address field. It is intended for integrations where the calling platform has already vetted the sending wallets. Only use this endpoint with wallets you own or have independently verified. Use POST /api/payouts/transfer (v1) when automated compliance screening of unknown wallet addresses is required.

v1.3.10
  • Added

    POST /api/payouts/transfer/v2 — new v2 transfer initiation endpoint for USDC-only payouts. Returns a hardcoded deposit address per source_token/source_network — no expiry window and no compliance pre-screening. Supported networks: base, solana, polygon, arbitrum, avalanche, ethereum. USDT transfers must continue to use POST /api/payouts/transfer. After sending USDC on-chain, callers must call POST /api/payouts/transfer/v2/confirm-transfer to link the transaction hash and trigger the payout pipeline.

  • Added

    POST /api/payouts/transfer/v2/confirm-transfer — new endpoint to link an on-chain transaction hash to a v2 transfer. Accepts transfer_id (returned by POST /api/payouts/transfer/v2) and tx_hash (EVM: 0x + 64 hex chars; Solana: base58, 85–88 chars). Each tx_hash may only be used once across the entire platform — submitting a hash already associated with any transfer returns 409. Idempotent: a second call with the same transfer_id + tx_hash returns 200 with the current status. Rate limit: 20 requests/minute.

v1.3.9
  • Removed

    POST /api/payouts/transfer/cancel — endpoint removed. This endpoint was not in active use. To abandon a transfer before USDC is sent, simply stop the flow; pending transfers expire automatically. For transfers already in progress, contact support.

v1.3.8
  • Added

    GET /api/payouts/transfer/:id — new `status` value `"refunded"`. Returned when a transfer could not be completed and the deposited USDC has been returned to the sender's wallet. This status is set by the platform operations team and indicates the transfer is permanently closed.

  • Added

    GET /api/payouts/transfer/:id — new `refund_tx_hash` field (string or null). Contains the on-chain Base transaction hash of the USDC refund returned to the sender's wallet. This field is non-null only when `status` is `"refunded"`; it is `null` for all other statuses. Use this hash to verify the refund on a block explorer (e.g. basescan.org).

v1.3.7
  • Removed

    POST /api/payouts/recipients — `user_id` field removed. It was previously required but could not be satisfied by external callers who do not have access to the internal account ID. Recipient ownership is enforced server-side by the authenticated API key. Remove `user_id` from your request body.

  • Removed

    POST /api/payouts/transfer — `user_id` field removed from the required fields list. The field is no longer validated against the authenticated account. Remove `user_id` from your transfer request body.

  • Removed

    Recipient objects — `user_id` field removed from GET /api/payouts/recipients and GET /api/payouts/recipients/:id responses. The field was an internal identifier not useful to external callers.

v1.3.6
  • Added

    POST /api/payouts/transfer — new optional boolean field `keep_recipient`. When set to `true`, the recipient account is not automatically deleted after the transfer reaches a terminal state (completed or failed). Use this when you manage recipients yourself and intend to reuse them across multiple transfers. Defaults to `false` — recipients are automatically removed after each transfer when this flag is absent or false.

v1.3.5
  • Added

    POST /api/payouts/recipients — new `type: "wise_email_recipient"` option for EUR recipients. Pass `currency: "EUR"`, `type: "wise_email_recipient"`, and `details: { "email": "recipient@example.com" }`. No account requirements lookup needed. Email recipients carry no transfer fee — the `transferFee` field in quote responses is `0` for these recipients.

  • Added

    GET /api/payouts/quote — new optional query parameter `recipientType=wise_email_recipient`. When present, the `quote.transferFee` in the response is always `0` and `quote.targetAmount` reflects the full EUR amount with no transfer fee deducted. All other fees (platform fee, fixed EUR fee) are unchanged.

v1.3.4
  • Changed

    GET /api/payouts/quote — fee fields now vary by account type. Standard accounts: `serviceFee` (USD deducted before conversion) + `serviceFeePercent` (%). Developer fee accounts: `serviceFeePercent` only (`serviceFee` is 0, developer fee is embedded in the exchange rate). When a fixed EUR fee applies at settlement, the new optional field `serviceFeeFixed` is included in the response (null/absent otherwise). `netUsdAmount` is the full `sourceAmount` for developer fee accounts (no USD deduction), or `sourceAmount − serviceFee` for standard accounts.

  • Removed

    GET /api/payouts/fee — response no longer includes `isDiscounted` or `capApplied` fields. The fee is now always derived from the global platform rate; account-specific fees are shown in the quote response instead.

v1.3.3
  • Changed

    POST /api/payouts/recipients — `details.address.country` is now strictly validated as ISO 3166-1 alpha-2 (two letters, e.g. `US`, `GB`, `DE`). Country names (`"United States"`) and ISO-3 codes (`"USA"`) are rejected with `400`. Previously, malformed country values silently coerced to null inside the sanctions screen, making the country signal a no-op. Address-less recipient types (e.g. `type: email`) are unaffected — the requirement only applies when `details.address` is present.

  • Changed

    PATCH /api/payouts/recipients/{id} — same ISO 3166-1 alpha-2 validation applies when a PATCH includes a new `details.address`. Patches that don't touch the address are unaffected.

v1.3.2
  • Changed

    POST /api/payouts/recipients — the sanctions screen now matches on three signals: accountHolderName, details.address.country (ISO 2-letter code), and details.address (street + city + postal-code token overlap). A request is rejected only if all available signals match the same sanctioned entity. This significantly reduces false positives for common names (e.g. 'John Smith') while preserving compliance coverage: 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.

  • Changed

    PATCH /api/payouts/recipients/{id} — name+country+address screening on rename. Country and address come from the new details.address when provided, otherwise from the existing recipient. Same algorithm as the POST endpoint.

v1.3.1
  • Added

    POST /api/payouts/recipients — the accountHolderName field is now screened against a sanctions database before the recipient is created. Matches return a 403 with the error message 'Recipient failed compliance screening' and no recipient is created. Comparison is case-folded and accent-stripped against the full set of sanctioned names and aliases. When the sanctions database is unavailable, the endpoint returns a 503 so callers should retry.

  • Added

    PATCH /api/payouts/recipients/{id} — when accountHolderName is included in an update, the new value is screened the same way. Matches return a 403 and the recipient is left unchanged. Updates that change only `details` (no name change) are not affected.

v1.3.0
  • Added

    POST /api/payouts/transfer — every request is now pre-screened for wallet compliance. Before any other processing, the supplied wallet_address is checked against a third-party risk database. Wallets that fail screening are rejected with a 403 error and no transfer is created. When the screening service is unavailable, the endpoint returns a 503 so callers should retry.

  • Removed

    POST /api/payouts/transfer — wallet_address no longer accepts Solana base58 public keys or Tron addresses. Only EVM addresses (0x + 40 hex chars) are accepted because the compliance screener supports Ethereum-family addresses only. Existing integrations that submitted Solana or Tron addresses will receive a 400 error until further notice.

v1.2.6
  • Added

    GET /api/payouts/deposit-options — now returns USDT on Ethereum as a supported deposit option.

  • Added

    POST /api/payouts/transfer — source_token=usdt is now accepted with source_network=ethereum. Use a standard EVM wallet_address (0x + 40 hex chars) for Ethereum.

v1.2.5
  • Changed

    POST /api/payouts/transfer — source_token and source_network are now REQUIRED fields. Previously they were optional and defaulted silently to usdc and base respectively. Requests that omit either field will now receive a 400 error immediately. Update any existing integrations that relied on the implicit defaults.

  • Added

    GET /api/payouts/deposit-options — now returns USDT on Tron in addition to the existing USDC options.

  • Changed

    POST /api/payouts/transfer — source_token now accepts usdt (Tron network only) in addition to usdc. Use GET /api/payouts/deposit-options for the full list of supported token/network combinations.

v1.2.4
  • Added

    POST /api/payouts/transfer — source_network now accepts `avalanche` as a valid value.

  • Removed

    POST /api/payouts/transfer — source_network no longer accepts `optimism` (not supported by the underlying transfer rails).

  • Fixed

    POST /api/payouts/transfer — USDC transfers with source_network=solana were silently rejected by the deposit provider because the network identifier was being sent as "solana" instead of the required "sol". This has been corrected. Solana transfers that previously failed with a provider error will now succeed.

v1.2.3
  • Changed

    POST /api/payouts/transfer — wallet_address now accepts Solana base58-encoded public keys (32–44 chars) when source_network is `solana`, in addition to the existing EVM format (0x + 40 hex chars) for EVM networks.

v1.2.2
  • Changed

    GET /api/payouts/quote and GET /api/payouts/account-requirements — currency support reduced from 164 to 82 verified currencies. Currencies that returned errors on the EUR → target corridor have been removed. Supported currencies: AED, ALL, ARS, AUD, BAM, BDT, BGN, 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.

v1.2.1
  • Changed

    GET /api/payouts/quote and GET /api/payouts/account-requirements — expanded currency support from ~45 currencies to all 164 currencies in the Wise /v1/currencies list. Previously unsupported codes (e.g. GEL, UAH, KRW, TWD, ARS, CLP, COP, CRC, PEN, UYU, ZMW, BWP, ETB, GHS update, TND, MAD update, and many more) are now accepted. The payout route is EUR → target currency via Wise; if a corridor is unavailable on your Wise account the API returns a descriptive error from Wise.

v1.2.0
  • Changed

    POST /api/payouts/transfer — the deposit_address returned is now on the network specified by source_network, not always on Base. The wallet_address field must be a valid address on that same network. EVM networks (arbitrum, avalanche, base, ethereum, polygon) require a 0x + 40 hex char address.

v1.1.0
  • Added

    POST /api/payouts/transfer — new required field `wallet_address` (Ethereum address, 0x + 40 hex chars). Specifies the address that will send USDC to the deposit address. Returned in the response of both POST /api/payouts/transfer and GET /api/payouts/transfer/:id.