curl --request PATCH \
--url https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"currency": "NGN",
"label": "Updated label",
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014",
"email": "john.updated@example.com"
}
'import requests
url = "https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}"
payload = {
"currency": "NGN",
"label": "Updated label",
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014",
"email": "john.updated@example.com"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
currency: 'NGN',
label: 'Updated label',
account_name: 'JOHN DOE',
account_number: '0123456789',
bank_name: 'Access Bank',
bank_code: '000014',
email: 'john.updated@example.com'
})
};
fetch('https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}', 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/beneficiaries/{beneficiaryId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'currency' => 'NGN',
'label' => 'Updated label',
'account_name' => 'JOHN DOE',
'account_number' => '0123456789',
'bank_name' => 'Access Bank',
'bank_code' => '000014',
'email' => 'john.updated@example.com'
]),
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/beneficiaries/{beneficiaryId}"
payload := strings.NewReader("{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Beneficiary updated successfully",
"success": true,
"data": {
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"account_name": "JOHN DOE",
"currency": "NGN",
"email": "john.updated@example.com",
"withdrawal_method": null,
"label": "Updated label",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014"
}
}{
"status": 400,
"message": "Beneficiary not found: c3d4e5f6-a7b8-9012-cdef-123456789012"
}{
"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)"
}
]
}Update Beneficiary
Updates an existing beneficiary’s details. Also used to backfill the account_owner_type and account_category classification fields on beneficiaries created before those fields existed. PUT /beneficiaries/ is accepted as an alias with identical behaviour.
curl --request PATCH \
--url https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"currency": "NGN",
"label": "Updated label",
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014",
"email": "john.updated@example.com"
}
'import requests
url = "https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}"
payload = {
"currency": "NGN",
"label": "Updated label",
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014",
"email": "john.updated@example.com"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
currency: 'NGN',
label: 'Updated label',
account_name: 'JOHN DOE',
account_number: '0123456789',
bank_name: 'Access Bank',
bank_code: '000014',
email: 'john.updated@example.com'
})
};
fetch('https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}', 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/beneficiaries/{beneficiaryId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'currency' => 'NGN',
'label' => 'Updated label',
'account_name' => 'JOHN DOE',
'account_number' => '0123456789',
'bank_name' => 'Access Bank',
'bank_code' => '000014',
'email' => 'john.updated@example.com'
]),
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/beneficiaries/{beneficiaryId}"
payload := strings.NewReader("{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rolla.xyz/api/v1/external/beneficiaries/{beneficiaryId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"currency\": \"NGN\",\n \"label\": \"Updated label\",\n \"account_name\": \"JOHN DOE\",\n \"account_number\": \"0123456789\",\n \"bank_name\": \"Access Bank\",\n \"bank_code\": \"000014\",\n \"email\": \"john.updated@example.com\"\n}"
response = http.request(request)
puts response.read_body{
"status": 200,
"message": "Beneficiary updated successfully",
"success": true,
"data": {
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"account_name": "JOHN DOE",
"currency": "NGN",
"email": "john.updated@example.com",
"withdrawal_method": null,
"label": "Updated label",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014"
}
}{
"status": 400,
"message": "Beneficiary not found: c3d4e5f6-a7b8-9012-cdef-123456789012"
}{
"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)"
}
]
}PATCH is the current update verb. PUT /beneficiaries/{beneficiaryId} remains supported as a
backward-compatible alias with identical behaviour, but new integrations should use PATCH.
Both verbs run the same handler and the same validation schema, so requests and responses are identical.Example Request
curl -X PATCH "https://api.rolla.xyz/api/v1/external/beneficiaries/c3d4e5f6-a7b8-9012-cdef-123456789012" \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"currency": "NGN",
"label": "Updated label",
"account_name": "JOHN DOE",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014",
"email": "john.updated@example.com"
}'
PATCH/PUT is also how you backfill the new classification fields on an existing
beneficiary that predates them — send account_owner_type (and account_category for USD)
to bring a legacy record into compliance.Example Response
The updated beneficiary is returned directly underdata, in the same shape as Get Beneficiary. During the grace period a deprecation_warning object is appended when a required classification field is missing, exactly as on create.
{
"status": 200,
"message": "Beneficiary updated successfully",
"success": true,
"data": {
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"account_name": "JOHN DOE",
"currency": "NGN",
"email": "john.updated@example.com",
"withdrawal_method": null,
"label": "Updated label",
"account_number": "0123456789",
"bank_name": "Access Bank",
"bank_code": "000014"
}
}
Error Response
Every failure, including an unknownbeneficiaryId, returns 400:
{
"status": 400,
"message": "Beneficiary not found: c3d4e5f6-a7b8-9012-cdef-123456789012"
}
400 with "message": "Validation failed" and an errors array, as on create.
account_owner_type and account_category fields and their grace-period behaviour. See the
Create Beneficiary endpoint for required
fields by withdrawal method, and the
Beneficiary Account Fields migration guide."withdrawal_method": "ach" or "domestic_wire" must include routing_number in the same
request, now that it is enforced for both rails. Omit withdrawal_method to keep the current
rail; send null or "" to clear it.Authorizations
Your Rolla API key
Path Parameters
Beneficiary UUID
Body
Validated as a whole, not as a partial patch: send the full required field set for the beneficiary's withdrawal_method, not only the fields you are changing.
Which other fields are required depends on withdrawal_method and currency; see the "Required Fields by Withdrawal Method" table on Create Beneficiary. The same schema is validated as a whole on PATCH and PUT.
Currency code (e.g., NGN, USD). Must be a supported wallet or FX corridor currency.
10"NGN"
Friendly label for the beneficiary
"Office rent"
Account holder name. Required on every rail except mobile_money (for crypto and rolla_transfer it is the nickname).
100"JOHN DOE"
Bank account number. Required for ach, domestic_wire and international_wire; surrounding whitespace is trimmed.
50"0123456789"
Bank name. Required for ach, domestic_wire and international_wire.
100"Access Bank"
Nigerian bank code from List Nigerian Banks. Required whenever currency is NGN and account_number is sent.
20"000014"
Postal address. On ach, domestic_wire and international_wire every part is required and country must be a two-letter ISO 3166-1 code; a missing part is rejected with a 400 naming the field (e.g. bank_address.city). Optional on every other rail.
Show child attributes
Show child attributes
SWIFT/BIC code (required for international wire)
20Beneficiary email
100"john@example.com"
Contact person name
100Postal address. On ach, domestic_wire and international_wire every part is required and country must be a two-letter ISO 3166-1 code; a missing part is rejected with a 400 naming the field (e.g. bank_address.city). Optional on every other rail.
Show child attributes
Show child attributes
Payout rail. Omit (or send null) for an NGN bank transfer. On update, omit to keep the current rail; null or "" clears it.
domestic_wire, international_wire, ach, local_transfer, crypto_usdt, crypto_usdc, rolla_transfer, mobile_money 9-digit ABA routing number. Required for ach and domestic_wire.
20Crypto wallet address (required for crypto_usdt / crypto_usdc). 26 to 64 alphanumeric characters; validated against the network.
26 - 64Blockchain network slug (required for crypto_usdt / crypto_usdc), e.g. base, tron, ethereum. Stored as the canonical slug.
50Intermediary bank name
255Intermediary bank routing number
50IBAN, for banks that use one instead of an account number
50BIC, where it differs from swift_code
20UK sort code
10Required for rolla_transfer. The UUID of the destination Rolla business (not your own).
"d4e5f6a7-b8c9-0123-defa-456789012345"
Whether the account is held by an individual or a business. Required for every bank-account beneficiary except NGN (ach, domestic_wire, international_wire). Accepted but not required for NGN. Not applicable to crypto_usdt, crypto_usdc, mobile_money or rolla_transfer. During the deprecation grace period, requests that omit this field still succeed but return a deprecation_warning object; after the enforcement date the request is rejected with a 400. See the Beneficiary Account Fields migration guide.
individual, business "business"
Whether a USD account is checking or savings. Required only when currency is USD. If you are not sure, use "checking". Subject to the same grace-period behaviour as account_owner_type.
checking, savings "checking"
Mobile money operator slug (required for mobile_money)
100Mobile money wallet number (required for mobile_money)
30Response
Beneficiary updated successfully
200
"Beneficiary updated successfully"
true
A saved beneficiary as returned to API-key callers. Only populated fields are included: a fiat beneficiary never carries wallet fields and a crypto beneficiary never carries bank fields, and empty values are dropped. withdrawal_method is always present and is null when no rail was set. Timestamps and internal ids (business_id, recipient_business_id) are not returned.
Show child attributes
Show child attributes
Present only during the grace period, when a required classification field (account_owner_type, account_category) was omitted.
Show child attributes
Show child attributes