Skip to main content

Overview

Initiate a payout from an end-user’s SPEND wallet to a linked payout method (bank account or card). The wallet is inferred from the linked account; the caller does not pass a wallet ID. Auth: Bearer token with scope external.apis (authorizer: walletos-account-service-{stage}-authorizer). Rate limit: 1 withdrawal per minute per user (and IP). Returns 429 if exceeded.

How it works

  1. Auth – Request must include a valid Bearer token; claims.sub is the entity (user/business) making the withdrawal.
  2. Linked accountlinkedAccountId must exist and its wallet must be associated with that entity.
  3. Validation – Body is validated (amount, currency, walletType, reason; MFA is not required for this external endpoint).
  4. Balance – Wallet balance must be at least amount + fees.
  5. Payout – Debit wallet, create transaction, and process payout. If the linked account is a bank and routingType is RTP, real-time payment (RTP) is used; otherwise ACH is used. For card linked accounts, push-to-card is used regardless of routingType.
Important: The linked account must have been created first (e.g. via the link-account flow). Supported payout methods include ACH, RTP, WIRE, and card.

Endpoint

  • POST {{LIQUIDITY_URL}}/v1/ext/linked-accounts/:currency/:linkedAccountId/withdrawal
  • Auth: Bearer {{accessToken}} (scope external.apis)

Path Parameters

ParameterTypeRequiredDescription
currencystringYesCurrency code. Supported: USD only.
linkedAccountIdstringYesUUID of the linked payout account (bank or card) to send funds to. Must be a valid UUID of an existing linked account whose wallet is associated with the authenticated entity. See Link Bank Account, Link Card Account, or Link International Bank Account for how to link an account.

Headers

HeaderRequiredDescription
AuthorizationYesBearer <token> with scope external.apis.
Content-TypeYesapplication/json.

Request Body

FieldTypeRequiredDescription
amountnumberYesPayout amount (e.g. dollars for USD). Must be > 0.
walletTypestringYesMust be "SPEND". Withdrawal is from the SPEND wallet tied to the linked account.
reasonstringYesReason or reference for the payout (e.g. “Contractor payment”, “Payout”).
routingTypestringNoAllowed values only: ACH, RTP, WIRE, BANK. Used for bank linked accounts: RTP = real-time payment; ACH or BANK = ACH; WIRE = wire. Ignored for card linked accounts (push-to-card).
transactionTypestringNoIf "payout" (case-insensitive), treated as PAYMENT; otherwise flow is PAYOUT.
Note: MFA is not required for this external payout endpoint. Do not send mfaCode.

Request Examples

RTP bank payout

curl -X POST "{{LIQUIDITY_URL}}/v1/ext/linked-accounts/USD/{{linkedAccountId}}/withdrawal" \
  -H "Authorization: Bearer {{accessToken}}" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100.50,
    "walletType": "SPEND",
    "reason": "Contractor payment",
    "routingType": "RTP"
  }'

ACH bank payout

{
  "amount": 250.00,
  "walletType": "SPEND",
  "reason": "Contractor payment",
  "routingType": "ACH"
}

Card payout (routingType optional)

{
  "amount": 50.00,
  "walletType": "SPEND",
  "reason": "Refund"
}

Supported Payout Types

Linked account typeroutingType (optional)Result
BANKRTPReal-time payment (RTP) to bank.
BANKACH or BANK or omittedACH transfer to bank.
BANKWIREWire-style handling (method-dependent).
CARDany or omittedPush-to-card. routingType is ignored.
Allowed routingType values: ACH, RTP, WIRE, BANK only. Any other value returns a validation error.

Response

Success (200)

{
  "success": true,
  "message": "Payment queued",
  "data": {
    "id": "<transaction-uuid>",
    "status": "<TransactionStatus>",
    "fees": "<fee amount as string>",
    "amount": "<total debit amount (amount + fees) as string>"
  }
}
  • Queued means the withdrawal was accepted and processing started. Final status may be delivered via webhooks or a separate status API.
  • data.amount is the total debited from the wallet (payout amount + fees).

Response Fields

FieldTypeDescription
successbooleanWhether the request succeeded.
messagestringStatus message (e.g. “Payment queued”).
data.idstringTransaction UUID for tracking.
data.statusstringTransaction status (e.g. SUCCESS, PENDING).
data.feesstringFee amount as string.
data.amountstringTotal debit amount (amount + fees) as string.

Error (4xx / 5xx)

{
  "success": false,
  "message": "<error detail>",
  "fatal": true
}

Typical Error Cases

SituationHTTPMessage / cause
Invalid or missing body400Validation errors (e.g. missing amount, walletType, reason).
Invalid routingType400Only ACH, RTP, WIRE, BANK are allowed.
Unauthorized / wrong scope401Invalid or expired token, or missing external.apis scope.
Linked account not found404Wrong ID or linked account not accessible.
Application token expired400When using a payment-link token that is expired/inactive.
Amount mismatch (payment link)400When withdrawal is merchant-funded and amount ≠ token amount.
Insufficient balance400"Insufficient Funds #912".
Rate limit4291 withdrawal per minute per user.
Server / processing error500"Something went wrong, please try again" or processing error.

Validation Rules (Summary)

  • amount – Required, number, > 0.
  • walletType – Required, must be "SPEND".
  • reason – Required, string.
  • routingType – Optional. If present, must be one of: ACH, RTP, WIRE, BANK.
  • currency – From path; must be USD (only supported currency).
  • linkedAccountId – From path; must be a valid UUID of an existing linked account whose wallet is associated with the authenticated entity. See Link Bank Account, Link Card Account, or Link International Bank Account for how to link an account.
  • MFANot required for this external endpoint. Do not send mfaCode.
If the request is made in the context of a payment link (application token) with withdrawalSource === 'MERCHANT_WALLET':
  • amount in the body must equal the amount on the application token; otherwise the response is 400 "Malformed request".
  • On success, the application token is invalidated immediately.

Notes for External Engineers

  1. Linked account first – Ensure the payout destination is already linked (e.g. via the link-account widget/API) and that you have the correct linkedAccountId for the entity’s SPEND wallet.
  2. No MFA – This external payout endpoint does not require MFA. Omit mfaCode from the request.
  3. RTP for faster bank payouts – For bank linked accounts, send routingType: "RTP" to use real-time payment when supported.
  4. Fees – Fees depend on payout method and configuration. The response data.fees and data.amount reflect the fee and total debit.
  5. Async outcome – Success means “queued”; final success/failure may be delivered asynchronously (e.g. webhooks). Use your transaction/withdrawal status APIs or webhooks to track final state.
  6. Rate limit – 1 request per minute per user (and IP). Design retries and UX accordingly.
  7. Idempotency – The API does not expose idempotency keys. Retrying the same request can create multiple withdrawals. Implement your own idempotency (e.g. linkedAccountId + amount + reason + timestamp) if you need to avoid duplicates.