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 scopeexternal.apis (authorizer: walletos-account-service-{stage}-authorizer).
Rate limit: 1 withdrawal per minute per user (and IP). Returns 429 if exceeded.
How it works
- Auth – Request must include a valid Bearer token;
claims.subis the entity (user/business) making the withdrawal. - Linked account –
linkedAccountIdmust exist and its wallet must be associated with that entity. - Validation – Body is validated (amount, currency, walletType, reason; MFA is not required for this external endpoint).
- Balance – Wallet balance must be at least
amount + fees. - Payout – Debit wallet, create transaction, and process payout. If the linked account is a bank and
routingTypeis RTP, real-time payment (RTP) is used; otherwise ACH is used. For card linked accounts, push-to-card is used regardless ofroutingType.
Endpoint
- POST
{{LIQUIDITY_URL}}/v1/ext/linked-accounts/:currency/:linkedAccountId/withdrawal - Auth: Bearer
{{accessToken}}(scopeexternal.apis)
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
currency | string | Yes | Currency code. Supported: USD only. |
linkedAccountId | string | Yes | UUID 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
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <token> with scope external.apis. |
Content-Type | Yes | application/json. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Payout amount (e.g. dollars for USD). Must be > 0. |
walletType | string | Yes | Must be "SPEND". Withdrawal is from the SPEND wallet tied to the linked account. |
reason | string | Yes | Reason or reference for the payout (e.g. “Contractor payment”, “Payout”). |
routingType | string | No | Allowed 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). |
transactionType | string | No | If "payout" (case-insensitive), treated as PAYMENT; otherwise flow is PAYOUT. |
mfaCode.
Request Examples
RTP bank payout
ACH bank payout
Card payout (routingType optional)
Supported Payout Types
| Linked account type | routingType (optional) | Result |
|---|---|---|
| BANK | RTP | Real-time payment (RTP) to bank. |
| BANK | ACH or BANK or omitted | ACH transfer to bank. |
| BANK | WIRE | Wire-style handling (method-dependent). |
| CARD | any or omitted | Push-to-card. routingType is ignored. |
routingType values: ACH, RTP, WIRE, BANK only. Any other value returns a validation error.
Response
Success (200)
- Queued means the withdrawal was accepted and processing started. Final status may be delivered via webhooks or a separate status API.
data.amountis the total debited from the wallet (payout amount + fees).
Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the request succeeded. |
message | string | Status message (e.g. “Payment queued”). |
data.id | string | Transaction UUID for tracking. |
data.status | string | Transaction status (e.g. SUCCESS, PENDING). |
data.fees | string | Fee amount as string. |
data.amount | string | Total debit amount (amount + fees) as string. |
Error (4xx / 5xx)
Typical Error Cases
| Situation | HTTP | Message / cause |
|---|---|---|
| Invalid or missing body | 400 | Validation errors (e.g. missing amount, walletType, reason). |
| Invalid routingType | 400 | Only ACH, RTP, WIRE, BANK are allowed. |
| Unauthorized / wrong scope | 401 | Invalid or expired token, or missing external.apis scope. |
| Linked account not found | 404 | Wrong ID or linked account not accessible. |
| Application token expired | 400 | When using a payment-link token that is expired/inactive. |
| Amount mismatch (payment link) | 400 | When withdrawal is merchant-funded and amount ≠ token amount. |
| Insufficient balance | 400 | "Insufficient Funds #912". |
| Rate limit | 429 | 1 withdrawal per minute per user. |
| Server / processing error | 500 | "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.
- MFA – Not required for this external endpoint. Do not send
mfaCode.
Payment-Link / Merchant-Funded Withdrawals
If the request is made in the context of a payment link (application token) withwithdrawalSource === '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
- Linked account first – Ensure the payout destination is already linked (e.g. via the link-account widget/API) and that you have the correct
linkedAccountIdfor the entity’s SPEND wallet. - No MFA – This external payout endpoint does not require MFA. Omit
mfaCodefrom the request. - RTP for faster bank payouts – For bank linked accounts, send
routingType: "RTP"to use real-time payment when supported. - Fees – Fees depend on payout method and configuration. The response
data.feesanddata.amountreflect the fee and total debit. - 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.
- Rate limit – 1 request per minute per user (and IP). Design retries and UX accordingly.
- 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.