Set up a payout account — Step 1
curl --request POST \
--url https://api.example.com/v1/payout/accounts/setup/liquidation-address \
--header 'Content-Type: application/json' \
--data '
{
"beneficiaryId": "<string>",
"beneficiaryAccountId": "<string>",
"chain": "<string>",
"currency": "<string>"
}
'import requests
url = "https://api.example.com/v1/payout/accounts/setup/liquidation-address"
payload = {
"beneficiaryId": "<string>",
"beneficiaryAccountId": "<string>",
"chain": "<string>",
"currency": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
beneficiaryId: '<string>',
beneficiaryAccountId: '<string>',
chain: '<string>',
currency: '<string>'
})
};
fetch('https://api.example.com/v1/payout/accounts/setup/liquidation-address', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/payout/accounts/setup/liquidation-address",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'beneficiaryId' => '<string>',
'beneficiaryAccountId' => '<string>',
'chain' => '<string>',
'currency' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/payout/accounts/setup/liquidation-address"
payload := strings.NewReader("{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/payout/accounts/setup/liquidation-address")
.header("Content-Type", "application/json")
.body("{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/payout/accounts/setup/liquidation-address")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"address": "<string>",
"chain": "<string>",
"currency": "<string>"
}Global Payout
Set up a payout account — Step 1
Step 1 of the 2-step payout account setup. Creates a crypto withdrawal address linked to a recipient’s bank account.
POST
/
v1
/
payout
/
accounts
/
setup
/
liquidation-address
Set up a payout account — Step 1
curl --request POST \
--url https://api.example.com/v1/payout/accounts/setup/liquidation-address \
--header 'Content-Type: application/json' \
--data '
{
"beneficiaryId": "<string>",
"beneficiaryAccountId": "<string>",
"chain": "<string>",
"currency": "<string>"
}
'import requests
url = "https://api.example.com/v1/payout/accounts/setup/liquidation-address"
payload = {
"beneficiaryId": "<string>",
"beneficiaryAccountId": "<string>",
"chain": "<string>",
"currency": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
beneficiaryId: '<string>',
beneficiaryAccountId: '<string>',
chain: '<string>',
currency: '<string>'
})
};
fetch('https://api.example.com/v1/payout/accounts/setup/liquidation-address', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/payout/accounts/setup/liquidation-address",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'beneficiaryId' => '<string>',
'beneficiaryAccountId' => '<string>',
'chain' => '<string>',
'currency' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/payout/accounts/setup/liquidation-address"
payload := strings.NewReader("{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/payout/accounts/setup/liquidation-address")
.header("Content-Type", "application/json")
.body("{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/payout/accounts/setup/liquidation-address")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"beneficiaryId\": \"<string>\",\n \"beneficiaryAccountId\": \"<string>\",\n \"chain\": \"<string>\",\n \"currency\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"address": "<string>",
"chain": "<string>",
"currency": "<string>"
}This endpoint is step 1 of the 2-step payout account setup for off-ramp settlement. It links a recipient’s bank account to a crypto withdrawal address. When stablecoins land at this address, the off-ramp transfer to the recipient’s bank is triggered automatically.
Typical timeline: stablecoin deposit detected → confirmed on-chain → fiat transfer submitted → fiat received at recipient bank. End-to-end takes 1–2 business days via SPEI.
1
Step 1 — POST /v1/payout/accounts/setup/liquidation-address (this endpoint)
Creates or returns the withdrawal address for
(beneficiaryId, beneficiaryAccountId, chain, currency). Idempotent — calling twice returns the same address.2
Step 2 — POST /v1/payout/accounts/setup/virtual-account
Creates a fiat virtual account (e.g. a SPEI CLABE in Mexico) tied to the withdrawal address. Returns the account number your client deposits fiat into. Also idempotent.
3
Operate — quote and execute
Use
POST /v1/payout/accounts/quote and POST /v1/payout/accounts/execute to move money. Track via GET /v1/orders/{orderId}.Both setup endpoints are idempotent. You can call them on every order without worrying about duplicates. This is the recommended pattern: don’t cache setup IDs in your backend, just call setup before each payment.
Prerequisites
- Workspace KYB completed (one-time, via dashboard)
- Recipient created (
POST /v1/payout/recipients) - Recipient’s bank account added (
POST /v1/payout/recipients/{id}/accountsor via invite link)
Request body
string
required
Recipient ID (from
POST /v1/payout/recipients).string
required
The recipient’s bank account ID (from
POST /v1/payout/recipients/{id}/accounts).string
required
Stablecoin chain. Supported:
base, polygon, arbitrum, solana. More on request.string
required
Stablecoin symbol on the chosen chain. Supported:
usdc, usdt.Response
string
Internal withdrawal address ID. You generally don’t need to store this — call setup again to retrieve.
string
The crypto address. Send stablecoins here to trigger an off-ramp settlement to the recipient’s bank.
string
Echo of the chain you requested.
string
Echo of the stablecoin you requested.
Each
(beneficiaryAccountId, chain, currency) triple maps to one withdrawal address forever. Different recipients get different addresses. Don’t reuse addresses across recipients.What happens after a deposit?
When stablecoins arrive at the returnedaddress, the off-ramp partner detects the deposit, settles the on-chain leg, and initiates the fiat transfer to the recipient’s bank. Track the full lifecycle as an order:
GET /v1/orders — list all orders
GET /v1/orders/{orderId} — get a single order with timeline
GET /v1/payout/recipients/{id}/orders — orders for this recipient
Authentication
Authorization: ApiKey rk_client_key_v1_... — workspace key with payout module enabled and payout:write scope.
Common errors
| Status | Cause | Fix |
|---|---|---|
400 | Unsupported chain + currency combination | See /v1/action/tokens/{chain}/usdc for supported pairs |
403 | payout module not active for workspace | Activate Payout Kit in the dashboard |
404 | beneficiaryId or beneficiaryAccountId not found, or belongs to another workspace | Cross-tenant lookups always return 404 (no existence leak) |
422 | Recipient’s bank account is not yet usable (pending registration) | Wait for the rails partner to confirm the bank account, then retry |
Next steps
POST /v1/payout/accounts/setup/virtual-account— Step 2: create the fiat virtual account- Payout Kit Flow Guide — full end-to-end narrative with code samples
POST /v1/payout/accounts/quotethenPOST /v1/payout/accounts/execute— operate once setup is complete