Atlas for Platform - Migration Guide
This guide is for Verto partners using Atlas for platforms (managing multi-currency sub-accounts for Collections on Behalf of underlying clients, FX, and Payments on Behalf of Underlying Clients)
This document serves as the official migration roadmap for Platforms partners utilizing Atlas for multi-currency collections,FX and settlement workflows on behalf of underlying customers (sub accounts). It outlines deprecated legacy endpoints, new API specifications, and the technical changes required to maintain operational continuity.
While core parts of the sub account workflow remain unchanged, there are breaking changes to our FX Services and Payout workflows that require your immediate attention.
| Workflow | Legacy Status | Impact Level | Action Required |
|---|---|---|---|
| Authorization (Login) | Breaking | Critical | Mandatory baseline update. Migrate to the new base URL and implement RSA-encrypted challenge-response payloads. Static API keys will be blocked |
| Sub-Account Onboarding | No Change | None | Keep current implementation. |
| Get Wallets | Deprecated | Low | Migrate from Get a Wallet to Get all Wallets. |
| Receive Funding Webhook | No Change | None | Keep current implementation. |
| Get FX Rate | Breaking | Breaking Change | Change method to POST; update nested body parameters. |
| Create FX Trade | Breaking | Breaking Change | Migrate to new FX trading endpoints and payloads. |
| Get Beneficiaries | Breaking | Breaking Change | Migrate to new response structure |
| Wallet Payout / Withdrawal | Breaking | Breaking Change | Migrate to the new payout framework. |
🛠️ Phase-by-Phase Technical Changes
Phase 0: Authorization & Authentication
Before interacting with any updated v1.1 endpoints, all Atlas clients must update their Authentication setup, as the new Login API endpoint requires a certificated based authentication and different URL.
Why is this a breaking change?
If you try to send your plain, raw API key from the Legacy Login method to the new system, the connection will be rejected.
Additionally, because a live timestamp is mixed into the package during encryption, the login request automatically expires after 30 seconds.
Your backend must create this encrypted package dynamically right before making the login call. To get started on migrating the login:
- Change your login URL:
- Old Endpoint:
POSThttps://api-v3-sandbox.vertofx.com/users/login - New Endpoint:
POSThttps://api-company-sandbox.vertofx.com/users/login
- Swap the Value:
In your JSON request body, keep the parameter name exactly as "apiKey", but replace your plain text key with your newly encrypted text block.
{
"clientId": "YOUR_28_DIGIT_CLIENT_ID",
"apiKey": "RAW_STATIC_API_KEY_STRING",
"mode": "apiKey"
}{
"clientId": "YOUR_28_DIGIT_CLIENT_ID",
"apiKey": "eyJKdW5lMTg...[Encrypted Base64 String Input]...",
"mode": "apiKey"
}For more technical examples, required encryption settings, and instructions on how to download your digital certificate from the dashboard, please see the Authentication Guide and the Certificate-Based Authentication Guide.
Phase 1: Account Creation & Funding Sweeps (Low/No Impact)
The underlying operational flow for managing sub-accounts in local currencies e.g KES, TZS, UGX, NGN remains identical. If you have built integrations around sub-account creation, you only need to verify one endpoint boundary:
| Actions | Status |
|---|---|
| Create Sub-Account | No change |
| Add Onboarding Data | No change |
| Create Wallet | No change |
| Get Account Details for Wallet (Funding Methods) | No change |
| Receive Funding Webhook | No change |
⚠️ Critical Endpoint Deprecation Notice: If you are calling the legacy Get a Wallet (v2.2) endpoint, you must transition to the new Get all Wallets endpoint immediately.
Phase 2: FX Services (Breaking Changes) 🚨
For platforms collecting in one currency (such as NGN or KES) and converting them into liquidity pools or settlement currencies (such as USD), the currency exchange layer contains critical breaking structural changes.
A. Get FX Rate
Migrating from the old Get an FX Rate endpoint to the new Get FX Rate introduces several critical breaking changes. You will need to rewrite the HTTP request method, the URL endpoints, and entirely restructure the data payload.
Here is a detailed breakdown of the breaking changes:
Base URL, Route & HTTP Method Change
The host domain and path have completely changed. Ensure your environment variables/configuration files are updated.
Old API: (GET) https://api-v3-sandbox.vertofx.com/orders/v2.1/fx
Used a GET request, meaning parameters were passed visibly in the URL string.
New API: (POST)https://api-exchange-now-beta.vertofx.com/fx/rate
Uses a POST request. Parameters must now be passed securely inside the HTTP request body as a JSON payload. Any code natively calling this as a GET request will fail with a 405 Method Not Allowed or 404 Not Found error.
Payload Restructuring (Flat Query String to Nested Objects)
The parameter format has changed from flat query strings to objects inside a JSON body.
Legacy API: Passed strings directly (currencyFrom=USD¤cyTo=NGN).
https://api-v3-sandbox.vertofx.com/orders/v2.1/fx?currencyFrom=GHS¤cyTo=USD' curl --request POST \
--url https://api-exchange-now-beta.vertofx.com/fx/rate \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"amountCurrency": "GBP",
"amount": 6543423.45,
"paymentMode": "immediate",
"currencyFrom": {
"id": 1,
"currencyName": "AED"
},
"currencyTo": {
"id": 1,
"currencyName": "AED"
}
}
'New API Requires currencyFrom and currencyTo to be formatted as JSON objects, not flat strings. Passing strings like "currencyFrom": "USD" in the new payload will result in a 400 Bad Request schema validation error.
New Mandatory Parameters (amount and amountCurrency)**
The Legacy API only required the currency pair (currencyFrom and currencyTo). You did not have to provide the transactional volume to get a general rate. The New API now introduces mandatory amount and amountCurrency parameters into the mix.
Introduction of paymentMode
The Legacy API had no logic for settlement timing on the rate fetch layer. The New API introduces an optional paymentMode parameter which defaults to immediate, but allows later. This gives your system new flexibility if you intend to book forward trades or delayed settlement cycles.
B. Create FX Trade
The new API endpoint /fx/payments is much more robust, treating FX trades as unified payment flows. It replaces the legacy API endpoint (BUY/SELL) with explicit source/target amounts and introduces wallet identifiers for a more secure and descriptive transaction structure.
Here are the key changes you need to make to your integration.
Endpoint URL Update
The API base URL and version routing have completely changed.
Legacy URL: POST https://api-v3-sandbox.vertofx.com/orders/v2.1/fx
New URL: POST https://api-exchange-now-beta.vertofx.com/fx/payments
LINK TO NEW URL TO BE ADDED
Request Payload Restructuring (Field-by-Field Mapping)
Payload properties have been renamed, restructured, or replaced.
Legacy Field | New Field | Change Type | Description / Migration Action |
|---|---|---|---|
|
| Breaking | Snake case changed to camelCase. Still required and bound to the Get Rate validity window |
side (BUY / SELL) |
| Breaking | (Core Logic ) The side enum is removed. Instead of specifying a BUY/SELL side direction against an implicit currency pair, you must explicitly pass the source/target objects and designate which currency the amount applies to using amountCurrency. |
|
| Modified Logic | While the field name is the same, its behavior now depends on amountCurrency rather than the legacy side orientation. |
|
| Breaking | Renamed. Max length and validation rules should be verified against the new schema. |
-- |
| Breaking | New Required Field. |
-- |
| Breaking | New Required Field. |
Handling the Removal of side (BUY / SELL)
In the new API, you no longer specify side. Instead, you define either sourceAmount or targetAmount inside the sources array based on what you know:
- If your Legacy Side was
SELL: (You are selling a fixed amount of the source currency).MapamounttosourceAmount. - If your Legacy Side was
BUY: (You want to buy a fixed amount of the destination currency).MapamounttotargetAmount.
🔄 Payload Comparison (Flow 1: Wallet-to-Wallet) Below is an explicit comparison of how to change your request payload to perform an intra-wallet FX trade.
Legacy Request Example:
{
"vfx_token": "tok_1234567890",
"side": "SELL",
"amount": 5000,
"clientReference": "InternalRef123"
}{
"paymentMode": "immediate",
"paymentType": "convertWithinWallets",
"vfxToken": "tok_1234567890",
"currencyFrom": {
"currencyName": "GBP"
},
"currencyTo": {
"currencyName": "NGN"
},
"amountCurrency": "GBP",
"amount": 5000,
"targetWalletId": 98765,
"customPaymentReference": "InternalRef123"
}Response Body Changes
The response payload is now flatter and provides significantly more metadata regarding pricing, settlement times, and state tracking.
- Order Object Nesting: The response no longer wraps the transaction details inside an order block. All core fields are now at the root level of the JSON response.
- Currency Fields: Previously returned only if successful,
currencyFromandcurrencyToare explicitly returned at the root. - Status Tracking: The fields
transactionStateandstatushave been consolidated and expanded. Look at the state field (e.g., initiated, confirmed, inwardSettlementDone) to track progress.
| Legacy Response Field | New Response Field | Notes |
|---|---|---|
order.id | id | Now returned as a String at the root level. |
order.reference | reference | Remains a string reference code |
order.amountFrom | amountFrom | Root level float |
order.amountTo | amountTo | Root level float |
order.rate | rate | Returned as a numeric type instead of a string |
order.transactionState | state | Updated enum values (initiated, confirmed, etc.) |
C. Beneficiary Creation
The core process remains the same i.e you are still registering a recipient's payout details. However, the new Create Beneficiary API endpoint introduces a cleaner structure, introduces stablecoin capabilities, and enforces explicit payment modes up front.
Key Endpoint Changes in Beneficiary creation:
- Old URL: POST https://api-v3-sandbox.vertofx.com/profile/v2.1/beneficiaries
- New URL: POST https://api-beneficiary-beta.vertofx.com/recipients
⚡ Critical Breaking Changes
Before writing your new code, please make sure you accommodate these four foundational changes:
- The
paymentModeField is Now Mandatory: instead of using a generic endpoint, you must now explicitly state how the money is moving. For Global Clearing (USD, GBP, EUR, etc.): UseLOCALorINTERNATIONAL. For Last Mile Mobile Money (KES, TZS, XAF): UseMOBILE_MONEY. - Currency Strings vs. Currency IDs
- Old: You passed a 3-letter code text string (e.g.,
"currency": "AED"). - New: You must now pass a numeric ID mapping to that currency (e.g.,
"currencyId": 4).
- Old: You passed a 3-letter code text string (e.g.,
- Structural Field Renaming Several parameter names have changed to align with universal payment standards:
beneficiaryEntityTypeis nowbeneficiaryType.nationalId(where you used to put Swift/Sort/Routing codes) is nowbankCode.
- Structured Addresses
Instead of passing flat text strings for countries and addresses, both the bank's address and the beneficiary's physical address must now be passed as dedicated objects (bankAddress and beneficiaryAddress).
Side by Side Request Comparison using examples
{
"beneficiaryEntityType": "individual",
"currency": "AED",
"isMobileMoney": false,
"nationalId": "01HW0JA",
"accountNumber": "1-021-2",
"beneficiaryCountryCode": "ke",
"beneficiaryEmail": "[email protected]",
"beneficiaryFirstName": "Chris",
"beneficiaryLastName": "Jack",
"country": "India"
}{
"paymentMode": "INTERNATIONAL",
"beneficiaryType": "individual",
"accountNumber": "98846622175",
"bankCode": "PUNB0644100",
"bankName": "Bank of India",
"firstName": "Chris",
"lastName": "Jack",
"currencyId": 4,
"partyType": "First_Party",
"bankAddress": {
"countryCode": "IN",
"stateOrProvience": "Delhi",
"city": "Delhi",
"postCode": "120121",
"address": "Downtown street"
},
"beneficiaryAddress": {
"countryCode": "KE",
"stateOrProvience": "Nairobi",
"city": "Nairobi",
"postCode": "1021212",
"addressLine1": "Downtown Road",
"email": "[email protected]"
}
}Phase 3: Get Beneficiaries (Breaking Changes) 🚨
This change affects scenarios where you need to fetch all beneficiaries of your own or your client's sub account.
Domain & Path Changes
The domain and path have changed to separate beneficiary management into its own optimized microservice.
Old Endpoint: https://api-v3-sandbox.vertofx.com/profile/v2.1/beneficiaries
New Endpoint: https://api-beneficiary-beta.vertofx.com/recipients
Pagination Parameters Renamed
If your automated scripts use pagination to look through large lists of bank accounts, the parameter keys have been renamed.
Old Parameters: You used customPageSize (how many items to show) and page (which page to view).
New Parameters: You must now use limit (max records to return) and skip (number of items to skip over).
Data Response Restructuring
The structure of the data your system receives back has changed format.
Old Response: Returned a flat list of items wrapped directly in a generic layout.
New Response:
Returns a clean, structured JSON object containing a totalCount integer and an items array.
Additionally, specific details like bank addresses are now cleaner, nested objects.
Examples:
curl --request GET \
--url 'https://api-v3-sandbox.vertofx.com/profile/v2.1/beneficiaries?customPageSize=10&page=1' \
--header 'accept: application/json'curl --request GET \
--url 'https://api-beneficiary-beta.vertofx.com/recipients?limit=10&skip=10' \
--header 'accept: application/json'[
{
"id": 284,
"accountNumber": "2000293918130",
"bankName": "PUNJAB NATIONAL BANK",
"beneficiaryAddress": "123 Main St, Floor 1..."
}
]{
"totalCount": 1,
"items": [
{
"id": 284,
"accountNumber": "2000293918130",
"bankName": "PUNJAB NATIONAL BANK",
"bankAddress": {
"address": "123 Main St, Floor 1..."
}
}
]
}Phase 3: Make Payout (Breaking Changes) 🚨
This action is used to withdraw or move funds out of a wallet.
Status: 🔴 CHANGED (Major Breaking Change)
What's New: The old method was a one-size-fits-all form. The new system is much smarter—it requires you to select a specific paymentType first, which then unlocks more powerful options like scheduling future payments or splitting a bill.
Domain & Path Changes
The domain and path have changed to separate beneficiary management into its own optimized microservice.
Old Endpoint: https://api-v3-sandbox.vertofx.com/profile/v2.2/request
New Endpoint: https://api-payment-preview.vertofx.dev/payments/create
curl --request POST \
--url https://api-v3-sandbox.vertofx.com/profile/v2.1/request \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'Authorization: Bearer <LEGACY_TOKEN>' \
--data '
{
"walletId": 3,
"amount": 100,
"beneficiaryId": 284,
"purposeId": 3,
"clientReference": "Payment-100"
}
'curl --request POST \
--url https://api-payment-sandbox.vertofx.com/payments/create \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"paymentType": "WALLET_PAYOUT",
"sourceWalletId": "12345",
"sourceAmount": 1000.5,
"targetAccountId": "67890",
"purposeId": "1",
"customPaymentReference": "Invoice payment #12345"
}
'What Has Changed in the Fields? To help your team adapt quickly, here is how the old fields map to the new ones:
- Amount: renamed to
sourceAmount(the exact amount leaving the wallet). - Wallet ID: renamed to
sourceWalletId. - Beneficiary ID: renamed to
targetAccountId(the account receiving the money). - Client Reference: renamed to
customPaymentReference.
New Features You Can Now Use
paymentType(Required): You must now tell the system what kind of payment this is (e.g., WALLET_PAYOUT, ).scheduledDate&recurFrequency: You can now set up a payment to go out automatically next week, next month, or on a specific future date.isSplitPayment: A simple true/false switch if you want to divide a single payment across multiple destinations.senderobject: Extra field to clearly log exactly who is initiating the transfer i.e underlying client. For more info, read this guide on How to Structure Sender objects
