SafePays API

RTP Payouts

API V2 endpoints for managing customer bank accounts and sending instant RTP / FedNow payouts

Overview

The RTP (Real-Time Payments) endpoints let you save US bank accounts on a customer profile and push instant payouts to them via Airwallex over the RTP and FedNow rails. The flow is always:

  1. Add a bank account for the customer (one-time setup).
  2. (Optional) List the customer's bank accounts to fetch a bank_account_id.
  3. Send a payout to that bank account from your vendor credit_wallet_balance.

Payouts are funded from your vendor credit_wallet_balance. If the Airwallex transfer fails for any reason, the funds are automatically reversed back into your credit_wallet_balance.


List Customer Bank Accounts

List all bank accounts saved for one of your customers.

Endpoint

GET /api/v2/customer/{customer_id}/bank-accounts?api_key=your_api_key

Path Parameters

ParameterTypeRequiredDescription
customer_idstringYesThe unique customer ID (UUID)

Query Parameters

ParameterTypeRequiredDescription
api_keystringYesYour vendor API key

Example Request

curl "https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-accounts?api_key=your_api_key"
const customerId = '550e8400-e29b-41d4-a716-446655440000';
const apiKey = 'your_api_key';

const response = await fetch(
  `https://app.safepays.com/api/v2/customer/${customerId}/bank-accounts?api_key=${apiKey}`
);

const result = await response.json();
console.log(`Found ${result.total} bank account(s)`);
import requests

customer_id = '550e8400-e29b-41d4-a716-446655440000'
api_key = 'your_api_key'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-accounts?api_key={api_key}'

response = requests.get(url)
result = response.json()
print(f"Found {result['total']} bank account(s)")

Success Response

Status Code: 200 OK

{
  "status": "success",
  "customer_id": "550e8400-e29b-41d4-a716-446655440000",
  "total": 1,
  "bank_accounts": [
    {
      "id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
      "customer_id": "550e8400-e29b-41d4-a716-446655440000",
      "entity_type": "PERSONAL",
      "email": "john@example.com",
      "address": {
        "country_code": "US",
        "state": "CA",
        "city": "San Francisco",
        "postcode": "94103",
        "street_address": "123 Main St"
      },
      "bank_details": {
        "account_name": "John Doe",
        "account_number": "1234567890",
        "routing_number": "111000025",
        "bank_name": "Bank of America",
        "account_category": "Checking"
      },
      "first_name": "John",
      "last_name": "Doe",
      "date_of_birth": "1990-01-15",
      "created_at": "2026-04-19 14:30:00"
    }
  ]
}

Response Fields

FieldTypeDescription
customer_idstringThe customer the bank accounts belong to
totalintegerNumber of bank accounts returned
bank_accountsarrayList of saved bank account objects (use id as the bank_account_id when creating a payout)

Error Responses

StatusError
400"api_key parameter is required"
401"Invalid API Key"
403"This vendor account has been banned."
404"Customer not found"

Add Customer Bank Account

Save a new US bank account (ACH / FedNow) for a customer. The account can later be used as a payout destination via Payout to Customer Bank Account.

Endpoint

POST /api/v2/customer/{customer_id}/bank-account

Request Headers

Content-Type: application/json

Request Body

{
  "api_key": "your_api_key",
  "entity_type": "PERSONAL",
  "email": "john@example.com",
  "address": {
    "country_code": "US",
    "state": "CA",
    "city": "San Francisco",
    "postcode": "94103",
    "street_address": "123 Main St"
  },
  "bank_details": {
    "account_name": "John Doe",
    "account_number": "1234567890",
    "routing_number": "111000025",
    "bank_name": "Bank of America",
    "account_category": "Checking"
  },
  "first_name": "John",
  "last_name": "Doe",
  "date_of_birth": "1990-01-15"
}
FieldRequiredDefaultDescription
api_keyYesYour vendor API key
entity_typeNo"PERSONAL""PERSONAL" or "COMPANY"
emailYesAccount holder email
address.country_codeNo"US"ISO 2-letter country code
address.stateYesState / Province
address.cityYesCity
address.postcodeYesPostal / ZIP code
address.street_addressYesStreet address
bank_details.account_nameYesName on bank account
bank_details.account_numberYesBank account number
bank_details.routing_numberYesABA routing number
bank_details.bank_nameYesBank name
bank_details.account_categoryNo"Checking""Checking" or "Savings"
first_nameYes*Required when entity_type = "PERSONAL"
last_nameYes*Required when entity_type = "PERSONAL"
date_of_birthNo""YYYY-MM-DD
company_nameYes*Required when entity_type = "COMPANY"

* Conditional fields depend on entity_type. Personal accounts must include first_name and last_name; company accounts must include company_name.

Example: Personal Account

curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-account \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "your_api_key",
    "entity_type": "PERSONAL",
    "email": "john@example.com",
    "address": {
      "country_code": "US",
      "state": "CA",
      "city": "San Francisco",
      "postcode": "94103",
      "street_address": "123 Main St"
    },
    "bank_details": {
      "account_name": "John Doe",
      "account_number": "1234567890",
      "routing_number": "111000025",
      "bank_name": "Bank of America",
      "account_category": "Checking"
    },
    "first_name": "John",
    "last_name": "Doe",
    "date_of_birth": "1990-01-15"
  }'
const customerId = '550e8400-e29b-41d4-a716-446655440000';

const response = await fetch(
  `https://app.safepays.com/api/v2/customer/${customerId}/bank-account`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: 'your_api_key',
      entity_type: 'PERSONAL',
      email: 'john@example.com',
      address: {
        country_code: 'US',
        state: 'CA',
        city: 'San Francisco',
        postcode: '94103',
        street_address: '123 Main St'
      },
      bank_details: {
        account_name: 'John Doe',
        account_number: '1234567890',
        routing_number: '111000025',
        bank_name: 'Bank of America',
        account_category: 'Checking'
      },
      first_name: 'John',
      last_name: 'Doe',
      date_of_birth: '1990-01-15'
    })
  }
);

const result = await response.json();
console.log('Bank Account ID:', result.bank_account.id);
import requests
import json

customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-account'
headers = {'Content-Type': 'application/json'}
data = {
    'api_key': 'your_api_key',
    'entity_type': 'PERSONAL',
    'email': 'john@example.com',
    'address': {
        'country_code': 'US',
        'state': 'CA',
        'city': 'San Francisco',
        'postcode': '94103',
        'street_address': '123 Main St'
    },
    'bank_details': {
        'account_name': 'John Doe',
        'account_number': '1234567890',
        'routing_number': '111000025',
        'bank_name': 'Bank of America',
        'account_category': 'Checking'
    },
    'first_name': 'John',
    'last_name': 'Doe',
    'date_of_birth': '1990-01-15'
}

response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Bank Account ID: {result['bank_account']['id']}")

Example: Company Account

curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-account \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "your_api_key",
    "entity_type": "COMPANY",
    "email": "ap@acme.com",
    "address": {
      "country_code": "US",
      "state": "NY",
      "city": "New York",
      "postcode": "10001",
      "street_address": "200 Broadway"
    },
    "bank_details": {
      "account_name": "Acme Inc",
      "account_number": "9876543210",
      "routing_number": "021000021",
      "bank_name": "Chase",
      "account_category": "Checking"
    },
    "company_name": "Acme Inc"
  }'
const customerId = '550e8400-e29b-41d4-a716-446655440000';

const response = await fetch(
  `https://app.safepays.com/api/v2/customer/${customerId}/bank-account`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: 'your_api_key',
      entity_type: 'COMPANY',
      email: 'ap@acme.com',
      address: {
        country_code: 'US',
        state: 'NY',
        city: 'New York',
        postcode: '10001',
        street_address: '200 Broadway'
      },
      bank_details: {
        account_name: 'Acme Inc',
        account_number: '9876543210',
        routing_number: '021000021',
        bank_name: 'Chase',
        account_category: 'Checking'
      },
      company_name: 'Acme Inc'
    })
  }
);

const result = await response.json();
console.log('Bank Account ID:', result.bank_account.id);
import requests
import json

customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-account'
headers = {'Content-Type': 'application/json'}
data = {
    'api_key': 'your_api_key',
    'entity_type': 'COMPANY',
    'email': 'ap@acme.com',
    'address': {
        'country_code': 'US',
        'state': 'NY',
        'city': 'New York',
        'postcode': '10001',
        'street_address': '200 Broadway'
    },
    'bank_details': {
        'account_name': 'Acme Inc',
        'account_number': '9876543210',
        'routing_number': '021000021',
        'bank_name': 'Chase',
        'account_category': 'Checking'
    },
    'company_name': 'Acme Inc'
}

response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Bank Account ID: {result['bank_account']['id']}")

Success Response

Status Code: 201 Created

{
  "status": "success",
  "message": "Bank account added successfully",
  "bank_account": {
    "id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
    "customer_id": "550e8400-e29b-41d4-a716-446655440000",
    "entity_type": "PERSONAL",
    "email": "john@example.com",
    "address": {
      "country_code": "US",
      "state": "CA",
      "city": "San Francisco",
      "postcode": "94103",
      "street_address": "123 Main St"
    },
    "bank_details": {
      "account_name": "John Doe",
      "account_number": "1234567890",
      "routing_number": "111000025",
      "bank_name": "Bank of America",
      "account_category": "Checking"
    },
    "first_name": "John",
    "last_name": "Doe",
    "date_of_birth": "1990-01-15",
    "created_at": "2026-04-19 14:30:00"
  }
}

Save the returned bank_account.id — you'll pass it as bank_account_id when creating a payout.

Error Responses

StatusError
400"api_key is required"
400"entity_type must be PERSONAL or COMPANY"
400"email is required" / "Invalid email format"
400"address.state, address.city, address.postcode and address.street_address are required"
400"bank_details.account_name, account_number, routing_number and bank_name are required"
400"first_name and last_name are required for PERSONAL accounts"
400"company_name is required for COMPANY accounts"
401"Invalid API Key"
403"This vendor account has been banned."
404"Customer not found"

Payout to Customer Bank Account

Send an instant payout (Airwallex RTP / FedNow) from your vendor credit wallet to one of a customer's saved bank accounts.

Funds Path

Vendor credit_wallet_balance
  → Customer wallet
  → Airwallex bank push (RTP / FedNow)
  → Customer bank account

If the Airwallex transfer fails, the funds are automatically reversed back to the vendor's credit_wallet_balance. The vendor must have sufficient credit_wallet_balance to cover the payout amount.

Endpoint

POST /api/v2/customer/{customer_id}/payout

Request Headers

Content-Type: application/json

Request Body

{
  "api_key": "your_api_key",
  "bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
  "amount": 100.00,
  "currency": "USD"
}
FieldRequiredDefaultDescription
api_keyYesYour vendor API key
bank_account_idYesID of a bank account belonging to this customer (see List Customer Bank Accounts)
amountYesPayout amount, must be > 0
currencyNo"USD"Currency code (currently only "USD" is supported)

Example Request

curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/payout \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "your_api_key",
    "bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
    "amount": 100.00,
    "currency": "USD"
  }'
const customerId = '550e8400-e29b-41d4-a716-446655440000';

const response = await fetch(
  `https://app.safepays.com/api/v2/customer/${customerId}/payout`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: 'your_api_key',
      bank_account_id: 'f3a1b2c4-1234-4abc-9def-1234567890ab',
      amount: 100.00,
      currency: 'USD'
    })
  }
);

const result = await response.json();
console.log('Payout status:', result.payout.status);
import requests
import json

customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/payout'
headers = {'Content-Type': 'application/json'}
data = {
    'api_key': 'your_api_key',
    'bank_account_id': 'f3a1b2c4-1234-4abc-9def-1234567890ab',
    'amount': 100.00,
    'currency': 'USD'
}

response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Payout status: {result['payout']['status']}")

Success Response

Status Code: 200 OK

{
  "status": "success",
  "message": "Payout sent successfully",
  "payout": {
    "customer_id": "550e8400-e29b-41d4-a716-446655440000",
    "bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
    "amount": 100.00,
    "currency": "USD",
    "status": "PAID",
    "airwallex_transfer_id": "trf_abc123xyz",
    "airwallex_status": "DELIVERED"
  }
}

Response Fields

FieldTypeDescription
customer_idstringThe customer that received the payout
bank_account_idstringThe bank account the funds were sent to
amountnumberAmount paid out
currencystringCurrency code
statusstringSafePays payout status (e.g. PAID)
airwallex_transfer_idstringUnderlying Airwallex transfer ID for reconciliation
airwallex_statusstringAirwallex transfer status (e.g. DELIVERED)

Error Responses

StatusError
400"api_key is required"
400"bank_account_id is required"
400"amount is required" / "Invalid amount format"
400"amount must be greater than 0"
400"Insufficient available balance" (vendor credit wallet has insufficient funds)
400"Withdrawal exceeds your limit of $X per request"
401"Invalid API Key"
403"This vendor account has been banned."
404"Customer not found" / "Bank account not found for this customer"
502"Airwallex error: ..." (transfer rejected by Airwallex; funds auto-reversed to your credit wallet)

A 502 response means Airwallex rejected the transfer. The payout amount has already been reversed back into your credit_wallet_balance, so it is safe to retry once the underlying issue (e.g. invalid routing number, closed account) is resolved.

On this page