Payouts
A payout sends money from your balance to a mobile money wallet or a bank account. The amount plus the fee is reserved when you create it, and returned if the payout fails.
The payout object
Attributes
Unique identifier (UUID).
true for live objects, false for test objects.
Always payout.
succeeded and failed are final. On failure, the amount and the fee return to your balance.
pendingprocessingsucceededfailedAmount the recipient receives, in minor units.
ISO 4217 currency code.
Monerail fee, debited from your balance on top of amount.
Who gets paid. Bank account numbers are masked.
Show child attributes
mobile_moneybankRecipient name.
Mobile money only: operator code.
Mobile money only: number, international format.
Bank only: masked account number.
Country code.
Your description.
Your own reference, as sent at creation.
Key-value pairs you attached at creation.
Why it failed, when status is failed or expired.
Detail from the payment partner, when available.
Creation time, ISO 8601 (UTC).
Last change, ISO 8601 (UTC).
{
"id": "7b05e2b6-6638-4088-a984-7155dfd6a036",
"object": "payout",
"livemode": false,
"status": "pending",
"amount": 150000,
"currency": "XAF",
"fee": 2250,
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"operator": "MTN",
"phone": "237670000777",
"country": "CM"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
"metadata": {},
"failure_code": null,
"failure_message": null,
"created_at": "2026-10-30T13:47:06.394Z",
"updated_at": "2026-10-30T13:47:06.394Z"
}Create a payout
Reserves amount + fee on your balance and sends the payout. Fails with 402 balance_insufficient if your balance does not cover both. Returns 201, or 202 with status: "processing" if the payment partner has not confirmed receipt yet: do not create the payout again, its outcome arrives by webhook.
Key roles: Owner, Finance or Approver
Body
Amount in minor units of currency (whole francs for XAF, XOF and GNF; centimes for CDF, in multiples of 100). Integer only.
ISO 4217 code.
XAFXOFGNFCDFNGNKESGHSUGXTZSRWFWho gets paid.
Show child attributes
mobile_moneybankRecipient name, 2 to 80 characters.
Mobile money: recipient number.
Mobile money: operator code.
Bank: a code from List banks, exactly as returned. Names and partial codes are refused with 400 bank_account_invalid.
Bank: account number.
ISO 3166-1 alpha-2. Defaults to the main country of the currency (CM for XAF, SN for XOF, GN for GNF, CD for CDF). Bank payouts are available in the countries listed with bank transfer in Coverage.
Up to 500 characters.
Your own reference, up to 64 characters: letters, digits and _ . : / -.
Up to 20 key-value pairs. Keys up to 40 characters (A-Z a-z 0-9 _ . -), values are stored as strings up to 500 characters.
Returns
Returns the new payout object with 201, or 202 when the outcome is not final yet.
Errors worth handling
| HTTP | Code | Description |
|---|---|---|
402 | balance_ | Your balance (or cards balance) does not cover the amount and the fee. |
400 | bank_ | bank_code does not exactly match a code from List banks, or the account number is too short. |
400 | bank_ | Bank payouts are not available in this country: pay to a mobile money wallet. |
400 | country_ | This country is not open for this currency. See Coverage. |
400 | test_ | Test mode: use one of the test phone numbers of this country. See Testing. |
402 | recipient_ | The payment partner rejected the recipient. |
403 | permission_ | The key’s creator does not have the role required for this operation. |
429 | rate_ | A rate limit was reached. Wait for Retry-After seconds. See Rate limits. |
503 | provider_ | The payment partner cannot process this payout right now. Retry later. |
curl https://api.monerail.com/v1/payouts \
-H "Authorization: Bearer $MONERAIL_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45" \
-d '{
"amount": 150000,
"currency": "XAF",
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"phone": "237670000777",
"operator": "MTN"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118"
}'const res = await fetch('https://api.monerail.com/v1/payouts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MONERAIL_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45',
},
body: JSON.stringify({
amount: 150000,
currency: 'XAF',
recipient: {
type: 'mobile_money',
name: 'Kossi Mensah',
phone: '237670000777',
operator: 'MTN',
},
description: 'Invoice 2026-118',
client_reference: 'inv_118',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.monerail.com/v1/payouts",
headers={"Authorization": f"Bearer {os.environ['MONERAIL_KEY']}", "Idempotency-Key": "a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45"},
json={
"amount": 150000,
"currency": "XAF",
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"phone": "237670000777",
"operator": "MTN",
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data)<?php
$ch = curl_init('https://api.monerail.com/v1/payouts');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MONERAIL_KEY'),
'Content-Type: application/json',
'Idempotency-Key: a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 150000,
'currency' => 'XAF',
'recipient' => [
'type' => 'mobile_money',
'name' => 'Kossi Mensah',
'phone' => '237670000777',
'operator' => 'MTN',
],
'description' => 'Invoice 2026-118',
'client_reference' => 'inv_118',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, err := json.Marshal(map[string]any{
"amount": 150000,
"currency": "XAF",
"recipient": map[string]any{
"type": "mobile_money",
"name": "Kossi Mensah",
"phone": "237670000777",
"operator": "MTN",
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, "https://api.monerail.com/v1/payouts", bytes.NewReader(payload))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("MONERAIL_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(fmt.Sprint(data["error"]))
}
fmt.Println(data)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
String body = """
{
"amount": 150000,
"currency": "XAF",
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"phone": "237670000777",
"operator": "MTN"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.monerail.com/v1/payouts"))
.header("Authorization", "Bearer " + System.getenv("MONERAIL_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException(response.body());
}
System.out.println(response.body()); // parse with your JSON library (Jackson, Gson…)
}
}require "json"
require "net/http"
uri = URI("https://api.monerail.com/v1/payouts")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("MONERAIL_KEY")}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = "a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45"
req.body = JSON.generate({
amount: 150000,
currency: "XAF",
recipient: {
type: "mobile_money",
name: "Kossi Mensah",
phone: "237670000777",
operator: "MTN",
},
description: "Invoice 2026-118",
client_reference: "inv_118",
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
raise "#{data["error"]["code"]}: #{data["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
puts datausing System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("MONERAIL_KEY"));
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.monerail.com/v1/payouts");
request.Headers.Add("Idempotency-Key", "a1f3c9e2-5b7d-4c21-9f80-3e6b2d1c7a45");
request.Content = JsonContent.Create(new
{
amount = 150000,
currency = "XAF",
recipient = new
{
type = "mobile_money",
name = "Kossi Mensah",
phone = "237670000777",
@operator = "MTN",
},
description = "Invoice 2026-118",
client_reference = "inv_118",
});
using var response = await http.SendAsync(request);
var data = await response.Content.ReadFromJsonAsync<JsonElement>();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(data.GetProperty("error").GetProperty("message").GetString());
}
Console.WriteLine(data);{
"id": "7b05e2b6-6638-4088-a984-7155dfd6a036",
"object": "payout",
"livemode": false,
"status": "pending",
"amount": 150000,
"currency": "XAF",
"fee": 2250,
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"operator": "MTN",
"phone": "237670000777",
"country": "CM"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
"metadata": {},
"failure_code": null,
"failure_message": null,
"created_at": "2026-10-30T13:47:06.394Z",
"updated_at": "2026-10-30T13:47:06.394Z"
}Retrieve a payout
If the payout is still on its way, Monerail first asks the payment partner for news.
Key roles: Every role
Path parameters
ID of the payout.
Returns
Returns the payout object.
curl https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036 \
-H "Authorization: Bearer $MONERAIL_KEY"const res = await fetch('https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036', {
headers: {
Authorization: `Bearer ${process.env.MONERAIL_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036",
headers={"Authorization": f"Bearer {os.environ['MONERAIL_KEY']}"},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data)<?php
$ch = curl_init('https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MONERAIL_KEY'),
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("MONERAIL_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(fmt.Sprint(data["error"]))
}
fmt.Println(data)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036"))
.header("Authorization", "Bearer " + System.getenv("MONERAIL_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException(response.body());
}
System.out.println(response.body()); // parse with your JSON library (Jackson, Gson…)
}
}require "json"
require "net/http"
uri = URI("https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("MONERAIL_KEY")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
raise "#{data["error"]["code"]}: #{data["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
puts datausing System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("MONERAIL_KEY"));
using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.monerail.com/v1/payouts/7b05e2b6-6638-4088-a984-7155dfd6a036");
using var response = await http.SendAsync(request);
var data = await response.Content.ReadFromJsonAsync<JsonElement>();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(data.GetProperty("error").GetProperty("message").GetString());
}
Console.WriteLine(data);{
"id": "7b05e2b6-6638-4088-a984-7155dfd6a036",
"object": "payout",
"livemode": false,
"status": "succeeded",
"amount": 150000,
"currency": "XAF",
"fee": 2250,
"recipient": {
"type": "mobile_money",
"name": "Kossi Mensah",
"operator": "MTN",
"phone": "237670000777",
"country": "CM"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
"metadata": {},
"failure_code": null,
"failure_message": null,
"created_at": "2026-10-30T13:47:06.394Z",
"updated_at": "2026-10-30T13:47:09.807Z"
}List payouts
Newest first.
Key roles: Every role
Query parameters
20Number of objects to return, 1 to 100.
Cursor: the ID of the last object of the previous page.
Returns
Returns a list object: data holds up to limit payout objects, and has_more tells whether more follow.
curl "https://api.monerail.com/v1/payouts?limit=10" \
-H "Authorization: Bearer $MONERAIL_KEY"const res = await fetch('https://api.monerail.com/v1/payouts?limit=10', {
headers: {
Authorization: `Bearer ${process.env.MONERAIL_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.monerail.com/v1/payouts",
headers={"Authorization": f"Bearer {os.environ['MONERAIL_KEY']}"},
params={"limit": 10},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data)<?php
$ch = curl_init('https://api.monerail.com/v1/payouts?limit=10');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MONERAIL_KEY'),
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.monerail.com/v1/payouts?limit=10", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("MONERAIL_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var data map[string]any
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(fmt.Sprint(data["error"]))
}
fmt.Println(data)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.monerail.com/v1/payouts?limit=10"))
.header("Authorization", "Bearer " + System.getenv("MONERAIL_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException(response.body());
}
System.out.println(response.body()); // parse with your JSON library (Jackson, Gson…)
}
}require "json"
require "net/http"
uri = URI("https://api.monerail.com/v1/payouts?limit=10")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("MONERAIL_KEY")}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
raise "#{data["error"]["code"]}: #{data["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
puts datausing System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("MONERAIL_KEY"));
using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.monerail.com/v1/payouts?limit=10");
using var response = await http.SendAsync(request);
var data = await response.Content.ReadFromJsonAsync<JsonElement>();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(data.GetProperty("error").GetProperty("message").GetString());
}
Console.WriteLine(data);{
"object": "list",
"url": "/v1/payouts",
"data": [
{
"id": "7b05e2b6-6638-4088-a984-7155dfd6a036",
"object": "payout",
"livemode": false,
"status": "pending",
"amount": 150000,
"currency": "XAF",
"fee": 2250,
"recipient": {
"type": "bank",
"name": "Kossi Mensah",
"bank_code": "10005",
"account": "••••4821",
"country": "CM"
},
"description": "Invoice 2026-118",
"client_reference": "inv_118",
"metadata": {},
"failure_code": null,
"failure_message": null,
"created_at": "2026-10-30T13:47:06.394Z",
"updated_at": "2026-10-30T13:47:06.394Z"
}
],
"has_more": false
}