When making transfers to different countries, currencies, or recipient types (bank accounts, Revolut accounts, or cards), the fields you need to provide – both for creating counterparties and for making the payment itself – vary depending on the destination.
Checking first, before you make the transfer, avoids errors and lets you preview costs and delivery time before committing. This guide walks you through the full flow, linking to dedicated guides for endpoint details.
Example use cases
- Your business uses an internal dashboard to manage salary payments across regions, and needs to create counterparties with the right details before paying invoices
- You build a vendor onboarding flow that validates recipient details and previews transfer costs before approval
The full flow
To prepare and validate a transfer, follow these steps:
- Check supported countries and currencies: Verify that the destination is supported.
- Check counterparty field requirements: Discover which fields are available and required to create a counterparty.
- Prepare a counterparty: Create or retrieve the counterparty.
- Check transfer field requirements: Discover which fields are available and required for the transfer.
- Get an indicative quote: See the expected fees, exchange rate, and delivery time.
- Make the payment: Execute the transfer.
We'll follow five examples throughout this guide:
- 🇬🇧 GB/GBP: A domestic transfer to a UK business bank account
- 🇩🇪 DE/EUR: A cross-currency SWIFT transfer to a German business bank account
- 🇺🇸 US/USD: A cross-currency transfer to a US business bank account
- Revolut: A transfer to a Revolut business account (instant, no fees)
- 💳 Card: A transfer to a saved card
In all examples, we're adding a business counterparty.
1. Check supported countries and currencies
This step applies to external bank account counterparties. Revolut counterparties are added via Revtag and don't require a country or currency. Card counterparties are created via payout link and don't require a country or currency either.
To check which bank countries and currencies are supported, make a GET request to the /counterparties/countries endpoint.
See the API reference: Check supported countries and currencies
As a response, you get all bank countries and their available currencies supported for counterparty creation for your business:
{
"countries": [
{ "country": "GB", "currencies": ["GBP"] },
{ "country": "US", "currencies": ["USD"] },
{ "country": "DE", "currencies": ["EUR"] }
]
}Check if your desired country and currency combination is supported before proceeding.
2. Check counterparty field requirements
Make a GET request to the /counterparties/fields endpoint with the country, currency, and recipient type (personal or business) as query parameters to discover which fields are needed to create a counterparty for that destination.
See the API reference: Check counterparty field requirements
As a response, you get a fields array where each item describes a conditionally available field – whether it's required or optional – along with its validation rules and allowed values.
Always required fields (bank_country, currency, and profile_type) are intentionally excluded from the response.
Field object properties
Each field object contains:
| Property | Description |
|---|---|
name | The name of the given field, matching the Create a counterparty endpoint request body. |
required | Whether the field must be provided when creating the counterparty. |
regex | Validation rules with a pattern and, in some cases, a human-readable description. |
options | Allowed values for the field, if applicable. Each option has a value and an optional default flag. |
For a UK business counterparty (country=GB, currency=GBP, recipient_type=business), the response looks like this:
{
"fields": [
{
"name": "company_name",
"required": true,
"regex": {
"pattern": "^.{3,255}$",
"description": "Beneficiary company name cannot be shorter than 3 characters and longer than 255 characters"
}
},
{
"name": "sort_code",
"required": true,
"regex": {
"pattern": "^\\d{6}$",
"description": "Must be a 6-digit number"
}
},
{
"name": "account_no",
"required": true,
"regex": {
"pattern": "^\\d{8}$",
"description": "Must be an 8-digit number"
}
},
{
"name": "address",
"required": true
}
]
}This means that to create a UK business counterparty with GBP as the currency, you must provide a company_name (3–255 characters), a sort_code (a 6-digit number), an account_no (an 8-digit number), and an address.
For more details and additional examples, see Create a counterparty.
3. Prepare a counterparty
Before creating or using a counterparty, consider validating the recipient's account name (CoP/VoP) to confirm the name matches the bank's records.
Add a new counterparty
Combine the conditionally available fields from the field requirements response with the always required fields (bank_country, currency, and profile_type).
{
"company_name": "Example Vendor Ltd",
"profile_type": "business", // ← always required
"bank_country": "GB", // ← always required
"currency": "GBP", // ← always required
"account_no": "12345678",
"sort_code": "123456",
"address": {
"street_line1": "1 Canada Square",
"city": "London",
"country": "GB",
"postcode": "E14 5AB"
}
}See the API reference: Create a counterparty
When you create or retrieve a counterparty, the response includes the counterparty id, an accounts array and/or a cards array, if applicable.
Each entry in these arrays is a payment method with its own id.
{
"id": "b53fdd78-8d67-4f63-a103-eeeeef53cac8", // ← counterparty ID
"name": "Example Vendor Ltd",
"profile_type": "business",
"state": "created",
"created_at": "2026-07-02T09:34:41.790401Z",
"updated_at": "2026-07-02T09:34:41.790401Z",
"accounts": [
{
"account_no": "12345678",
"sort_code": "123456",
"id": "5c9e171c-7e23-4d6a-b768-aaaaaba535f3", // ← payment method ID
"name": "Example Vendor Ltd",
"bank_country": "GB",
"currency": "GBP",
"type": "external"
}
]
}Save the counterparty id – you'll need it for the transfer.
Retrieve an existing counterparty
Counterparty ID
You can retrieve a list of your counterparties filtering by name and/or account number, and save the counterparty id from the response.
Payment method ID
If the counterparty has multiple payment methods available (for example, 2 accounts, or 1 account and 1 card), you will also need the ID of the account or card to which you want to transfer the money in subsequent steps.
- For bank account and Revolut counterparties, look in the
accountsarray for the accountid. - For card counterparties, look in the
cardsarray for the cardid.
Update a counterparty's address
Some payment rails may require a complete address for the recipient, including the region field.
If a counterparty was created without an address, or the address is incomplete, you can update the address for the specific payment method using the PATCH /counterparties/{counterparty_id}/payment-methods/{payment_method_id} endpoint.
To get the payment_method_id, retrieve the counterparty's details and find the ID of the relevant account or card in the response.
{
"address": {
"street_line1": "1 Canada Square",
"city": "London",
"region": "Greater London", // ← added: not always required at creation, but needed for some payment rails
"country": "GB",
"postcode": "E14 5AB"
}
}The address object accepts the following fields: street_line1, street_line2, region, city, country, and postcode.
The country and postcode fields are required.
A successful response returns 204 No Content, confirming the address was updated.
See the API reference: Update a counterparty payment method
4. Check transfer field requirements
Once you have your counterparty ID, check which fields are needed for the transfer to that destination.
Build the request payload
To check transfer field requirements, you need your account ID and the counterparty ID (and the counterparty's account ID or card ID if they have multiple payment methods).
- To find your account ID, retrieve your accounts and use the
idfrom the response. - You can also retrieve your account's bank details to get the account name and address – useful for displaying a transfer summary before confirming the payment.
{
"account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"receiver": {
"counterparty_id": "b53fdd78-8d67-4f63-a103-eeeeef53cac8",
"account_id": "5c9e171c-7e23-4d6a-b768-aaaaaba535f3" // ← required when counterparty has multiple payment methods
}
}Check the field requirements
Make a POST request to the /pay/fields endpoint with the payload above.
See the API reference: Check transfer field requirements
As a response, you get a fields array where each item describes a conditionally available field for this transfer, indicating whether it's required or optional, along with its validation rules and allowed values.
Field object properties
Each field object contains:
| Property | Description |
|---|---|
name | The field name, matching the /pay endpoint request body. |
required | Whether the field must be provided for this transfer. |
validation | Validation rules such as min_length, max_length, and regex constraints. |
options | Allowed values for the field, if applicable. Each option has a value and an optional default flag. |
{
"fields": [
{
"name": "charge_bearer",
"required": false,
"options": [
{ "value": "shared", "default": true },
{ "value": "debtor" }
]
},
{
"name": "reference",
"required": true,
"validation": {
"max_length": 140
}
}
]
}For a domestic GBP transfer to a UK counterparty, you must provide a reference of up to 140 characters.
You can optionally provide a charge_bearer – the default is shared, meaning fees are split between sender and receiver, but you can also set it to debtor to charge all fees to the sender.
For more details and additional examples, see Bank transfers or Card transfers.
5. Get an indicative quote
Make a POST request to the /pay/indicative-quote endpoint to preview the expected fees, exchange rate, and delivery time without committing to the transfer.
Provide the same transfer details you would provide to the /pay endpoint (account ID, recipient, amount, currency), along with any required fields from step 4.
You may also add any optional fields you want that you discovered in step 4.
See the API reference: Get an indicative quote
For a domestic GBP transfer of 1,000.00 from your GBP account:
{
"account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"receiver": {
"counterparty_id": "b53fdd78-8d67-4f63-a103-eeeeef53cac8",
"account_id": "5c9e171c-7e23-4d6a-b768-aaaaaba535f3" // ← required when counterparty has multiple payment methods
},
"amount": 1000.00,
"currency": "GBP",
"charge_bearer": "shared" // ← optional (discovered in step 4)
}{
"amount": {
"amount": 1000.00,
"currency": "GBP"
},
"fee": {
"amount": 0,
"currency": "GBP"
},
"estimated_total": {
"amount": 1000.00,
"currency": "GBP"
},
"estimated_arrival": {
"date": "2026-07-02",
"speed": "in_seconds"
}
}For more details, see Bank transfers or Card transfers.
With all the information gathered – sender account details, counterparty details, and the indicative quote – you can present a human-readable transfer summary for review before executing the payment. The sample scripts in the Automate the flow section show how to build and display this summary.
6. Make the payment
To execute the transfer, make a POST request to the /pay endpoint with the same details you used for the indicative quote, plus a unique request_id (UUID is recommended) for idempotency.
- The always required fields are:
request_id,account_id,receiver(withcounterparty_id),amount, andcurrency. - You must also provide a
reference, which is required for all example cases, as discovered in step 4. - Optionally, add any other fields you discovered in step 4.
Build the payment payload
{
"request_id": "a1b2c3d4-e5f6-4789-abcd-ef1234567890", // ← always required (UUID for idempotency)
"account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"receiver": {
"counterparty_id": "b53fdd78-8d67-4f63-a103-eeeeef53cac8",
"account_id": "5c9e171c-7e23-4d6a-b768-aaaaaba535f3"
},
"amount": 1000.00,
"currency": "GBP",
"charge_bearer": "shared", // ← optional (discovered in step 4)
"reference": "Invoice #12345" // ← required (discovered in step 4)
}Make the payment
For detailed instructions and example requests, see Bank transfers or Card transfers.
See the API reference: Create a transfer to another account or card
Automate the flow
The method and end result will differ depending on your needs and implementation. To give you an example of how to connect these steps, we provide sample Python scripts below.
The scripts use the standard-library uuid module and require the requests library to be installed.
1. Create a new counterparty and make a transfer
- Check supported countries and currencies and verify the destination is supported
- Check counterparty field requirements
- Build the counterparty payload from the required and applicable fields, validate the account name (CoP/VoP), create the counterparty, and extract the payment method details,
- Check transfer field requirements, get an indicative quote for the transfer, display a transfer summary, check the account balance, ask for confirmation, and execute the transfer
- Check the transaction status
Sample Python script 1: new counterparty
Adjust the configuration variables at the top to match your setup.
import requests
import uuid
# =============================================
# Configuration: adjust these values as needed
# =============================================
API_URL = "https://b2b.revolut.com/api/1.0"
API_TOKEN = "YOUR_API_TOKEN" # Replace with your own token
HEADERS = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
# Your account (the pocket you're sending from)
ACCOUNT_ID = "YOUR_ACCOUNT_ID" # Replace with your own account ID
# Destination details
COUNTRY = "GB" # Counterparty's bank country
CURRENCY = "GBP" # Counterparty's account currency
RECIPIENT_TYPE = "business" # "business" or "personal"
# Counterparty data — provide all fields that apply for your destination.
# The script will keep only the fields required by the API (discovered in step 3).
COUNTERPARTY_DATA = {
"company_name": "Example Vendor Ltd",
"account_no": "12345678",
"sort_code": "123456",
"address": {
"street_line1": "1 Canada Square",
"city": "London",
"region": "Greater London",
"country": "GB",
"postcode": "E14 5AB"
}
}
# Transfer details
AMOUNT = 1.23
REFERENCE = "Invoice #12345"
# =============================================
# Error handling
# =============================================
class ApiError(Exception):
pass
def check_response(response):
"""Print error details and raise ApiError if the response failed."""
if response.ok:
return
try:
error_data = response.json()
print(f"\nAPI Error (HTTP {response.status_code})")
print(f" Code: {error_data.get('code', 'N/A')}")
print(f" Message: {error_data.get('message', 'N/A')}")
print(f" Error ID: {error_data.get('error_id', 'N/A')}")
except Exception:
print(f"\nAPI Error (HTTP {response.status_code}): {response.text}")
raise ApiError(f"Request failed with status {response.status_code}")
# =============================================
# Print destination details
# =============================================
def print_destination_details(country, currency, recipient_type, counterparty_type):
"""Print a summary of the destination details."""
print("=" * 50)
print(f"DESTINATION: {country} / {currency}")
print(f"RECIPIENT TYPE: {recipient_type}")
print(f"COUNTERPARTY TYPE: {counterparty_type}")
print("=" * 50)
# =============================================
# Step 1: Check supported countries and currencies
# =============================================
def check_supported_countries():
"""GET /counterparties/countries — returns supported country/currency pairs."""
print("Checking supported countries and currencies...")
response = requests.get(f"{API_URL}/counterparties/countries", headers=HEADERS)
check_response(response)
data = response.json()
countries = data.get("countries", [])
print(f" Found {len(countries)} supported countries")
return countries
# =============================================
# Step 2: Verify the destination is supported
# =============================================
def verify_destination_supported(countries, country, currency):
"""Check if the desired country/currency combination is supported.
Returns True if supported, False otherwise.
"""
for entry in countries:
if entry.get("country") == country and currency in entry.get("currencies", []):
print(f" Destination {country}/{currency} is supported.")
return True
print(f" Destination {country}/{currency} is not supported.")
return False
# =============================================
# Step 3: Check counterparty field requirements
# =============================================
def check_counterparty_field_requirements(country, currency, recipient_type):
"""GET /counterparties/fields — returns required and applicable fields."""
print("Checking counterparty field requirements...")
params = {"country": country, "currency": currency, "recipient_type": recipient_type}
response = requests.get(f"{API_URL}/counterparties/fields", params=params, headers=HEADERS)
check_response(response)
data = response.json()
required = [f["name"] for f in data.get("fields", []) if f.get("required")]
optional = [f["name"] for f in data.get("fields", []) if not f.get("required")]
print(f" Required fields: {required}")
print(f" Optional fields: {optional}")
return data
# =============================================
# Step 4: Build the counterparty payload
# =============================================
def build_counterparty_payload(field_requirements, country, currency, recipient_type, counterparty_data):
"""Combine always required fields with applicable fields from the requirements response.
Only fields present in the requirements response (or always required) are included.
Fields with no data (None) are dropped.
"""
payload = {
"profile_type": recipient_type,
"bank_country": country,
"currency": currency,
}
for field in field_requirements.get("fields", []):
name = field["name"]
if name in counterparty_data and counterparty_data[name] is not None:
payload[name] = counterparty_data[name]
# Group first_name/last_name into individual_name for personal counterparties
if recipient_type == "personal":
individual_name = {}
if "first_name" in payload:
individual_name["first_name"] = payload.pop("first_name")
if "last_name" in payload:
individual_name["last_name"] = payload.pop("last_name")
if individual_name:
payload["individual_name"] = individual_name
print(f" Counterparty payload fields: {list(payload.keys())}")
return payload
# =============================================
# Step 5: Validate the account name (CoP/VoP)
# =============================================
def build_cop_request(counterparty_data, country, currency, recipient_type):
"""Build the CoP request body based on the destination region.
Returns the request dict, or None if CoP is not available for this destination.
"""
request = {}
if recipient_type == "business":
request["company_name"] = counterparty_data.get("company_name")
else:
individual = counterparty_data.get("individual_name", {})
request["individual_name"] = {
"first_name": individual.get("first_name") or counterparty_data.get("first_name"),
"last_name": individual.get("last_name") or counterparty_data.get("last_name"),
}
if country == "GB" and currency == "GBP":
request["account_no"] = counterparty_data.get("account_no")
request["sort_code"] = counterparty_data.get("sort_code")
elif country == "AU" and currency == "AUD":
request["account_no"] = counterparty_data.get("account_no")
request["bsb"] = counterparty_data.get("bsb")
elif currency == "EUR" or (country == "RO" and currency == "RON"):
request["iban"] = counterparty_data.get("iban")
request["recipient_country"] = country
request["recipient_currency"] = currency
if counterparty_data.get("bic"):
request["bic"] = counterparty_data.get("bic")
else:
return None
return request
def validate_account_name(counterparty_data, country, currency, recipient_type):
"""POST /account-name-validation — validate the account name (CoP/VoP).
Returns True if the flow should proceed, False if it should stop.
"""
cop_request = build_cop_request(counterparty_data, country, currency, recipient_type)
if cop_request is None:
print("CoP/VoP is not available for this destination.")
return ask_to_proceed()
print("Validating account name (CoP/VoP)...")
try:
response = requests.post(f"{API_URL}/account-name-validation", json=cop_request, headers=HEADERS)
check_response(response)
except ApiError:
print(" CoP/VoP check failed.")
return ask_to_proceed()
result = response.json()
result_code = result.get("result_code")
print(f" Result: {result_code}")
if result_code in ("matched", "close_match"):
if result_code == "close_match":
if "company_name" in result:
print(f" Actual name: {result['company_name']}")
elif "individual_name" in result:
name = result["individual_name"]
print(f" Actual name: {name.get('first_name', '')} {name.get('last_name', '')}")
return True
if result_code == "not_matched":
print(" The account name does not match.")
return False
# cannot_be_checked or temporarily_unavailable
print(f" The check could not be completed (result: {result_code}).")
return ask_to_proceed()
def ask_to_proceed():
"""Ask the user whether to proceed after a non-blocking issue.
Returns True if the user wants to proceed, False otherwise.
"""
confirm = input("Proceed anyway? ([y]es/[n]o): ").strip().lower()
if confirm in ("y", "yes"):
return True
print("Transfer cancelled.")
return False
# =============================================
# Step 6: Create the counterparty
# =============================================
def create_counterparty(payload):
"""POST /counterparty — create the counterparty."""
print("Creating counterparty...")
response = requests.post(f"{API_URL}/counterparty", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
print(f" Counterparty ID: {data.get('id')}")
print(f" State: {data.get('state')}")
return data
# =============================================
# Step 7: Extract payment method details
# =============================================
def extract_payment_method(counterparty_data):
"""Extract the counterparty ID, payment method ID, and human-readable details.
Returns (counterparty_id, payment_method_id, receiver_key, pm_details).
"""
counterparty_id = counterparty_data["id"]
accounts = counterparty_data.get("accounts", [])
cards = counterparty_data.get("cards", [])
if accounts:
account = accounts[0]
pm_id = account["id"]
pm_details = {
"account_ref": account.get("iban") or account.get("account_no") or "Revolut account",
"address": account.get("address", {}),
}
return counterparty_id, pm_id, "account_id", pm_details
if cards:
card = cards[0]
pm_id = card["id"]
pm_details = {
"account_ref": f"Card *{card.get('last_digits', '????')} ({card.get('scheme', 'unknown')})",
"address": {},
}
return counterparty_id, pm_id, "card_id", pm_details
return counterparty_id, None, None, {
"account_ref": "Revolut account (revtag)",
"address": {},
}
def build_receiver(counterparty_id, payment_method_id, receiver_key):
"""Build the receiver object for /pay endpoints."""
receiver = {"counterparty_id": counterparty_id}
if payment_method_id:
receiver[receiver_key] = payment_method_id
return receiver
# =============================================
# Step 8: Check transfer field requirements
# =============================================
def check_transfer_field_requirements(account_id, receiver):
"""POST /pay/fields — returns required and optional fields for the transfer."""
print("Checking transfer field requirements...")
payload = {"account_id": account_id, "receiver": receiver}
response = requests.post(f"{API_URL}/pay/fields", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
required = [f["name"] for f in data.get("fields", []) if f.get("required")]
optional = [f["name"] for f in data.get("fields", []) if not f.get("required")]
print(f" Required fields: {required}")
print(f" Optional fields: {optional}")
return data
def build_payment_payload(transfer_fields, account_id, receiver, amount, currency, reference):
"""Build the payment payload from always required fields + conditionally available fields.
Returns (payload, missing_required) where missing_required is a list of required
field names that have no value.
"""
payload = {
"request_id": str(uuid.uuid4()),
"account_id": account_id,
"receiver": receiver,
"amount": amount,
"currency": currency,
}
provided = {"reference": reference}
missing_required = []
for field in transfer_fields.get("fields", []):
name = field["name"]
is_required = field.get("required", False)
if name in provided and provided[name] is not None:
payload[name] = provided[name]
elif is_required:
missing_required.append(name)
return payload, missing_required
# =============================================
# Step 9: Get an indicative quote
# =============================================
def get_indicative_quote(payment_payload, currency):
"""POST /pay/indicative-quote — returns fees, exchange rate, and delivery time."""
print("Fetching indicative quote...")
payload = {k: v for k, v in payment_payload.items() if k != "request_id"}
response = requests.post(f"{API_URL}/pay/indicative-quote", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
fee = data.get("fee", {})
total = data.get("estimated_total", {})
print(f" Fee: {fee.get('amount', 0)} {fee.get('currency', currency)}")
print(f" Total: {total.get('amount', 0)} {total.get('currency', currency)}")
return data
# =============================================
# Step 10: Get own account details and print transfer summary
# =============================================
def get_account_details(account_id):
"""GET /accounts/{id}/bank-details — returns the sender's bank details."""
print("Fetching account bank details...")
response = requests.get(f"{API_URL}/accounts/{account_id}/bank-details", headers=HEADERS)
check_response(response)
data = response.json()
entry = data[0] if isinstance(data, list) else data
print(f" Sender: {entry.get('beneficiary', 'Unknown')}")
return entry
def print_transfer_summary(sender, counterparty_name, pm_details, pm_address, quote):
"""Display a human-readable transfer summary."""
fee = quote.get("fee", {})
total = quote.get("estimated_total", {})
rate = quote.get("estimated_exchange_rate")
arrival = quote.get("estimated_arrival", {})
sender_name = sender.get("beneficiary", "Unknown")
sender_addr = sender.get("beneficiary_address", {})
sender_ref = sender.get("iban") or sender.get("account_no") or "N/A"
sender_addr_str = ", ".join(filter(None, [
sender_addr.get("street_line1"),
sender_addr.get("city"),
sender_addr.get("country"),
sender_addr.get("postcode"),
])) if sender_addr else ""
pm_addr_str = ", ".join(filter(None, [
pm_address.get("street_line1"),
pm_address.get("city"),
pm_address.get("country"),
pm_address.get("postcode"),
])) if pm_address else ""
print("\n" + "=" * 50)
print("TRANSFER SUMMARY")
print("=" * 50)
print(f"FROM: {sender_name}")
print(f" {sender_ref}")
if sender_addr_str:
print(f" {sender_addr_str}")
print(f"TO: {counterparty_name}")
print(f" {pm_details['account_ref']}")
if pm_addr_str:
print(f" {pm_addr_str}")
print(f"\nREFERENCE: {REFERENCE}")
print("-" * 50)
print(f"Amount: {AMOUNT} {CURRENCY}")
if fee.get("amount") is not None:
print(f"Fee: {fee.get('amount')} {fee.get('currency')}")
if fee.get("breakdown"):
for key, val in fee["breakdown"].items():
print(f" - {key}: {val.get('amount')} {val.get('currency')}")
if rate:
print(f"Exchange rate: {rate}")
if total.get("amount") is not None:
print(f"Total cost: {total.get('amount')} {total.get('currency')}")
if arrival:
print(f"Delivery: {arrival.get('speed', '')} ({arrival.get('date', '')})")
print("=" * 50)
print("\n")
# =============================================
# Step 11: Check account balance
# =============================================
def check_balance(account_id, total_cost, total_currency):
"""GET /accounts/{id} — check if the balance covers the total cost.
Returns True if sufficient, False otherwise.
"""
print("Checking account balance...")
response = requests.get(f"{API_URL}/accounts/{account_id}", headers=HEADERS)
check_response(response)
account = response.json()
balance = account.get("balance", 0)
balance_currency = account.get("currency")
print(f" Balance: {balance} {balance_currency}")
print(f" Total cost: {total_cost} {total_currency}")
if balance_currency == total_currency and balance < total_cost:
print("Insufficient funds.")
return False
return True
# =============================================
# Step 12: Ask for confirmation
# =============================================
def confirm_transfer():
"""Ask the user to confirm the transfer.
Returns True if confirmed, False otherwise.
"""
confirm = input("\nProceed with this transfer? ([y]es/[n]o): ").strip().lower()
if confirm not in ("y", "yes"):
print("Transfer cancelled.")
return False
return True
# =============================================
# Step 13: Execute the transfer
# =============================================
def make_transfer(payload):
"""POST /pay — execute the transfer."""
print("Initiating transfer...")
response = requests.post(f"{API_URL}/pay", json=payload, headers=HEADERS)
check_response(response)
result = response.json()
print(f" Transaction ID: {result.get('id')}")
print(f" State: {result.get('state')}")
return result
# =============================================
# Step 14: Check transaction status
# =============================================
def check_transaction_status(transaction_id):
"""GET /transaction/{id} — check the transaction status."""
print("\nChecking transaction status...")
response = requests.get(f"{API_URL}/transaction/{transaction_id}", headers=HEADERS)
check_response(response)
transaction = response.json()
status = transaction.get("state")
print(f" Transaction ID: {transaction_id}")
print(f" Transaction status: {status}")
if status == "pending":
print("Please check again later.")
return status
# =============================================
# Main flow
# =============================================
def run():
# Print destination details
counterparty_type = "Revolut (revtag)" if "revtag" in COUNTERPARTY_DATA else "bank account"
print_destination_details(COUNTRY, CURRENCY, RECIPIENT_TYPE, counterparty_type)
# 1. Check supported countries and currencies
countries = check_supported_countries()
# 2. Verify the destination is supported
if not verify_destination_supported(countries, COUNTRY, CURRENCY):
return
# 3. Check counterparty field requirements
field_requirements = check_counterparty_field_requirements(COUNTRY, CURRENCY, RECIPIENT_TYPE)
# 4. Build the counterparty payload
counterparty_payload = build_counterparty_payload(
field_requirements, COUNTRY, CURRENCY, RECIPIENT_TYPE, COUNTERPARTY_DATA
)
# 5. Validate the account name (CoP/VoP)
if not validate_account_name(COUNTERPARTY_DATA, COUNTRY, CURRENCY, RECIPIENT_TYPE):
return
# 6. Create the counterparty
counterparty = create_counterparty(counterparty_payload)
# 7. Extract payment method details
counterparty_id, pm_id, receiver_key, pm_details = extract_payment_method(counterparty)
receiver = build_receiver(counterparty_id, pm_id, receiver_key)
print(f"Payment method ID: {pm_id or 'none (revtag-only)'}")
# 8. Check transfer field requirements
transfer_fields = check_transfer_field_requirements(ACCOUNT_ID, receiver)
# Build the payment payload and check for missing required fields
payment_payload, missing_required = build_payment_payload(
transfer_fields, ACCOUNT_ID, receiver, AMOUNT, CURRENCY, REFERENCE
)
if missing_required:
print(f"\nMissing required transfer fields: {missing_required}")
return
# 9. Get an indicative quote
quote = get_indicative_quote(payment_payload, CURRENCY)
# 10. Print transfer summary
sender = get_account_details(ACCOUNT_ID)
pm_address = pm_details.get("address", {})
print_transfer_summary(sender, counterparty.get("name", "Unknown"), pm_details, pm_address, quote)
# 11. Check that the account balance is sufficient
total = quote.get("estimated_total", {})
total_cost = total.get("amount", AMOUNT)
total_currency = total.get("currency", CURRENCY)
if not check_balance(ACCOUNT_ID, total_cost, total_currency):
return
# 12. Ask for confirmation
if not confirm_transfer():
return
# 13. Execute the transfer
transaction = make_transfer(payment_payload)
print("Transfer initiated successfully!")
# 14. Check transaction status
check_transaction_status(transaction.get("id"))
if __name__ == "__main__":
try:
run()
except ApiError:
print("\nTransfer flow stopped due to API error.")
except KeyboardInterrupt:
print("\nTransfer flow cancelled by user.")
except Exception as e:
print(f"\nUnexpected error: {e}")==================================================
DESTINATION: GB / GBP
RECIPIENT TYPE: business
COUNTERPARTY TYPE: bank account
==================================================
Checking supported countries and currencies...
Found 213 supported countries
Destination GB/GBP is supported.
Checking counterparty field requirements...
Required fields: ['company_name', 'account_no', 'sort_code', 'address']
Optional fields: []
Counterparty payload fields: ['profile_type', 'bank_country', 'currency', 'company_name', 'account_no', 'sort_code', 'address']
Validating account name (CoP/VoP)...
Result: matched
Creating counterparty...
Counterparty ID: b53fdd78-8d67-4f63-a103-eeeeef53cac8
State: created
Payment method ID: 5c9e171c-7e23-4d6a-b768-aaaaaba535f3
Checking transfer field requirements...
Required fields: ['reference']
Optional fields: ['charge_bearer']
Fetching indicative quote...
Fee: 0 GBP
Total: 1000 GBP
Fetching account bank details...
Sender: Example Corp Ltd
==================================================
TRANSFER SUMMARY
==================================================
FROM: Example Corp Ltd
31926819
1 Westminster Way, London, GB, E14 5AB
TO: Example Vendor Ltd
12345678
1 Canada Square, London, GB, E14 5AB
REFERENCE: Invoice #12345
--------------------------------------------------
Amount: 1000 GBP
Fee: 0 GBP
Total cost: 1000 GBP
Delivery: in_seconds (2026-07-07)
==================================================
Checking account balance...
Balance: 50000 GBP
Total cost: 1000 GBP
Initiating transfer...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
State: pending
Transfer initiated successfully!
Checking transaction status...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
Transaction status: pending
Please check again later.2. Make a transfer to an existing counterparty
- Retrieve the counterparty, resolve the payment method (uses the provided one, defaults to the single available payment method, or asks for input)
- Check transfer field requirements, check and update the counterparty's address if needed
- Get an indicative quote for the transfer, display a transfer summary, check the account balance, ask for confirmation, and execute the transfer
- Check the transaction status
Sample Python script 2: saved counterparty
Adjust the configuration variables at the top to match your setup.
import requests
import uuid
# =============================================
# Configuration: adjust these values as needed
# =============================================
API_URL = "https://b2b.revolut.com/api/1.0"
API_TOKEN = "YOUR_API_TOKEN" # Replace with your own token
HEADERS = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
# Your account (the pocket you're sending from)
ACCOUNT_ID = "YOUR_ACCOUNT_ID" # Replace with your own account ID
# The counterparty to send to
COUNTERPARTY_ID = "YOUR_COUNTERPARTY_ID" # Replace with the ID of your counterparty
# Optional: if your counterparty has more than one payment method specified,
# uncomment the second RECEIVER block below and provide the ID of the appropriate payment method (account or card)
RECEIVER = None
#RECEIVER = {
# "account_id": "YOUR_COUNTERPARTY_ACCOUNT_ID", # Replace with the account ID of the selected payment method, set card_id to None
# "card_id": None # Replace with the card ID of the selected payment method, set account_id to None
#}
# Transfer details
AMOUNT = 1000
CURRENCY = "EUR"
REFERENCE = "Invoice #12345"
# Optional: address to set if the counterparty's payment method is missing one
UPDATED_ADDRESS = {
"street_line1": "Hauptstraße 45",
"city": "München",
"region": "Bayern",
"country": "DE",
"postcode": "80331"
}
# =============================================
# Error handling
# =============================================
class ApiError(Exception):
pass
def check_response(response):
"""Print error details and raise ApiError if the response failed."""
if response.ok:
return
try:
error_data = response.json()
print(f"\nAPI Error (HTTP {response.status_code})")
print(f" Code: {error_data.get('code', 'N/A')}")
print(f" Message: {error_data.get('message', 'N/A')}")
print(f" Error ID: {error_data.get('error_id', 'N/A')}")
except Exception:
print(f"\nAPI Error (HTTP {response.status_code}): {response.text}")
raise ApiError(f"Request failed with status {response.status_code}")
# =============================================
# Print destination details
# =============================================
def print_destination_details(country, currency, recipient_type, counterparty_type):
"""Print a summary of the destination details."""
print("=" * 50)
print(f"DESTINATION: {country} / {currency}")
print(f"RECIPIENT TYPE: {recipient_type}")
print(f"COUNTERPARTY TYPE: {counterparty_type}")
print("=" * 50)
# =============================================
# Step 1: Retrieve the counterparty
# =============================================
def retrieve_counterparty(counterparty_id):
"""GET /counterparty/{id} — returns the counterparty data."""
print(f"Retrieving counterparty {counterparty_id}...")
response = requests.get(f"{API_URL}/counterparty/{counterparty_id}", headers=HEADERS)
check_response(response)
data = response.json()
print(f" Name: {data.get('name', 'Unknown')}")
return data
# =============================================
# Step 2: Resolve the payment method
# =============================================
def resolve_payment_method(counterparty_data):
"""Determine which payment method to use.
If RECEIVER["account_id"] or RECEIVER["card_id"] is set, use it directly.
If RECEIVER not set, but counterparty has only one payment method, use it.
Otherwise, ask for payment method ID.
Returns (payment_method_id, receiver_key) where receiver_key is
"account_id", "card_id", or None.
"""
accounts = counterparty_data.get("accounts", [])
cards = counterparty_data.get("cards", [])
if RECEIVER:
if RECEIVER["account_id"]:
return RECEIVER["account_id"], "account_id"
if RECEIVER["card_id"]:
return RECEIVER["card_id"], "card_id"
else:
print("Invalid recipient. Please provide a valid payment method ID (account_id or card_id).")
return None, None
if not accounts and not cards:
return None, None
if accounts and len(accounts) == 1:
return accounts[0]["id"], "account_id"
if cards and len(cards) == 1:
return cards[0]["id"], "card_id"
print("Please provide a payment method (account_id or card_id)")
return None, None
def build_receiver(counterparty_id, payment_method_id, receiver_key):
"""Build the receiver object for /pay endpoints."""
receiver = {"counterparty_id": counterparty_id}
if payment_method_id:
receiver[receiver_key] = payment_method_id
return receiver
# =============================================
# Step 3: Check transfer field requirements
# =============================================
def check_transfer_fields(account_id, receiver):
"""POST /pay/fields — returns required and optional fields for the transfer."""
print("Checking transfer field requirements...")
payload = {"account_id": account_id, "receiver": receiver}
response = requests.post(f"{API_URL}/pay/fields", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
required = [f["name"] for f in data.get("fields", []) if f.get("required")]
optional = [f["name"] for f in data.get("fields", []) if not f.get("required")]
print(f" Required fields: {required}")
print(f" Optional fields: {optional}")
return data
def build_payment_payload(transfer_fields, account_id, receiver, amount, currency, reference):
"""Build the payment payload from always required fields + conditionally available fields.
Returns (payload, missing_required) where missing_required is a list of required
field names that have no value.
"""
payload = {
"request_id": str(uuid.uuid4()),
"account_id": account_id,
"receiver": receiver,
"amount": amount,
"currency": currency,
}
provided = {"reference": reference}
missing_required = []
for field in transfer_fields.get("fields", []):
name = field["name"]
is_required = field.get("required", False)
if name in provided and provided[name] is not None:
payload[name] = provided[name]
elif is_required:
missing_required.append(name)
return payload, missing_required
# =============================================
# Step 4: Check and update the counterparty's address
# =============================================
def is_address_complete(address):
"""Check if an address has the minimum required fields."""
if not address:
return False
required = ["street_line1", "city", "country"]
return all(address.get(field) for field in required)
def check_and_update_address(counterparty_data, payment_method_id, receiver_key):
"""If the payment method's address is missing or incomplete, update it.
Returns the (possibly updated) address.
Applicable only to accounts.
"""
# Check if the payment method is an account; if not, exit
if receiver_key != "account_id":
return {}
accounts = counterparty_data.get("accounts", [])
# Find the current address on the payment method
current_address = {}
for account in accounts:
if account.get("id") == payment_method_id:
current_address = account.get("address", {})
break
if is_address_complete(current_address):
print("Address is complete.")
return current_address
print("Address is missing or incomplete.")
if payment_method_id:
print("Updating address...")
url = f"{API_URL}/counterparties/{counterparty_data['id']}/payment-methods/{payment_method_id}"
response = requests.patch(url, json={"address": UPDATED_ADDRESS}, headers=HEADERS)
check_response(response)
print(" Address updated.")
return UPDATED_ADDRESS
return {}
# =============================================
# Step 5: Get payment method details
# =============================================
def get_payment_method_details(counterparty_data, payment_method_id):
"""Extract human-readable details for the payment method.
Returns a dict with "account_ref" and "address".
"""
accounts = counterparty_data.get("accounts", [])
cards = counterparty_data.get("cards", [])
for account in accounts:
if account.get("id") == payment_method_id:
return {
"account_ref": account.get("iban") or account.get("account_no") or "Revolut account",
"address": account.get("address", {}),
}
for card in cards:
if card.get("id") == payment_method_id:
return {
"account_ref": f"Card *{card.get('last_digits', '????')} ({card.get('scheme', 'unknown')})",
"address": {},
}
return {
"account_ref": "Revolut account (revtag)",
"address": {},
}
# =============================================
# Step 6: Get own account details
# =============================================
def get_account_details(account_id):
"""GET /accounts/{id}/bank-details — returns the sender's bank details."""
print("Fetching account bank details...")
response = requests.get(f"{API_URL}/accounts/{account_id}/bank-details", headers=HEADERS)
check_response(response)
data = response.json()
# bank-details returns an array — use the first entry
entry = data[0] if isinstance(data, list) else data
print(f" Sender: {entry.get('beneficiary', 'Unknown')}")
return entry
# =============================================
# Step 7: Get an indicative quote
# =============================================
def get_indicative_quote(payment_payload, currency):
"""POST /pay/indicative-quote — returns fees, exchange rate, and delivery time."""
print("Fetching indicative quote...")
payload = {k: v for k, v in payment_payload.items() if k != "request_id"}
response = requests.post(f"{API_URL}/pay/indicative-quote", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
fee = data.get("fee", {})
total = data.get("estimated_total", {})
print(f" Fee: {fee.get('amount', 0)} {fee.get('currency', currency)}")
print(f" Total: {total.get('amount', 0)} {total.get('currency', currency)}")
return data
# =============================================
# Step 8: Print transfer summary
# =============================================
def print_transfer_summary(sender, counterparty_data, pm_details, pm_address, quote):
"""Display a human-readable transfer summary."""
fee = quote.get("fee", {})
total = quote.get("estimated_total", {})
rate = quote.get("estimated_exchange_rate")
arrival = quote.get("estimated_arrival", {})
sender_name = sender.get("beneficiary", "Unknown")
sender_addr = sender.get("beneficiary_address", {})
sender_ref = sender.get("iban") or sender.get("account_no") or "N/A"
sender_addr_str = ", ".join(filter(None, [
sender_addr.get("street_line1"),
sender_addr.get("city"),
sender_addr.get("country"),
sender_addr.get("postcode"),
])) if sender_addr else ""
pm_addr_str = ", ".join(filter(None, [
pm_address.get("street_line1"),
pm_address.get("city"),
pm_address.get("country"),
pm_address.get("postcode"),
])) if pm_address else ""
print("\n" + "=" * 50)
print("TRANSFER SUMMARY")
print("=" * 50)
print(f"FROM: {sender_name}")
print(f" {sender_ref}")
if sender_addr_str:
print(f" {sender_addr_str}")
print(f"TO: {counterparty_data.get('name', 'Unknown')}")
print(f" {pm_details['account_ref']}")
if pm_addr_str:
print(f" {pm_addr_str}")
print(f"\nREFERENCE: {REFERENCE}")
print("-" * 50)
print(f"Amount: {AMOUNT} {CURRENCY}")
if fee.get("amount") is not None:
print(f"Fee: {fee.get('amount')} {fee.get('currency')}")
if fee.get("breakdown"):
for key, val in fee["breakdown"].items():
print(f" - {key}: {val.get('amount')} {val.get('currency')}")
if rate:
print(f"Exchange rate: {rate}")
if total.get("amount") is not None:
print(f"Total cost: {total.get('amount')} {total.get('currency')}")
if arrival:
print(f"Delivery: {arrival.get('speed', '')} ({arrival.get('date', '')})")
print("=" * 50)
print("\n")
# =============================================
# Step 9: Check account balance
# =============================================
def check_balance(account_id, total_cost, total_currency):
"""GET /accounts/{id} — check if the balance covers the total cost.
Returns True if sufficient, False otherwise.
"""
print("Checking account balance...")
response = requests.get(f"{API_URL}/accounts/{account_id}", headers=HEADERS)
check_response(response)
account = response.json()
balance = account.get("balance", 0)
balance_currency = account.get("currency")
print(f" Balance: {balance} {balance_currency}")
print(f" Total cost: {total_cost} {total_currency}")
if balance_currency == total_currency and balance < total_cost:
print("Insufficient funds.")
return False
return True
# =============================================
# Step 10: Ask for confirmation
# =============================================
def confirm_transfer():
"""Ask the user to confirm the transfer.
Returns True if confirmed, False otherwise.
"""
confirm = input("\nProceed with this transfer? ([y]es/[n]o): ").strip().lower()
if confirm not in ("y", "yes"):
print("Transfer cancelled.")
return False
return True
# =============================================
# Step 11: Execute the transfer
# =============================================
def make_transfer(payload):
"""POST /pay — execute the transfer."""
print("Initiating transfer...")
response = requests.post(f"{API_URL}/pay", json=payload, headers=HEADERS)
check_response(response)
result = response.json()
print(f" Transaction ID: {result.get('id')}")
print(f" State: {result.get('state')}")
return result
# =============================================
# Step 12: Check transaction status
# =============================================
def check_transaction_status(transaction_id):
"""GET /transaction/{id} — check the transaction status."""
print("\nChecking transaction status...")
response = requests.get(f"{API_URL}/transaction/{transaction_id}", headers=HEADERS)
check_response(response)
transaction = response.json()
status = transaction.get("state")
print(f" Transaction ID: {transaction_id}")
print(f" Transaction status: {status}")
if status == "pending":
print("Please check again later.")
return status
# =============================================
# Main flow
# =============================================
def run():
# 1. Retrieve the counterparty
counterparty = retrieve_counterparty(COUNTERPARTY_ID)
# 2. Resolve the payment method and build the receiver
payment_method_id, receiver_key = resolve_payment_method(counterparty)
receiver = build_receiver(counterparty["id"], payment_method_id, receiver_key)
print(f"Payment method ID: {payment_method_id or 'none (revtag-only)'}")
# Print destination details
recipient_type = counterparty.get("profile_type", "unknown")
accounts = counterparty.get("accounts", [])
cards = counterparty.get("cards", [])
is_revolut = "revtag" in counterparty
if receiver_key == "account_id":
for account in accounts:
if account.get("id") == payment_method_id:
bank_country = account.get("bank_country", "unknown")
account_currency = account.get("currency", "unknown")
break
else:
bank_country = "unknown"
account_currency = "unknown"
counterparty_type = "Revolut account" if is_revolut else "bank account"
elif receiver_key == "card_id":
bank_country = "N/A"
account_currency = "N/A"
counterparty_type = "card"
else:
bank_country = counterparty.get("country", "unknown")
account_currency = "N/A"
counterparty_type = "Revolut (revtag)"
print_destination_details(bank_country, account_currency, recipient_type, counterparty_type)
# 3. Check transfer field requirements
transfer_fields = check_transfer_fields(ACCOUNT_ID, receiver)
# Build the payment payload and check for missing required fields
payment_payload, missing_required = build_payment_payload(
transfer_fields, ACCOUNT_ID, receiver, AMOUNT, CURRENCY, REFERENCE
)
if missing_required:
print(f"\nMissing required transfer fields: {missing_required}")
return
# 4. Check and update the counterparty's address
pm_address = check_and_update_address(counterparty, payment_method_id, receiver_key)
# 5. Get payment method details
pm_details = get_payment_method_details(counterparty, payment_method_id)
# 6. Get own account details
sender = get_account_details(ACCOUNT_ID)
# 7. Get an indicative quote
quote = get_indicative_quote(payment_payload, CURRENCY)
# 8. Print transfer summary
print_transfer_summary(sender, counterparty, pm_details, pm_address, quote)
# 9. Check that the account balance is sufficient
total = quote.get("estimated_total", {})
total_cost = total.get("amount", AMOUNT)
total_currency = total.get("currency", CURRENCY)
if not check_balance(ACCOUNT_ID, total_cost, total_currency):
return
# 10. Ask for confirmation
if not confirm_transfer():
return
# 11. Execute the transfer
transaction = make_transfer(payment_payload)
print("Transfer initiated successfully!")
# 12. Check transaction status
check_transaction_status(transaction.get("id"))
if __name__ == "__main__":
try:
run()
except ApiError:
print("\nTransfer flow stopped due to API error.")
except KeyboardInterrupt:
print("\nTransfer flow cancelled by user.")
except Exception as e:
print(f"\nUnexpected error: {e}")Retrieving counterparty 239d6f1f-2222-4ae6-1111-5cc603341b4e...
Name: Beispiel GmbH
Payment method ID: b209281b-aakk-9911-8d83-ac72469cef8f
==================================================
DESTINATION: DE / EUR
RECIPIENT TYPE: business
COUNTERPARTY TYPE: bank account
==================================================
Checking transfer field requirements...
Required fields: ['reference']
Optional fields: ['charge_bearer']
Address is missing or incomplete.
Updating address...
Address updated.
Fetching account bank details...
Sender: Example Corp Ltd
Fetching indicative quote...
Fee: 3 GBP
Total: 1167.58 GBP
==================================================
TRANSFER SUMMARY
==================================================
FROM: Example Corp Ltd
31926819
1 Westminster Way, London, GB, E14 5AB
TO: Beispiel GmbH
DE89370400440532013000
Hauptstraße 45, München, DE, 80331
REFERENCE: Invoice #12345
--------------------------------------------------
Amount: 1000 EUR
Fee: 3 GBP
Exchange rate: 1.16458
Total cost: 1167.58 GBP
Delivery: by_date (2026-07-09)
==================================================
Checking account balance...
Balance: 50000 GBP
Total cost: 1167.58 GBP
Proceed with this transfer? ([y]es/[n]o): y
Initiating transfer...
Transaction ID: 7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f
State: pending
Transfer initiated successfully!
Checking transaction status...
Transaction ID: 7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f
Transaction status: pending
Please check again later.3. Interactive: add or fetch a counterparty, then prepare, validate, and make a transfer
- Choose to create a new counterparty or use an existing one
- New: check supported countries and currencies, verify the destination, build the payload, validate the account name (CoP/VoP), and create the counterparty
- Existing: retrieve the counterparty (enter ID or get the list of counterparties and select from the list), optionally validate the account name (CoP/VoP)
- Resolve the payment method, check transfer field requirements
- Handle currency mismatches: get the exchange rate of the original amount to the destination currency and either switch source account with FX-converted amount suggestion, or switch transfer currency with FX conversion
- Get an indicative quote for the transfer, display a transfer summary, check the account balance, ask for confirmation, and execute the transfer
- Check the transaction status
Sample Python script 3: interactive, extended flow
Interactive script that combines creating a new counterparty or using an existing one, with full validation and transfer flow. Asks the user at each step — no hardcoded counterparty data, account ID, or transfer details required. For existing counterparties, offers a two-stage selection: enter the ID directly or browse the paginated list. For the source account, offers the same two-stage selection: enter the ID or choose from the list of active accounts. Handles currency mismatches (switch source account with FX-converted amount suggestion, or switch transfer currency with FX conversion), multiple payment methods, and CoP/VoP validation for both new and existing counterparties.
Each step is a separate function for clarity.
Adjust the API URL and token at the top to match your setup.
The script can be run interactively, or you can populate it with data before you run it. For the second option, adjust the configuration variables at the top to match your setup.
import requests
import uuid
# =============================================
# Configuration: adjust these values as needed
# =============================================
API_URL = "https://b2b.revolut.com/api/1.0"
API_TOKEN = "YOUR_API_TOKEN" # Replace with your own token
HEADERS = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
# Optional: pre-fill new counterparty details.
# Leave a field empty ("") to be prompted for it interactively.
# Set COUNTERPARTY_DATA to a full dict to override individual fields entirely.
CP_COUNTRY = ""
CP_CURRENCY = ""
CP_RECIPIENT_TYPE = "" # "business" or "personal"
CP_COMPANY_NAME = ""
CP_FIRST_NAME = ""
CP_LAST_NAME = ""
CP_ACCOUNT_NO = ""
CP_SORT_CODE = ""
CP_IBAN = ""
CP_BIC = ""
CP_ROUTE = ""
CP_ROUTING_NUMBER = ""
CP_REVTAG = ""
CP_ADDRESS = {
"street_line1": "",
"city": "",
"region": "",
"country": "",
"postcode": ""
}
COUNTERPARTY_DATA = None # Set to a full dict to override the fields above
# =============================================
# Error handling
# =============================================
class ApiError(Exception):
pass
def check_response(response):
"""Print error details and raise ApiError if the response failed."""
if response.ok:
return
try:
error_data = response.json()
print(f"\nAPI Error (HTTP {response.status_code})")
print(f" Code: {error_data.get('code', 'N/A')}")
print(f" Message: {error_data.get('message', 'N/A')}")
print(f" Error ID: {error_data.get('error_id', 'N/A')}")
except Exception:
print(f"\nAPI Error (HTTP {response.status_code}): {response.text}")
raise ApiError(f"Request failed with status {response.status_code}")
# =============================================
# Print destination details
# =============================================
def print_destination_details(country, currency, recipient_type, counterparty_type):
"""Print a summary of the destination details."""
print("=" * 50)
print(f"DESTINATION: {country} / {currency}")
print(f"RECIPIENT TYPE: {recipient_type}")
print(f"COUNTERPARTY TYPE: {counterparty_type}")
print("=" * 50)
# =============================================
# Helper: ask user for input with default
# =============================================
def ask(prompt, default=""):
"""Prompt the user for input, with an optional default value."""
if default:
prompt = f"{prompt} [{default}]: "
else:
prompt = f"{prompt}: "
value = input(prompt).strip()
return value if value else default
# =============================================
# Step 1: Choose new or existing counterparty
# =============================================
def choose_counterparty_mode():
"""Ask the user whether to create a new counterparty or use an existing one.
Returns "new" or "existing".
"""
print("\n1. Create a new counterparty")
print("2. Use an existing counterparty")
choice = ask("Select an option (1/2)")
while choice not in ("1", "2"):
choice = ask("Invalid choice. Select 1 or 2")
return "new" if choice == "1" else "existing"
# =============================================
# Step 2: Check supported countries and currencies
# =============================================
def check_supported_countries():
"""GET /counterparties/countries — returns supported country/currency pairs."""
print("Checking supported countries and currencies...")
response = requests.get(f"{API_URL}/counterparties/countries", headers=HEADERS)
check_response(response)
data = response.json()
countries = data.get("countries", [])
print(f" Found {len(countries)} supported countries")
return countries
# =============================================
# Step 3: Verify the destination is supported
# =============================================
def verify_destination_supported(countries, country, currency):
"""Check if the desired country/currency combination is supported.
Returns True if supported, False otherwise.
"""
for entry in countries:
if entry.get("country") == country and currency in entry.get("currencies", []):
print(f" Destination {country}/{currency} is supported.")
return True
print(f" Destination {country}/{currency} is not supported.")
return False
# =============================================
# Step 4: Check counterparty field requirements
# =============================================
def check_counterparty_field_requirements(country, currency, recipient_type):
"""GET /counterparties/fields — returns required and applicable fields."""
print("Checking counterparty field requirements...")
params = {"country": country, "currency": currency, "recipient_type": recipient_type}
response = requests.get(f"{API_URL}/counterparties/fields", params=params, headers=HEADERS)
check_response(response)
data = response.json()
required = [f["name"] for f in data.get("fields", []) if f.get("required")]
optional = [f["name"] for f in data.get("fields", []) if not f.get("required")]
print(f" Required fields: {required}")
print(f" Optional fields: {optional}")
return data
# =============================================
# Step 5: Build the counterparty payload
# =============================================
def build_counterparty_payload(field_requirements, country, currency, recipient_type):
"""Build the counterparty payload from config vars or interactive input.
Uses COUNTERPARTY_DATA if set, otherwise individual CP_* vars.
Prompts interactively for any field that is empty.
"""
if COUNTERPARTY_DATA:
print(" Using provided COUNTERPARTY_DATA.")
payload = dict(COUNTERPARTY_DATA)
payload.setdefault("profile_type", recipient_type)
payload.setdefault("bank_country", country)
payload.setdefault("currency", currency)
return payload
payload = {
"profile_type": recipient_type,
"bank_country": country,
"currency": currency,
}
config_map = {
"company_name": CP_COMPANY_NAME,
"first_name": CP_FIRST_NAME,
"last_name": CP_LAST_NAME,
"account_no": CP_ACCOUNT_NO,
"sort_code": CP_SORT_CODE,
"iban": CP_IBAN,
"bic": CP_BIC,
"route": CP_ROUTE,
"routing_number": CP_ROUTING_NUMBER,
"revtag": CP_REVTAG,
}
for field in field_requirements.get("fields", []):
name = field["name"]
default = config_map.get(name, "")
if name == "address":
address = {}
for addr_field in ["street_line1", "city", "region", "country", "postcode"]:
val = CP_ADDRESS.get(addr_field, "")
val = ask(f" Enter {addr_field}", val)
if val:
address[addr_field] = val
if address:
payload["address"] = address
else:
val = ask(f" Enter {name}", default)
if val:
payload[name] = val
# Group first_name/last_name into individual_name for personal counterparties
if recipient_type == "personal":
individual_name = {}
if "first_name" in payload:
individual_name["first_name"] = payload.pop("first_name")
if "last_name" in payload:
individual_name["last_name"] = payload.pop("last_name")
if individual_name:
payload["individual_name"] = individual_name
print(f" Counterparty payload fields: {list(payload.keys())}")
return payload
# =============================================
# Step 6: Validate the account name (CoP/VoP)
# =============================================
def build_cop_request(payload, country, currency, recipient_type):
"""Build the CoP request body based on the destination region.
Returns the request dict, or None if CoP is not available for this destination.
"""
request = {}
if recipient_type == "business":
request["company_name"] = payload.get("company_name")
else:
individual = payload.get("individual_name", {})
request["individual_name"] = {
"first_name": individual.get("first_name") or payload.get("first_name"),
"last_name": individual.get("last_name") or payload.get("last_name"),
}
if country == "GB" and currency == "GBP":
request["account_no"] = payload.get("account_no")
request["sort_code"] = payload.get("sort_code")
elif country == "AU" and currency == "AUD":
request["account_no"] = payload.get("account_no")
request["bsb"] = payload.get("bsb")
elif currency == "EUR" or (country == "RO" and currency == "RON"):
request["iban"] = payload.get("iban")
request["recipient_country"] = country
request["recipient_currency"] = currency
if payload.get("bic"):
request["bic"] = payload.get("bic")
else:
return None
return request
def ask_to_proceed():
"""Ask the user whether to proceed after a non-blocking issue.
Returns True if the user wants to proceed, False otherwise.
"""
confirm = input("Proceed anyway? ([y]es/[n]o): ").strip().lower()
if confirm in ("y", "yes"):
return True
print("Transfer cancelled.")
return False
def validate_account_name(payload, country, currency, recipient_type):
"""POST /account-name-validation — validate the account name (CoP/VoP).
Returns True if the flow should proceed, False if it should stop.
"""
cop_request = build_cop_request(payload, country, currency, recipient_type)
if cop_request is None:
print("CoP/VoP is not available for this destination.")
return ask_to_proceed()
print("Validating account name (CoP/VoP)...")
try:
response = requests.post(f"{API_URL}/account-name-validation", json=cop_request, headers=HEADERS)
check_response(response)
except ApiError:
print(" CoP/VoP check failed.")
return ask_to_proceed()
result = response.json()
result_code = result.get("result_code")
print(f" Result: {result_code}")
if result_code in ("matched", "close_match"):
if result_code == "close_match":
if "company_name" in result:
print(f" Actual name: {result['company_name']}")
elif "individual_name" in result:
name = result["individual_name"]
print(f" Actual name: {name.get('first_name', '')} {name.get('last_name', '')}")
return True
if result_code == "not_matched":
print(" The account name does not match.")
return False
print(f" The check could not be completed (result: {result_code}).")
return ask_to_proceed()
# =============================================
# Step 7: Create the counterparty
# =============================================
def create_counterparty(payload):
"""POST /counterparty — create the counterparty."""
print("Creating counterparty...")
response = requests.post(f"{API_URL}/counterparty", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
print(f" Counterparty ID: {data.get('id')}")
print(f" State: {data.get('state')}")
return data
# =============================================
# Step 8: Retrieve an existing counterparty
# =============================================
def retrieve_existing_counterparty():
"""Ask the user how to identify an existing counterparty, then retrieve it.
Two options:
1. Enter the counterparty ID directly.
2. Browse the list of counterparties (paginated) and select one.
Returns the selected counterparty's data (full GET /counterparty/{id} response).
"""
print("\n1. Enter the counterparty ID")
print("2. Choose from the list of counterparties")
choice = ask("Select an option (1/2)")
while choice not in ("1", "2"):
choice = ask("Invalid choice. Select 1 or 2")
if choice == "1":
cp_id = ask("Enter the counterparty ID")
return retrieve_counterparty(cp_id)
# Browse the list (paginated)
print("Retrieving your counterparties...")
all_counterparties = []
created_before = None
while True:
params = {"limit": 100}
if created_before:
params["created_before"] = created_before
response = requests.get(f"{API_URL}/counterparties", params=params, headers=HEADERS)
check_response(response)
page = response.json()
if not page:
break
all_counterparties.extend(page)
# Check if there are more pages
if len(page) < 100:
break
# Use the last item's created_at as the cursor for the next page
created_before = page[-1].get("created_at")
if not created_before:
break
more = ask(f"\n Loaded {len(all_counterparties)} counterparties so far. Load more? (y/n)", "n")
if more.lower() != "y":
break
if not all_counterparties:
print(" No counterparties found.")
return None
print(f"\n Found {len(all_counterparties)} counterpart{'y' if len(all_counterparties) == 1 else 'ies'}:\n")
for i, cp in enumerate(all_counterparties):
name = cp.get("name", "Unknown")
profile = cp.get("profile_type", "?")
cp_type = "Revolut (revtag)" if "revtag" in cp else "bank account" if cp.get("accounts") else "card" if cp.get("cards") else "unknown"
# Summarise payment methods
methods = []
for acc in cp.get("accounts", []):
cur = acc.get("currency", "?")
country = acc.get("bank_country", "?")
methods.append(f"{cur} ({country})")
for card in cp.get("cards", []):
methods.append(f"card ({card.get('currency', '?')})")
if "revtag" in cp:
methods.append(f"@{cp['revtag']}")
methods_str = ", ".join(methods) if methods else "no methods"
print(f" {i + 1}. {name} [{profile}, {cp_type}] — {methods_str}")
choice = ask(f"\n Select a counterparty (1-{len(all_counterparties)})")
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(all_counterparties):
choice = ask(f" Invalid choice. Enter a number 1-{len(all_counterparties)}")
selected = all_counterparties[int(choice) - 1]
cp_id = selected["id"]
print(f"\n Selected: {selected.get('name', 'Unknown')} ({cp_id})")
return retrieve_counterparty(cp_id)
def retrieve_counterparty(counterparty_id):
"""GET /counterparty/{id} — returns the counterparty data."""
print(f"Retrieving counterparty {counterparty_id}...")
response = requests.get(f"{API_URL}/counterparty/{counterparty_id}", headers=HEADERS)
check_response(response)
data = response.json()
print(f" Name: {data.get('name', 'Unknown')}")
return data
# =============================================
# Step 9: Resolve the payment method
# =============================================
def resolve_payment_method(counterparty_data):
"""Determine which payment method to use.
If the counterparty has a single payment method, use it directly.
If multiple, list them and ask the user to select.
If none (revtag-only), return (None, None).
Returns (payment_method_id, receiver_key).
"""
accounts = counterparty_data.get("accounts", [])
cards = counterparty_data.get("cards", [])
if not accounts and not cards:
return None, None
all_methods = []
for account in accounts:
all_methods.append(("account_id", account))
for card in cards:
all_methods.append(("card_id", card))
if len(all_methods) == 1:
receiver_key, method = all_methods[0]
print(f" Single payment method found: {method.get('id')}")
return method["id"], receiver_key
print("\nMultiple payment methods available:")
for i, (key, method) in enumerate(all_methods, 1):
if key == "account_id":
method_type = "bank account"
ref = method.get("iban") or method.get("account_no") or "Revolut account"
currency = method.get("currency", "N/A")
print(f" {i}. [{method_type}] {method.get('name', 'N/A')} — {ref} ({currency})")
print(f" ID: {method['id']}")
else:
method_type = "card"
ref = f"*{method.get('last_digits', '????')} ({method.get('scheme', 'unknown')})"
print(f" {i}. [{method_type}] {method.get('name', 'N/A')} — {ref}")
print(f" ID: {method['id']}")
print(f" {len(all_methods) + 1}. Cancel")
choice = ask(f"Select a payment method (1-{len(all_methods) + 1})")
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(all_methods) + 1:
choice = ask(f"Invalid choice. Select 1-{len(all_methods) + 1}")
idx = int(choice) - 1
if idx == len(all_methods):
print("Transfer cancelled.")
return None, None
receiver_key, method = all_methods[idx]
return method["id"], receiver_key
def build_receiver(counterparty_id, payment_method_id, receiver_key):
"""Build the receiver object for /pay endpoints."""
receiver = {"counterparty_id": counterparty_id}
if payment_method_id:
receiver[receiver_key] = payment_method_id
return receiver
# =============================================
# Step 10: Get destination details
# =============================================
def get_destination_details(counterparty_data, payment_method_id, receiver_key):
"""Extract destination details from the resolved payment method.
Returns (country, currency, recipient_type, counterparty_type).
"""
recipient_type = counterparty_data.get("profile_type", "unknown")
accounts = counterparty_data.get("accounts", [])
is_revolut = "revtag" in counterparty_data
if receiver_key == "account_id":
for account in accounts:
if account.get("id") == payment_method_id:
bank_country = account.get("bank_country", "unknown")
account_currency = account.get("currency", "unknown")
break
else:
bank_country = "unknown"
account_currency = "unknown"
counterparty_type = "Revolut account" if is_revolut else "bank account"
elif receiver_key == "card_id":
bank_country = "N/A"
account_currency = "N/A"
counterparty_type = "card"
else:
bank_country = counterparty_data.get("country", "unknown")
account_currency = "N/A"
counterparty_type = "Revolut (revtag)"
return bank_country, account_currency, recipient_type, counterparty_type
# =============================================
# Step 11: Ask for source account and transfer details
# =============================================
def ask_source_account():
"""Ask the user how to identify their source account, then retrieve it.
Two options:
1. Enter the account ID directly.
2. Choose from the list of active accounts.
Returns the account ID.
"""
print("\n1. Enter your account ID (the pocket to send from)")
print("2. Choose from the list of your accounts")
choice = ask("Select an option (1/2)")
while choice not in ("1", "2"):
choice = ask("Invalid choice. Select 1 or 2")
if choice == "1":
return ask("Enter your account ID")
# List accounts and let the user pick
print("Retrieving your accounts...")
response = requests.get(f"{API_URL}/accounts", headers=HEADERS)
check_response(response)
accounts = response.json()
active = [a for a in accounts if a.get("state") == "active"]
if not active:
print(" No active accounts found.")
return ask("Enter your account ID manually")
print(f"\n Found {len(active)} active account{'s' if len(active) != 1 else ''}:\n")
for i, acc in enumerate(active):
name = acc.get("name", "Unknown")
cur = acc.get("currency", "?")
balance = acc.get("balance", "?")
print(f" {i + 1}. {name} [{cur}] — balance: {balance}")
choice = ask(f"\n Select an account (1-{len(active)})")
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(active):
choice = ask(f" Invalid choice. Enter a number 1-{len(active)}")
selected = active[int(choice) - 1]
acc_id = selected["id"]
print(f"\n Selected: {selected.get('name', 'Unknown')} ({acc_id})")
return acc_id
def ask_transfer_details():
"""Ask the user for the transfer amount and currency.
Returns (amount, currency).
"""
amount_str = ask("Enter the amount to transfer")
while not amount_str or not amount_str.replace(".", "").replace(",", "").isdigit():
amount_str = ask("Invalid amount. Enter a numeric value")
amount = float(amount_str.replace(",", ""))
currency = ask("Enter the transfer currency (e.g. GBP)").upper()
return amount, currency
# =============================================
# Step 12: Check currency mismatch
# =============================================
def get_account_currency(account_id):
"""GET /accounts/{id} — returns the account's currency."""
response = requests.get(f"{API_URL}/accounts/{account_id}", headers=HEADERS)
check_response(response)
account = response.json()
return account.get("currency", "unknown")
def get_matching_accounts(currency):
"""GET /accounts — find active accounts matching the given currency.
Returns a list of matching account dicts.
"""
response = requests.get(f"{API_URL}/accounts", headers=HEADERS)
check_response(response)
accounts = response.json()
return [a for a in accounts if a.get("currency") == currency and a.get("state") == "active"]
def get_exchange_rate(from_currency, to_currency, amount):
"""GET /rate — returns the exchange rate and converted amount."""
params = {"from": from_currency, "to": to_currency, "amount": amount}
response = requests.get(f"{API_URL}/rate", params=params, headers=HEADERS)
check_response(response)
return response.json()
def check_currency_mismatch(account_id, transfer_currency, destination_currency, amount):
"""Check if the transfer currency matches the destination currency.
If mismatch, offer the user three options:
1. Switch to a source account whose currency matches the destination
2. Switch the transfer currency to the destination currency (with FX preview)
3. Cancel
Returns (account_id, transfer_currency, amount_or_flag):
- amount_or_flag is None when currencies match or option 2 is chosen
(option 2 returns the string "fx" so the caller can do the conversion)
- amount_or_flag is a float when option 1 is chosen (new amount in dest currency)
- Returns None entirely if cancelled.
"""
if transfer_currency == destination_currency:
return account_id, transfer_currency, None
print(f"\nCurrency mismatch: transfer is {transfer_currency}, destination is {destination_currency}.")
print("Options:")
print(" 1. Use a different source account (matching destination currency)")
print(" 2. Switch transfer currency to destination currency (FX conversion)")
print(" 3. Cancel")
choice = ask("Select an option (1/2/3)")
while choice not in ("1", "2", "3"):
choice = ask("Invalid choice. Select 1, 2, or 3")
if choice == "3":
print("Transfer cancelled.")
return None
if choice == "1":
matching = get_matching_accounts(destination_currency)
if not matching:
print(f" No active accounts found with currency {destination_currency}.")
print(" Consider option 2 (switch transfer currency) instead.")
return check_currency_mismatch(account_id, transfer_currency, destination_currency, amount)
print(f"\nAccounts matching {destination_currency}:")
for i, acc in enumerate(matching, 1):
print(f" {i}. {acc.get('name', 'N/A')} — balance: {acc.get('balance', 0)} {acc.get('currency')}")
print(f" ID: {acc['id']}")
print(f" {len(matching) + 1}. Cancel")
sel = ask(f"Select an account (1-{len(matching) + 1})")
while not sel.isdigit() or int(sel) < 1 or int(sel) > len(matching) + 1:
sel = ask(f"Invalid choice. Select 1-{len(matching) + 1}")
idx = int(sel) - 1
if idx == len(matching):
print("Transfer cancelled.")
return None
new_account_id = matching[idx]["id"]
print(f" Switched to account {new_account_id} ({matching[idx].get('currency')})")
# Convert the original amount to the destination currency via FX rate
print(f"\n Converting {amount} {transfer_currency} → {destination_currency}...")
fx_data = get_exchange_rate(transfer_currency, destination_currency, amount)
rate = fx_data.get("rate", 0)
converted_amount = round(amount * rate, 2)
print(f" FX rate: {transfer_currency} → {destination_currency}: {rate}")
print(f" Suggested amount: {converted_amount} {destination_currency}")
entered = ask(f" Use this amount? (y/n)", "y")
if entered.lower() in ("y", "yes"):
return new_account_id, destination_currency, converted_amount
amount_str = ask(f" Enter your own amount ({destination_currency})")
while not amount_str or not amount_str.replace(".", "").replace(",", "").isdigit():
amount_str = ask(" Invalid amount. Enter a numeric value")
return new_account_id, destination_currency, float(amount_str.replace(",", ""))
if choice == "2":
print(f"\nChecking FX rate: {transfer_currency} → {destination_currency}...")
fx_data = get_exchange_rate(transfer_currency, destination_currency, amount)
rate = fx_data.get("rate", 0)
converted_amount = round(amount * rate, 2)
print(f" FX rate: {transfer_currency} → {destination_currency}: {rate}")
print(f" Converted amount: {converted_amount} {destination_currency}")
confirm = ask(f"Confirm transfer of {converted_amount} {destination_currency}? (y/n)")
if confirm not in ("y", "yes"):
print("Transfer cancelled.")
return None
return account_id, destination_currency, converted_amount
# =============================================
# Step 13: Check transfer field requirements
# =============================================
def check_transfer_field_requirements(account_id, receiver):
"""POST /pay/fields — returns required and optional fields for the transfer."""
print("Checking transfer field requirements...")
payload = {"account_id": account_id, "receiver": receiver}
response = requests.post(f"{API_URL}/pay/fields", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
required = [f["name"] for f in data.get("fields", []) if f.get("required")]
optional = [f["name"] for f in data.get("fields", []) if not f.get("required")]
print(f" Required fields: {required}")
print(f" Optional fields: {optional}")
return data
def build_payment_payload(transfer_fields, account_id, receiver, amount, currency, reference):
"""Build the payment payload from always required fields + conditionally available fields.
Returns (payload, missing_required) where missing_required is a list of required
field names that have no value.
"""
payload = {
"request_id": str(uuid.uuid4()),
"account_id": account_id,
"receiver": receiver,
"amount": amount,
"currency": currency,
}
provided = {"reference": reference}
missing_required = []
for field in transfer_fields.get("fields", []):
name = field["name"]
is_required = field.get("required", False)
if name in provided and provided[name] is not None:
payload[name] = provided[name]
elif is_required:
missing_required.append(name)
return payload, missing_required
# =============================================
# Step 14: Get an indicative quote
# =============================================
def get_indicative_quote(payment_payload, currency):
"""POST /pay/indicative-quote — returns fees, exchange rate, and delivery time."""
print("Fetching indicative quote...")
payload = {k: v for k, v in payment_payload.items() if k != "request_id"}
response = requests.post(f"{API_URL}/pay/indicative-quote", json=payload, headers=HEADERS)
check_response(response)
data = response.json()
fee = data.get("fee", {})
total = data.get("estimated_total", {})
print(f" Fee: {fee.get('amount', 0)} {fee.get('currency', currency)}")
print(f" Total: {total.get('amount', 0)} {total.get('currency', currency)}")
return data
# =============================================
# Step 15: Get own account details and print transfer summary
# =============================================
def get_account_details(account_id):
"""GET /accounts/{id}/bank-details — returns the sender's bank details.
Falls back to GET /accounts/{id} for the account name when bank details
are not available (e.g. currency pockets without local bank details).
"""
print("Fetching account bank details...")
response = requests.get(f"{API_URL}/accounts/{account_id}/bank-details", headers=HEADERS)
check_response(response)
data = response.json()
if isinstance(data, list) and data:
entry = data[0]
elif isinstance(data, dict):
entry = data
else:
# No bank details — fall back to account info for at least the name
print(" No bank details available, fetching account info...")
acct_resp = requests.get(f"{API_URL}/accounts/{account_id}", headers=HEADERS)
check_response(acct_resp)
acct = acct_resp.json()
entry = {"beneficiary": acct.get("name", "Unknown")}
print(f" Sender: {entry.get('beneficiary', 'Unknown')}")
return entry
def print_transfer_summary(sender, counterparty_name, pm_details, pm_address, amount, currency, reference, quote):
"""Display a human-readable transfer summary."""
fee = quote.get("fee", {})
total = quote.get("estimated_total", {})
rate = quote.get("estimated_exchange_rate")
arrival = quote.get("estimated_arrival", {})
sender_name = sender.get("beneficiary", "Unknown")
sender_addr = sender.get("beneficiary_address", {})
sender_ref = sender.get("iban") or sender.get("account_no") or "N/A"
sender_addr_str = ", ".join(filter(None, [
sender_addr.get("street_line1"),
sender_addr.get("city"),
sender_addr.get("country"),
sender_addr.get("postcode"),
])) if sender_addr else ""
pm_addr_str = ", ".join(filter(None, [
pm_address.get("street_line1"),
pm_address.get("city"),
pm_address.get("country"),
pm_address.get("postcode"),
])) if pm_address else ""
print("\n" + "=" * 50)
print("TRANSFER SUMMARY")
print("=" * 50)
print(f"FROM: {sender_name}")
print(f" {sender_ref}")
if sender_addr_str:
print(f" {sender_addr_str}")
print(f"TO: {counterparty_name}")
print(f" {pm_details['account_ref']}")
if pm_addr_str:
print(f" {pm_addr_str}")
print(f"\nREFERENCE: {reference}")
print("-" * 50)
print(f"Amount: {amount} {currency}")
if fee.get("amount") is not None:
print(f"Fee: {fee.get('amount')} {fee.get('currency')}")
if fee.get("breakdown"):
for key, val in fee["breakdown"].items():
print(f" - {key}: {val.get('amount')} {val.get('currency')}")
if rate:
print(f"Exchange rate: {rate}")
if total.get("amount") is not None:
print(f"Total cost: {total.get('amount')} {total.get('currency')}")
if arrival:
print(f"Delivery: {arrival.get('speed', '')} ({arrival.get('date', '')})")
print("=" * 50)
print("\n")
# =============================================
# Step 16: Check account balance
# =============================================
def check_balance(account_id, total_cost, total_currency):
"""GET /accounts/{id} — check if the balance covers the total cost.
Returns True if sufficient, False otherwise.
"""
print("Checking account balance...")
response = requests.get(f"{API_URL}/accounts/{account_id}", headers=HEADERS)
check_response(response)
account = response.json()
balance = account.get("balance", 0)
balance_currency = account.get("currency")
print(f" Balance: {balance} {balance_currency}")
print(f" Total cost: {total_cost} {total_currency}")
if balance_currency == total_currency and balance < total_cost:
print("Insufficient funds.")
return False
return True
# =============================================
# Step 17: Ask for confirmation
# =============================================
def confirm_transfer():
"""Ask the user to confirm the transfer.
Returns True if confirmed, False otherwise.
"""
confirm = input("\nProceed with this transfer? ([y]es/[n]o): ").strip().lower()
if confirm not in ("y", "yes"):
print("Transfer cancelled.")
return False
return True
# =============================================
# Step 18: Execute the transfer
# =============================================
def make_transfer(payload):
"""POST /pay — execute the transfer."""
print("Initiating transfer...")
response = requests.post(f"{API_URL}/pay", json=payload, headers=HEADERS)
check_response(response)
result = response.json()
print(f" Transaction ID: {result.get('id')}")
print(f" State: {result.get('state')}")
return result
# =============================================
# Step 19: Check transaction status (optional)
# =============================================
def check_transaction_status(transaction_id):
"""GET /transaction/{id} — check the transaction status."""
print("\nChecking transaction status...")
response = requests.get(f"{API_URL}/transaction/{transaction_id}", headers=HEADERS)
check_response(response)
transaction = response.json()
status = transaction.get("state")
print(f" Transaction ID: {transaction_id}")
print(f" Transaction status: {status}")
if status == "pending":
print("Please check again later.")
return status
# =============================================
# Main flow
# =============================================
def run():
# 1. Choose new or existing counterparty
mode = choose_counterparty_mode()
if mode == "new":
# 2. Get destination details (from config or interactive)
country = ask("Enter counterparty bank country (e.g. GB)", CP_COUNTRY)
currency = ask("Enter counterparty account currency (e.g. GBP)", CP_CURRENCY)
recipient_type = ask("Enter recipient type (business/personal)", CP_RECIPIENT_TYPE)
# Print destination details (pre-creation)
counterparty_type = "Revolut (revtag)" if CP_REVTAG else "bank account"
print_destination_details(country, currency, recipient_type, counterparty_type)
# 3. Check supported countries and currencies
countries = check_supported_countries()
# 4. Verify the destination is supported
if not verify_destination_supported(countries, country, currency):
return
# 5. Check counterparty field requirements
field_requirements = check_counterparty_field_requirements(country, currency, recipient_type)
# 6. Build the counterparty payload
counterparty_payload = build_counterparty_payload(field_requirements, country, currency, recipient_type)
# 7. Validate the account name (CoP/VoP)
if not validate_account_name(counterparty_payload, country, currency, recipient_type):
return
# 8. Create the counterparty
counterparty = create_counterparty(counterparty_payload)
else:
# 9. Retrieve an existing counterparty (enter ID or choose from list)
counterparty = retrieve_existing_counterparty()
# 10. Validate the account name (CoP/VoP) — offer for existing counterparties too
accounts = counterparty.get("accounts", [])
country = counterparty.get("country", "")
if accounts:
if not country:
country = accounts[0].get("bank_country", "")
currency = accounts[0].get("currency", "")
recipient_type = counterparty.get("profile_type", "business")
else:
currency = ""
recipient_type = counterparty.get("profile_type", "business")
if country and currency:
cop_payload = {}
cp_name = counterparty.get("name", "")
if recipient_type == "business":
cop_payload["company_name"] = cp_name
else:
# Split the full name into first_name/last_name for CoP
parts = cp_name.split()
if len(parts) == 1:
cop_payload["individual_name"] = {"first_name": parts[0], "last_name": ""}
elif len(parts) == 2:
cop_payload["individual_name"] = {"first_name": parts[0], "last_name": parts[1]}
else:
# Multiple parts — list split options for the user to choose
print(f"\n The counterparty name is: {cp_name}")
print(" Select the correct first name / last name split:")
for i, split_idx in enumerate(range(1, len(parts)), 1):
first = " ".join(parts[:split_idx])
last = " ".join(parts[split_idx:])
print(f" {i}. First: {first} | Last: {last}")
sel = ask(f" Select an option (1-{len(parts) - 1})")
while not sel.isdigit() or int(sel) < 1 or int(sel) > len(parts) - 1:
sel = ask(f" Invalid choice. Enter 1-{len(parts) - 1}")
split_idx = int(sel)
cop_payload["individual_name"] = {
"first_name": " ".join(parts[:split_idx]),
"last_name": " ".join(parts[split_idx:]),
}
if accounts:
cop_payload["account_no"] = accounts[0].get("account_no", "")
cop_payload["sort_code"] = accounts[0].get("sort_code", "")
cop_payload["iban"] = accounts[0].get("iban", "")
cop_payload["bic"] = accounts[0].get("bic", "")
print("\nWould you like to validate the account name (CoP/VoP)?")
if ask("Run CoP/VoP check? (y/n)") in ("y", "yes"):
if not validate_account_name(cop_payload, country, currency, recipient_type):
return
# 11. Resolve the payment method
payment_method_id, receiver_key = resolve_payment_method(counterparty)
if not payment_method_id and not receiver_key and not ("revtag" in counterparty):
print("No payment method available.")
return
receiver = build_receiver(counterparty["id"], payment_method_id, receiver_key)
print(f"Payment method ID: {payment_method_id or 'none (revtag-only)'}")
# 12. Get destination details
bank_country, dest_currency, recipient_type, cp_type = get_destination_details(
counterparty, payment_method_id, receiver_key
)
print_destination_details(bank_country, dest_currency, recipient_type, cp_type)
# 13. Ask for source account and transfer details
account_id = ask_source_account()
amount, transfer_currency = ask_transfer_details()
reference = ask("Enter the payment reference")
# 14. Check currency mismatch
if dest_currency and dest_currency != "N/A":
result = check_currency_mismatch(account_id, transfer_currency, dest_currency, amount)
if result is None:
return
account_id, transfer_currency, mismatch_result = result
if mismatch_result is not None:
amount = mismatch_result
# 15. Check transfer field requirements
transfer_fields = check_transfer_field_requirements(account_id, receiver)
# Build the payment payload and check for missing required fields
payment_payload, missing_required = build_payment_payload(
transfer_fields, account_id, receiver, amount, transfer_currency, reference
)
if missing_required:
print(f"\nMissing required transfer fields: {missing_required}")
return
# 16. Get an indicative quote
quote = get_indicative_quote(payment_payload, transfer_currency)
# 17. Print transfer summary
sender = get_account_details(account_id)
pm_details = {
"account_ref": "",
"address": {},
}
accounts = counterparty.get("accounts", [])
cards = counterparty.get("cards", [])
for account in accounts:
if account.get("id") == payment_method_id:
pm_details = {
"account_ref": account.get("iban") or account.get("account_no") or "Revolut account",
"address": account.get("address", {}),
}
break
for card in cards:
if card.get("id") == payment_method_id:
pm_details = {
"account_ref": f"Card *{card.get('last_digits', '????')} ({card.get('scheme', 'unknown')})",
"address": {},
}
break
if not pm_details["account_ref"]:
pm_details = {"account_ref": "Revolut account (revtag)", "address": {}}
print_transfer_summary(sender, counterparty.get("name", "Unknown"), pm_details, pm_details["address"],
amount, transfer_currency, reference, quote)
# 18. Check that the account balance is sufficient
total = quote.get("estimated_total", {})
total_cost = total.get("amount", amount)
total_currency = total.get("currency", transfer_currency)
if not check_balance(account_id, total_cost, total_currency):
return
# 19. Ask for confirmation
if not confirm_transfer():
return
# 20. Execute the transfer
transaction = make_transfer(payment_payload)
print("Transfer initiated successfully!")
# 21. Ask if user wants to check transaction status
check_status = ask("\nCheck transaction status now? (y/n)")
if check_status in ("y", "yes"):
check_transaction_status(transaction.get("id"))
if __name__ == "__main__":
try:
run()
except ApiError:
print("\nTransfer flow stopped due to API error.")
except KeyboardInterrupt:
print("\nTransfer flow cancelled by user.")
except Exception as e:
print(f"\nUnexpected error: {e}")1. Create a new counterparty
2. Use an existing counterparty
Select an option (1/2): 1
Enter counterparty bank country (e.g. GB): GB
Enter counterparty account currency (e.g. GBP): GBP
Enter recipient type (business/personal): business
==================================================
DESTINATION: GB / GBP
RECIPIENT TYPE: business
COUNTERPARTY TYPE: bank account
==================================================
Checking supported countries and currencies...
Found 213 supported countries
Destination GB/GBP is supported.
Checking counterparty field requirements...
Required fields: ['company_name', 'account_no', 'sort_code', 'address']
Optional fields: []
Enter company_name: Example Vendor Ltd
Enter account_no: 70294360
Enter sort_code: 30-96-17
Enter street_line1: 1 Canada Square
Enter city: London
Enter region:
Enter country: GB
Enter postcode: E14 5AB
Counterparty payload fields: ['profile_type', 'bank_country', 'currency', 'company_name', 'account_no', 'sort_code', 'address']
Validating account name (CoP/VoP)...
Result: matched
Creating counterparty...
Counterparty ID: b53fdd78-8d67-4f63-a103-eeeeef53cac8
State: created
Single payment method found: 5c9e171c-7e23-4d6a-b768-aaaaaba535f3
Payment method ID: 5c9e171c-7e23-4d6a-b768-aaaaaba535f3
==================================================
DESTINATION: GB / GBP
RECIPIENT TYPE: business
COUNTERPARTY TYPE: bank account
==================================================
1. Enter your account ID (the pocket to send from)
2. Choose from the list of your accounts
Select an option (1/2): 1
Enter your account ID: a3b4c5d6-e7f8-9012-abcd-ef12345678901
Enter the amount to transfer: 1000
Enter the transfer currency (e.g. GBP): GBP
Enter the payment reference: Invoice #12345
Checking transfer field requirements...
Required fields: ['reference']
Optional fields: ['charge_bearer']
Fetching indicative quote...
Fee: 0 GBP
Total: 1000 GBP
Fetching account bank details...
Sender: Example Corp Ltd
==================================================
TRANSFER SUMMARY
==================================================
FROM: Example Corp Ltd
92109701
11 Washington Walk APT 327, Washington, GB, PE3 9UP
TO: Example Vendor Ltd
70294360
1 Canada Square, London, GB, E14 5AB
REFERENCE: Invoice #12345
--------------------------------------------------
Amount: 1000 GBP
Fee: 0 GBP
Total cost: 1000 GBP
Delivery: in_seconds (2026-07-07)
==================================================
Checking account balance...
Balance: 50000 GBP
Total cost: 1000 GBP
Proceed with this transfer? ([y]es/[n]o): y
Initiating transfer...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
State: pending
Transfer initiated successfully!
Check transaction status now? (y/n): y
Checking transaction status...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
Transaction status: completed1. Create a new counterparty
2. Use an existing counterparty
Select an option (1/2): 2
1. Enter the counterparty ID
2. Choose from the list of counterparties
Select an option (1/2): 2
Retrieving your counterparties...
Loaded 100 counterparties so far. Load more? (y/n) [n]: n
Found 100 counterparties:
1. Example Vendor Ltd [business, bank account] — GBP (GB)
2. Acme Corp [business, bank account] — EUR (DE)
3. John Smith [personal, Revolut (revtag)] — @johnsmith
... (97 more)
Select a counterparty (1-100): 1
Selected: Example Vendor Ltd (b53fdd78-8d67-4f63-a103-eeeeef53cac8)
Retrieving counterparty b53fdd78-8d67-4f63-a103-eeeeef53cac8...
Name: Example Vendor Ltd
Would you like to validate the account name (CoP/VoP)?
Run CoP/VoP check? (y/n): y
Validating account name (CoP/VoP)...
Result: matched
Single payment method found: 5c9e171c-7e23-4d6a-b768-aaaaaba535f3
Payment method ID: 5c9e171c-7e23-4d6a-b768-aaaaaba535f3
==================================================
DESTINATION: GB / GBP
RECIPIENT TYPE: business
COUNTERPARTY TYPE: bank account
==================================================
1. Enter your account ID (the pocket to send from)
2. Choose from the list of your accounts
Select an option (1/2): 2
Retrieving your accounts...
Found 5 active accounts:
1. GBP pocket [GBP] — balance: 50000
2. EUR pocket [EUR] — balance: 10000
3. USD pocket [USD] — balance: 5000
4. Payroll [GBP] — balance: 12000
5. Misc [GBP] — balance: 0
Select an account (1-5): 1
Selected: GBP pocket (a3b4c5d6-e7f8-9012-abcd-ef12345678901)
Enter the amount to transfer: 1000
Enter the transfer currency (e.g. GBP): GBP
Enter the payment reference: Invoice #12345
Checking transfer field requirements...
Required fields: ['reference']
Optional fields: ['charge_bearer']
Fetching indicative quote...
Fee: 0 GBP
Total: 1000 GBP
Fetching account bank details...
Sender: Example Corp Ltd
==================================================
TRANSFER SUMMARY
==================================================
FROM: Example Corp Ltd
92109701
11 Washington Walk APT 327, Washington, GB, PE3 9UP
TO: Example Vendor Ltd
70294360
1 Canada Square, London, GB, E14 5AB
REFERENCE: Invoice #12345
--------------------------------------------------
Amount: 1000 GBP
Fee: 0 GBP
Total cost: 1000 GBP
Delivery: in_seconds (2026-07-07)
==================================================
Checking account balance...
Balance: 50000 GBP
Total cost: 1000 GBP
Proceed with this transfer? ([y]es/[n]o): y
Initiating transfer...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
State: pending
Transfer initiated successfully!
Check transaction status now? (y/n): y
Checking transaction status...
Transaction ID: 3a2b1c0d-9e8f-4a7b-6c5d-4e3f2a1b0c9d
Transaction status: completedWhat's next
Now that you've prepared, validated, and made your transaction, learn how to:
- Retrieve your transaction data and check its details
- Map transaction data to a related transaction or card
See also how to automate payouts at scale.