> ## Documentation Index
> Fetch the complete documentation index at: https://developer.madhousewallet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a transfer

> 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.

**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.

**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.

**Prerequisites:**
- Your account must have a deployed Safe wallet.
- Call `GET /api/payouts/quote` within the last 5 minutes — pass the returned `quoteId` as `quote_id`.
- The `recipientId` must be a recipient belonging to your account.

**Concurrent transfers:** Multiple transfers can be in flight simultaneously — each gets a unique deposit address and is settled independently.

**Workflow:**
1. `GET /api/payouts/quote` → save `quoteId`.
2. `POST /api/payouts/transfer` with `quote_id`, `amount`, `recipientId`, `source_network`, `wallet_address` → receive `deposit_address` and `transfer_id`.
3. Send exactly `amount` USDC from `wallet_address` to `deposit_address` on `source_network`.
4. MW detects the deposit and automatically initiates the fiat payout to your recipient — email confirmation sent.

**Rate limit:** 20 requests/minute.



## OpenAPI

````yaml /openapi.json post /api/payouts/transfer
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.


    All 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: []
tags:
  - name: Payouts
    description: USDC-to-fiat payout operations
  - name: Recipients
    description: Payout recipient management
paths:
  /api/payouts/transfer:
    post:
      tags:
        - Payouts
      summary: Create a transfer
      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.


        **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.


        **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.


        **Prerequisites:**

        - Your account must have a deployed Safe wallet.

        - Call `GET /api/payouts/quote` within the last 5 minutes — pass the
        returned `quoteId` as `quote_id`.

        - The `recipientId` must be a recipient belonging to your account.


        **Concurrent transfers:** Multiple transfers can be in flight
        simultaneously — each gets a unique deposit address and is settled
        independently.


        **Workflow:**

        1. `GET /api/payouts/quote` → save `quoteId`.

        2. `POST /api/payouts/transfer` with `quote_id`, `amount`,
        `recipientId`, `source_network`, `wallet_address` → receive
        `deposit_address` and `transfer_id`.

        3. Send exactly `amount` USDC from `wallet_address` to `deposit_address`
        on `source_network`.

        4. MW detects the deposit and automatically initiates the fiat payout to
        your recipient — email confirmation sent.


        **Rate limit:** 20 requests/minute.
      operationId: initiateTransfer
      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'
components:
  schemas:
    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
    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
    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message
      required:
        - error
    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
          description: ISO 3166-1 alpha-2 country code
          example: DE
          nullable: true
        details:
          type: object
          description: Account-type-specific details (routing/account numbers, IBAN, etc.)
      required:
        - id
        - accountHolderName
        - currency
        - type
        - details
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        Pass your API key as a Bearer token: `Authorization: Bearer
        mw_live_<keyId>_<secret>`

````