curl --request POST \
--url https://api.rolla.xyz/api/v1/external/wallet/transfer \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "partner@example.com",
"description": "Invoice payment",
"saveBeneficiary": true
}
'import requests
url = "https://api.rolla.xyz/api/v1/external/wallet/transfer"
payload = {
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "partner@example.com",
"description": "Invoice payment",
"saveBeneficiary": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
amount: 100,
currency: 'USD',
destinationCurrency: 'USD',
recipient: 'partner@example.com',
description: 'Invoice payment',
saveBeneficiary: true
})
};
fetch('https://api.rolla.xyz/api/v1/external/wallet/transfer', 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.rolla.xyz/api/v1/external/wallet/transfer",
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([
'amount' => 100,
'currency' => 'USD',
'destinationCurrency' => 'USD',
'recipient' => 'partner@example.com',
'description' => 'Invoice payment',
'saveBeneficiary' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.rolla.xyz/api/v1/external/wallet/transfer"
payload := strings.NewReader("{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.rolla.xyz/api/v1/external/wallet/transfer")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rolla.xyz/api/v1/external/wallet/transfer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Transfer initiated successfully",
"success": true,
"data": {
"transfer_reference": "TRANSFER-1712345678-abc12345",
"sender_transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction_type": "withdrawal",
"status": "completed",
"description": "Invoice payment",
"external_reference": "TRANSFER-1712345678-abc12345-DEBIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"created_at": "2026-03-23T13:45:28.138Z",
"updated_at": "2026-03-23T13:45:28.263Z",
"metadata": {
"invoice_id": "INV-2026-042"
}
},
"recipient_transaction": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"transaction_type": "deposit",
"status": "completed",
"description": "Transfer from Acme Holdings",
"external_reference": "TRANSFER-1712345678-abc12345-CREDIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"created_at": "2026-03-23T13:45:28.201Z",
"updated_at": "2026-03-23T13:45:28.201Z",
"payer": {
"name": "Acme Holdings",
"bank_name": "Rolla"
}
},
"pending_claim": null
}
}{
"status": 400,
"message": "Validation failed",
"code": "ACCOUNT_RESTRICTED",
"errors": [
{
"path": [
"amount"
],
"message": "amount must be a whole number of smallest currency units (e.g. cents)"
}
]
}{
"status": 400,
"message": "Validation failed",
"code": "ACCOUNT_RESTRICTED",
"errors": [
{
"path": [
"amount"
],
"message": "amount must be a whole number of smallest currency units (e.g. cents)"
}
]
}{
"status": 403,
"message": "This endpoint requires IP whitelisting. Add at least one whitelisted IP to your API key before using withdraw."
}Rolla Transfer
Send funds to another Rolla business instantly and fee-free. Identify the recipient by email, Rolla tag, or a saved beneficiaryId. Requires an API key with at least one whitelisted IP.
curl --request POST \
--url https://api.rolla.xyz/api/v1/external/wallet/transfer \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "partner@example.com",
"description": "Invoice payment",
"saveBeneficiary": true
}
'import requests
url = "https://api.rolla.xyz/api/v1/external/wallet/transfer"
payload = {
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "partner@example.com",
"description": "Invoice payment",
"saveBeneficiary": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
amount: 100,
currency: 'USD',
destinationCurrency: 'USD',
recipient: 'partner@example.com',
description: 'Invoice payment',
saveBeneficiary: true
})
};
fetch('https://api.rolla.xyz/api/v1/external/wallet/transfer', 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.rolla.xyz/api/v1/external/wallet/transfer",
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([
'amount' => 100,
'currency' => 'USD',
'destinationCurrency' => 'USD',
'recipient' => 'partner@example.com',
'description' => 'Invoice payment',
'saveBeneficiary' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.rolla.xyz/api/v1/external/wallet/transfer"
payload := strings.NewReader("{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.rolla.xyz/api/v1/external/wallet/transfer")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rolla.xyz/api/v1/external/wallet/transfer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount\": 100,\n \"currency\": \"USD\",\n \"destinationCurrency\": \"USD\",\n \"recipient\": \"partner@example.com\",\n \"description\": \"Invoice payment\",\n \"saveBeneficiary\": true\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Transfer initiated successfully",
"success": true,
"data": {
"transfer_reference": "TRANSFER-1712345678-abc12345",
"sender_transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction_type": "withdrawal",
"status": "completed",
"description": "Invoice payment",
"external_reference": "TRANSFER-1712345678-abc12345-DEBIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"created_at": "2026-03-23T13:45:28.138Z",
"updated_at": "2026-03-23T13:45:28.263Z",
"metadata": {
"invoice_id": "INV-2026-042"
}
},
"recipient_transaction": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"transaction_type": "deposit",
"status": "completed",
"description": "Transfer from Acme Holdings",
"external_reference": "TRANSFER-1712345678-abc12345-CREDIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"created_at": "2026-03-23T13:45:28.201Z",
"updated_at": "2026-03-23T13:45:28.201Z",
"payer": {
"name": "Acme Holdings",
"bank_name": "Rolla"
}
},
"pending_claim": null
}
}{
"status": 400,
"message": "Validation failed",
"code": "ACCOUNT_RESTRICTED",
"errors": [
{
"path": [
"amount"
],
"message": "amount must be a whole number of smallest currency units (e.g. cents)"
}
]
}{
"status": 400,
"message": "Validation failed",
"code": "ACCOUNT_RESTRICTED",
"errors": [
{
"path": [
"amount"
],
"message": "amount must be a whole number of smallest currency units (e.g. cents)"
}
]
}{
"status": 403,
"message": "This endpoint requires IP whitelisting. Add at least one whitelisted IP to your API key before using withdraw."
}beneficiaryId with withdrawal_method: rolla_transfer.
The request amount is always in the smallest currency unit for currency (USD = cents — 100 = $1.00 USD; NGN = kobo).
X-Account-Id header set to the account ID you want to send from — the transfer is then debited from that account’s wallet. Without it, it runs against your own (parent) account. This is how you move funds between your institutional account and merchant wallets.Example Request (by email)
curl -X POST "https://api.rolla.xyz/api/v1/external/wallet/transfer" \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "partner@example.com",
"description": "Invoice payment",
"saveBeneficiary": true,
"metadata": { "invoice_id": "INV-2026-042" }
}'
Example Request (by saved beneficiary)
curl -X POST "https://api.rolla.xyz/api/v1/external/wallet/transfer" \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 100,
"currency": "USD",
"destinationCurrency": "USD",
"beneficiaryId": "d4e5f6a7-b8c9-0123-defa-456789012345",
"description": "Invoice payment"
}'
Example Request (cross-currency)
Cross-currency transfers require arateToken from the GET /wallet/rates endpoint first.
curl -X POST "https://api.rolla.xyz/api/v1/external/wallet/transfer" \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 100,
"currency": "USD",
"destinationCurrency": "NGN",
"recipient": "partner@example.com",
"rateToken": "rate_token_from_rates_endpoint",
"description": "Cross-currency payment"
}'
Example Response
Theamount you send uses the smallest unit of currency (USD = cents below). Ledger fields such as source_amount mirror that unit.
{
"status": 200,
"message": "Transfer initiated successfully",
"success": true,
"data": {
"transfer_reference": "TRANSFER-1712345678-abc12345",
"sender_transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction_type": "withdrawal",
"status": "completed",
"description": "Invoice payment",
"external_reference": "TRANSFER-1712345678-abc12345-DEBIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"metadata": { "invoice_id": "INV-2026-042" },
"created_at": "2026-03-23T13:45:28.138Z",
"updated_at": "2026-03-23T13:45:28.263Z"
},
"recipient_transaction": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"transaction_type": "deposit",
"status": "completed",
"description": "Transfer from Acme Holdings",
"external_reference": "TRANSFER-1712345678-abc12345-CREDIT",
"fee_amount": 0,
"source_amount": 100,
"destination_amount": 100,
"source_currency": "USD",
"destination_currency": "USD",
"payer": { "name": "Acme Holdings", "bank_name": "Rolla" },
"created_at": "2026-03-23T13:45:28.201Z",
"updated_at": "2026-03-23T13:45:28.201Z"
},
"pending_claim": null
}
}
transfer_reference; each transaction’s own external_reference is that
reference suffixed with -DEBIT on the sender side and -CREDIT on the recipient side. On a
cross-currency transfer the sender leg’s transaction_type is fx_withdrawal rather than
withdrawal, and its destination_amount / destination_currency are the converted amount in
the recipient’s currency.
Key Behaviours
recipient_transaction, and every webhook for it, carries payer with the sending account’s name and bank_name: "Rolla", so the recipient can see who the funds came from. The sender’s email is never included.pending_claim — If the recipient email matches multiple Rolla businesses, funds are held in escrow and a pending_claim object is returned. The recipient must log in and claim the funds.Parameters
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount in the smallest currency unit for currency (e.g. cents for USD: 100 = $1.00 USD; kobo for NGN) |
currency | string | Yes | Source currency code (e.g. USD) |
destinationCurrency | string | Yes | Recipient’s currency code |
recipient | string | Conditional | Recipient’s email or Rolla tag. Required if beneficiaryId not provided |
beneficiaryId | string (UUID) | Conditional | Saved beneficiary ID with withdrawal_method: rolla_transfer. Required if recipient not provided |
description | string | No | Transfer description (max 500 chars) |
saveBeneficiary | boolean | No | Auto-save the recipient as a beneficiary for future transfers |
rateToken | string | Conditional | Required for cross-currency transfers — obtain from GET /wallet/rates |
metadata | object | No | Your own key/value data, returned on the transaction and its webhooks. See Attaching your own metadata |
Attaching your own metadata
Pass ametadata object to carry your own identifiers on the transfer — an order id, an invoice number, or, when you forward funds received from a customer, who originally paid. Rolla stores it verbatim, never interprets it, and returns it unchanged on the transaction and every webhook for it.
curl -X POST "https://api.rolla.xyz/api/v1/external/wallet/transfer" \
-H "X-API-Key: your_api_key_here" \
-H "X-Account-Id: 8f14e45f-ceea-467a-9575-2d0c3b1f7c21" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "USD",
"destinationCurrency": "USD",
"recipient": "@acmeholdings",
"metadata": {
"original_payer": "Contoso Retail UK Ltd",
"order_id": "ORD-88213"
}
}'
metadata is always returned on the sending side (sender_transaction). It is also returned on the receiving side (recipient_transaction) when both accounts belong to the same owner — for example a merchant account forwarding funds to your institutional account. A transfer to another company’s account never shows them your metadata.
Limits are the same as for payouts: at most 20 keys, keys up to 64 characters, values that are a string (up to 500 characters), number, boolean or null — no nested objects or arrays. Exceeding any of them returns 400 and the transfer is not made.
Authorizations
Your Rolla API key
Body
Either recipient or beneficiaryId is required.
Amount in the smallest currency unit for the given currency (e.g. kobo for NGN, cents for USD — 100 cents = $1.00 USD). Must be a whole number and at least 1.
x >= 1100
Source currency code
3 - 4"USD"
Recipient currency code. Differ it from currency for a cross-currency transfer.
3 - 4"USD"
Recipient email or Rolla tag. Required if beneficiaryId not provided.
"partner@example.com"
Saved beneficiary ID with withdrawal_method rolla_transfer. Required if recipient not provided.
Transfer description. Defaults to "In-network transfer" when omitted or blank.
500"Invoice payment"
Auto-save the recipient as a beneficiary for future transfers
Required when destinationCurrency differs from currency. Obtain from GET /wallet/rates.
Your own key/value data, stored on the transfer and returned on the transaction and its webhooks. Always returned on the sending side; returned on the receiving side only when both accounts belong to the same owner. At most 20 keys (up to 64 characters each); values are a string (up to 500 characters), number, boolean or null — no nested objects or arrays.
Show child attributes
Show child attributes
{
"original_payer": "Contoso Retail UK Ltd",
"order_id": "ORD-88213"
}