API reference
Payouts
Pay out from your app pool to any wallet address. External wallets are paid on-chain; wallets on our network are settled internally — instantly and without a network fee.
Pay out to any address
The destination decides the route
External wallet
201 · PENDINGPaid on-chain. The response is 201 with status PENDING, which becomes SUCCESS once the transaction is confirmed. The asset's network fee (withdrawal_fee in total-balance) is taken from the pool together with the amount.
Wallet on our network
201 · SUCCESSSettled internally — instantly, with no network fee. The response is 201 with status SUCCESS. Your app's internal transfer fee applies.
Addresses that can't receive
400 · INVALID_ADDRESSReserved addresses and customer deposit addresses — your own customers' or another app's — can't be paid. The request fails with INVALID_ADDRESS.
Endpoint
/api/partner/{app_id}/transfer/Pays from the pool to to_address and returns the payout object. POST /withdrawals/ is an alias that behaves the same way.
Path parameters
app_idstringrequiredYour app ID (UUID), shown on the app's Developer tab.
Body parameters
to_addressstringrequiredDestination Solana address — an external wallet or a wallet on our network.amountdecimal stringrequiredAmount as a positive decimal string. Up to 6 decimals for USDT and 9 for SOL.referencestringYour payout reference — recommended, unique per app, up to 128 characters. Sending the same reference again returns the original result without paying again.asset_idintegerAsset ID. Optional — defaults to USDT on Solana. SOL and USDT are supported.user_idstringCustomer user_id. Optional — records which customer the payout is for. It doesn't debit the customer's balance; use balance/adjust for that.
Request example
curl -X POST "https://api.moallet.com/api/partner/{app_id}/transfer/" \
-H "Authorization: ApiKey {client_id}:{client_secret}" \
-H "Content-Type: application/json" \
-d '{
"to_address": "5DmYQC3fLxHUpmYbYuRkHFQ2yH4H3kP3VpYWgPMbpnGk",
"amount": "50",
"reference": "payout-20261022-0001",
"user_id": "customer-1001"
}'Response — external wallet (on-chain)
{
"success": true,
"data": {
"id": "2a1f7a03-9b2b-4fa1-9f77-33f8a09e8a12",
"asset": 2,
"asset_symbol": "USDT",
"network_name": "Solana Mainnet",
"amount": "50.000000000000000000",
"to_user_id": "customer-1001",
"error_message": "",
"created_at": "2026-10-22T10:15:22Z",
"reference": "payout-20261022-0001",
"to_address": "5DmYQC3fLxHUpmYbYuRkHFQ2yH4H3kP3VpYWgPMbpnGk",
"status": "PENDING",
"tx_hash": "",
"fee": "1.000000000000000000",
"processed_at": null,
"is_external_payout": true
},
"error": null
}Response — wallet on our network (instant)
{
"success": true,
"data": {
"id": "5c0d1e44-0f6a-4b1e-a3c2-9d1f0b7e2c55",
"asset": 2,
"asset_symbol": "USDT",
"network_name": "Solana Mainnet",
"amount": "50.000000000000000000",
"to_user_id": "customer-1001",
"error_message": "",
"created_at": "2026-10-22T10:15:22Z",
"reference": "payout-20261022-0001",
"to_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"status": "SUCCESS",
"tx_hash": "internal:5c0d1e44-0f6a-4b1e-a3c2-9d1f0b7e2c55",
"fee": "1.000000000000000000",
"processed_at": "2026-10-22T10:15:22Z",
"is_external_payout": false
},
"error": null
}Payout status
PENDINGAccepted, waiting to be sentPROCESSINGSent, waiting for confirmationSUCCESSPaidFAILEDFailed — the amount and fee go back to the poolA 201 response isn't final for on-chain payouts. Poll the payout until it is SUCCESS or FAILED:
curl "https://api.moallet.com/api/partner/{app_id}/withdrawals/{withdrawal_id}/" \
-H "Authorization: ApiKey {client_id}:{client_secret}"72-hour hold on internal inflows
Errors
INVALID_ADDRESS400Invalid address, or an address that can't receive.INVALID_AMOUNT400The amount isn't positive or has too many decimals.INVALID_REFERENCE400reference is longer than 128 characters.UNSUPPORTED_ASSET400Unsupported asset — SOL and USDT only.INSUFFICIENT_POOL400The pool holds less than amount + fee.WITHDRAWAL_LOCKED400Part of the pool is on the 72-hour hold. data includes locked, available and next_unlock_at.DUPLICATE_REFERENCE_MISMATCH409The reference was already used with different details (amount, address …).ADDRESS_IS_TOKEN_ACCOUNT400The destination is a token account or the token's mint address — send to the owner wallet address.ADDRESS_NOT_ALLOWLISTED403The payout allowlist is on and the destination isn't an active address on it (or it's still in its 24-hour activation period).READ_ONLY_KEY403The request was made with a read-only API key.PARTNER_KYC_REQUIRED403The account behind this app hasn't completed verification.PAYOUT_DISABLED403On-chain payouts are paused for this app. Contact your account manager.
{
"success": false,
"data": {
"requested_total": "101",
"pool": "120.8",
"locked": "40",
"available": "80.8",
"next_unlock_at": "2026-10-25T03:12:00+00:00"
},
"error": {
"code": "WITHDRAWAL_LOCKED",
"message": "Part of the pool is still in the 72-hour hold."
}
}Response fields
Payout object
idstring (UUID)Payout id (UUID).statusstringPayout status (see above).is_external_payoutbooleantrue for an on-chain payout, false for internal settlement.referencestring | nullThe reference you sent (null if none).amountstringAmount paid, in asset units.feestringFee taken — the network fee on-chain, or your app's internal transfer fee.to_addressstringDestination address.to_user_idstringCustomer user_id recorded with the payout.tx_hashstringThe on-chain signature, or an internal: reference for internal settlement.asset · asset_symbol · network_nameinteger · string · stringAsset id, symbol and network name.error_messagestringWhy the payout failed (status FAILED).created_atstring (ISO 8601)When the payout was requested.processed_atstring | nullWhen it finished — at confirmation on-chain, immediately for internal settlement.
Restrict destinations with the payout allowlist — see Security controls.
List and look up payouts with the payout history API.