Test helpers
Endpoints that simulate what the card issuer would do. They only work with a test key (mr_test_…) and return 400 test_mode_only otherwise.
/v1/test/cardholders/{id}/verificationSimulate a verification decisionPOST/v1/test/cards/{id}/authorizationsSimulate a card paymentSimulate a verification decision Test mode only
Approves or rejects a cardholder whose verification is in_review. The status changes within seconds and cardholder.kyc_updated is sent.
Key roles: Owner or Finance
Path parameters
ID of the cardholder.
Body
The decision to simulate.
approvedrejectedDocument unreadable (simulation)Only with rejected: becomes verification.reason. Up to 200 characters.
Returns
Returns the cardholder object with 202: the outcome follows by webhook.
Errors worth handling
| HTTP | Code | Description |
|---|---|---|
400 | test_ | Test helpers need a test key. |
400 | verification_ | The test helper needs a verification submitted first. |
404 | resource_ | No object with this ID in this mode. |
curl https://api.monerail.com/v1/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification \
-H "Authorization: Bearer $MONERAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"result": "rejected",
"reason": "Name/dob mismatch"
}'const res = await fetch('https://api.monerail.com/v1/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MONERAIL_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
result: 'rejected',
reason: 'Name/dob mismatch',
}),
});
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/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification",
headers={"Authorization": f"Bearer {os.environ['MONERAIL_KEY']}"},
json={
"result": "rejected",
"reason": "Name/dob mismatch",
},
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/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MONERAIL_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'result' => 'rejected',
'reason' => 'Name/dob mismatch',
]),
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{
"result": "rejected",
"reason": "Name/dob mismatch",
})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, "https://api.monerail.com/v1/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification", bytes.NewReader(payload))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("MONERAIL_KEY"))
req.Header.Set("Content-Type", "application/json")
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 = """
{
"result": "rejected",
"reason": "Name/dob mismatch"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.monerail.com/v1/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification"))
.header("Authorization", "Bearer " + System.getenv("MONERAIL_KEY"))
.header("Content-Type", "application/json")
.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/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("MONERAIL_KEY")}"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
result: "rejected",
reason: "Name/dob mismatch",
})
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/test/cardholders/5df6acd9-4bac-4e19-869c-fcde6b79f389/verification");
request.Content = JsonContent.Create(new
{
result = "rejected",
reason = "Name/dob mismatch",
});
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": "5df6acd9-4bac-4e19-869c-fcde6b79f389",
"object": "cardholder",
"livemode": false,
"type": "customer",
"first_name": "Awa",
"last_name": "Ndiaye",
"email": "awa@example.com",
"phone": "+237670000999",
"country": "CM",
"city": "Douala",
"birth_date": "1991-02-03",
"id_document": "national_id",
"id_number_last4": "3408",
"verification": {
"status": "in_review",
"reason": null,
"verified_at": null
},
"created_at": "2026-10-30T13:47:06.394Z",
"updated_at": "2026-10-30T13:47:09.807Z"
}Simulate a card payment Test mode only
Simulates a merchant charging the card. An approved payment and its fee appear on the card within seconds, with a card.transaction event. A declined one sends card.authorization_declined, charges nothing and returns authorization: null.
Key roles: Owner or Finance
Path parameters
ID of the card.
Body
In US cents, up to 10,000,000.
Merchant name, up to 40 characters.
approvedOutcome of the simulated payment.
approveddeclinedReturns
Returns the test authorization object with 202: the outcome follows by webhook.
Errors worth handling
| HTTP | Code | Description |
|---|---|---|
400 | test_ | Test helpers need a test key. |
404 | resource_ | No object with this ID in this mode. |
curl https://api.monerail.com/v1/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations \
-H "Authorization: Bearer $MONERAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 400,
"merchant": "Notion"
}'const res = await fetch('https://api.monerail.com/v1/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MONERAIL_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 400,
merchant: 'Notion',
}),
});
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/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations",
headers={"Authorization": f"Bearer {os.environ['MONERAIL_KEY']}"},
json={
"amount": 400,
"merchant": "Notion",
},
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/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MONERAIL_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 400,
'merchant' => 'Notion',
]),
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": 400,
"merchant": "Notion",
})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, "https://api.monerail.com/v1/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations", bytes.NewReader(payload))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("MONERAIL_KEY"))
req.Header.Set("Content-Type", "application/json")
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": 400,
"merchant": "Notion"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.monerail.com/v1/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations"))
.header("Authorization", "Bearer " + System.getenv("MONERAIL_KEY"))
.header("Content-Type", "application/json")
.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/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("MONERAIL_KEY")}"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
amount: 400,
merchant: "Notion",
})
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/test/cards/ce2f0aea-7f87-4cbd-8383-03129d1253e0/authorizations");
request.Content = JsonContent.Create(new
{
amount = 400,
merchant = "Notion",
});
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": "test_authorization",
"card": "ce2f0aea-7f87-4cbd-8383-03129d1253e0",
"amount": 400,
"currency": "USD",
"merchant": "Notion",
"status": "approved",
"authorization": "auth_7731"
}