curl --request POST \
--url https://api.rheon.io/v1/virtual-accounts/{id}/payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"chain": 42161,
"fiatCurrency": "BRL",
"beneficiary": {
"name": "Ana Ribeiro",
"schema": "bank_sepa"
},
"fiatAmount": "100",
"cryptoAmount": "1000.00",
"reference": "payout-2026-09-02-115"
}
'import requests
url = "https://api.rheon.io/v1/virtual-accounts/{id}/payouts"
payload = {
"chain": 42161,
"fiatCurrency": "BRL",
"beneficiary": {
"name": "Ana Ribeiro",
"schema": "bank_sepa"
},
"fiatAmount": "100",
"cryptoAmount": "1000.00",
"reference": "payout-2026-09-02-115"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
chain: 42161,
fiatCurrency: 'BRL',
beneficiary: {name: 'Ana Ribeiro', schema: 'bank_sepa'},
fiatAmount: '100',
cryptoAmount: '1000.00',
reference: 'payout-2026-09-02-115'
})
};
fetch('https://api.rheon.io/v1/virtual-accounts/{id}/payouts', 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.rheon.io/v1/virtual-accounts/{id}/payouts",
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([
'chain' => 42161,
'fiatCurrency' => 'BRL',
'beneficiary' => [
'name' => 'Ana Ribeiro',
'schema' => 'bank_sepa'
],
'fiatAmount' => '100',
'cryptoAmount' => '1000.00',
'reference' => 'payout-2026-09-02-115'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.rheon.io/v1/virtual-accounts/{id}/payouts"
payload := strings.NewReader("{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.rheon.io/v1/virtual-accounts/{id}/payouts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rheon.io/v1/virtual-accounts/{id}/payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}"
response = http.request(request)
puts response.read_body{
"environment": "sandbox",
"mock": true,
"id": "bpo_9f0c4e2a18b7d3c6",
"depositAddress": "0x0000000000000000000000000000000000000000",
"depositAmount": "1234567.89",
"chain": 42161,
"fiatAmount": "1234567.89",
"rail": "pix",
"status": "pending"
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}Pay out from a virtual account
A bank transfer for a user who already holds a virtual account. The account never holds a fiat balance, so this is not a withdrawal: the user’s wallet sends stablecoins to the address answered here and the beneficiary is paid in fiat. Poll it on POST /v1/bank/payout/status.
The provider behind this endpoint is currently being connected, docs updating. Until then every answer carries mock: true, every money figure is the placeholder 1234567.89, and ids and shapes are real and deterministic from your input.
curl --request POST \
--url https://api.rheon.io/v1/virtual-accounts/{id}/payouts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"chain": 42161,
"fiatCurrency": "BRL",
"beneficiary": {
"name": "Ana Ribeiro",
"schema": "bank_sepa"
},
"fiatAmount": "100",
"cryptoAmount": "1000.00",
"reference": "payout-2026-09-02-115"
}
'import requests
url = "https://api.rheon.io/v1/virtual-accounts/{id}/payouts"
payload = {
"chain": 42161,
"fiatCurrency": "BRL",
"beneficiary": {
"name": "Ana Ribeiro",
"schema": "bank_sepa"
},
"fiatAmount": "100",
"cryptoAmount": "1000.00",
"reference": "payout-2026-09-02-115"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
chain: 42161,
fiatCurrency: 'BRL',
beneficiary: {name: 'Ana Ribeiro', schema: 'bank_sepa'},
fiatAmount: '100',
cryptoAmount: '1000.00',
reference: 'payout-2026-09-02-115'
})
};
fetch('https://api.rheon.io/v1/virtual-accounts/{id}/payouts', 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.rheon.io/v1/virtual-accounts/{id}/payouts",
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([
'chain' => 42161,
'fiatCurrency' => 'BRL',
'beneficiary' => [
'name' => 'Ana Ribeiro',
'schema' => 'bank_sepa'
],
'fiatAmount' => '100',
'cryptoAmount' => '1000.00',
'reference' => 'payout-2026-09-02-115'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.rheon.io/v1/virtual-accounts/{id}/payouts"
payload := strings.NewReader("{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.rheon.io/v1/virtual-accounts/{id}/payouts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rheon.io/v1/virtual-accounts/{id}/payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"chain\": 42161,\n \"fiatCurrency\": \"BRL\",\n \"beneficiary\": {\n \"name\": \"Ana Ribeiro\",\n \"schema\": \"bank_sepa\"\n },\n \"fiatAmount\": \"100\",\n \"cryptoAmount\": \"1000.00\",\n \"reference\": \"payout-2026-09-02-115\"\n}"
response = http.request(request)
puts response.read_body{
"environment": "sandbox",
"mock": true,
"id": "bpo_9f0c4e2a18b7d3c6",
"depositAddress": "0x0000000000000000000000000000000000000000",
"depositAmount": "1234567.89",
"chain": 42161,
"fiatAmount": "1234567.89",
"rail": "pix",
"status": "pending"
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}{
"environment": "sandbox",
"error": {
"code": "invalid_request",
"message": "amount must be a decimal string of the token's smallest unit.",
"fields": {
"applicantInfo.nationality": "iso3166_1_alpha2"
}
}
}network from GET /v1/config). Known limits: only the EUR/IBAN rail is wired for the simulated pay-in, and identity checks run on test-mode applicants. Every response says which environment answered in its environment field.mock: true: ids, shapes and statuses are real and deterministic from your input, every money figure is the placeholder 1234567.89, and nothing is sent to a provider. Integrate against the shape; do not compute anything from the figures.Authorizations
Your API key, issued by us and shown once at creation.
Path Parameters
The virtual account id.
"va_1"
Body
Chain id the user's wallet sends the stablecoins from. Must be one your key may pay FROM and one the catalogue lists.
x <= 900719925474099142161
ISO 4217 code the beneficiary is credited in. It does not have to match the currency the virtual account receives.
^[A-Za-z]{3}$"BRL"
Always name and schema; the rest of the fields follow the schema.
Show child attributes
Show child attributes
What must arrive, decimal string in major units of fiatCurrency, solved backwards. Send this OR cryptoAmount, never both.
^\d+(\.\d+)?$"100"
What the user gives up, decimal string in major units of cryptoCurrency. Send this OR fiatAmount, never both.
^\d+(\.\d+)?$"1000.00"
Your idempotency key, up to 64 characters. A retry carrying the same value answers the original object instead of creating a second one.
64"payout-2026-09-02-115"
Response
The payout and where to send.
Which environment answered. sandbox: at least one money upstream is the provider's sandbox - no real money moves there, and the card corridor is the sandbox's asset (read GET /v1/config), not the documented production one. production: every configured upstream is real. Derived from the configured upstream hosts at boot, never a flag.
sandbox, production "sandbox"
Always true on this endpoint: the provider behind it is currently being connected, docs updating. Ids, shapes and statuses are real and deterministic from your input; every money figure is the placeholder 1234567.89; nothing is sent to a provider. The field disappears the day the provider is wired, so branch on its presence, not its value.
true
The payout id. Poll it on POST /v1/bank/payout/status - virtual account payouts share that endpoint and vocabulary.
"bpo_9f0c4e2a18b7d3c6"
Where the user sends the stablecoins. While the payout providers are being connected this is the zero address and nothing must be sent to it; wired, it is a single-use address derived per payout.
^0x[0-9a-fA-F]{40}$"0x0000000000000000000000000000000000000000"
Placeholder while the provider is being connected: always 1234567.89, never a price. Do not compute anything from it. When the provider is wired this becomes a decimal string in major units.
"1234567.89""1234567.89"
The chain depositAddress lives on - the one the request named.
x <= 900719925474099142161
Placeholder while the provider is being connected: always 1234567.89, never a price. Do not compute anything from it. When the provider is wired this becomes a decimal string in major units.
"1234567.89""1234567.89"
The fastest published payout rail for fiatCurrency, as GET /v1/limits names it.
"pix"
pending: created, waiting for the user's stablecoin at the deposit address. converting: the stablecoin landed and is being converted. sent: left the provider, NOT arrived. settled: reached the card or bank account - the one state to tell the user about. rejected: the provider refused it. returned: sent and came back. While the payout providers are being connected every payout answers pending.
pending, converting, sent, settled, rejected, returned "pending"