Overview
The OpenFX Platform API is organized into four layers that build on each other. Each layer consumes the services of the layers below it, creating a clean dependency chain from identity at the bottom to money movement at the top. This architecture reflects how real financial operations work: you must establish identity before you can hold money, you must hold money before you can convert it, and you must have funds (potentially converted) before you can pay someone.End-to-end request flow
In production, every API request flows through a four-layer stack:“Decouple everything, block nothing.” The UI is built before the provider is known. The
API spec is written before the rails are live. The ledger is architected before the bank
accounts exist. When a provider is ready, it plugs in underneath.
The four-layer model
Layer 0: Identity and Entities
The foundation of every integration. Before any money can be held or moved, OpenFX must know who is transacting.| Resource | Description | ID Prefix |
|---|---|---|
| Onboarding | An orchestrated onboarding flow that creates entity + customer in a single trackable resource | onb_ |
| Customer | A program-scoped wrapper that binds an entity to a specific OpenFX program with KYB status | cus_ |
In the MVP API, orchestrated onboarding (
POST /onboardings) is the supported path for
creating entities and customers. The onboarding endpoint creates both resources in a single
call and optionally submits the customer for KYB review.POST /onboardings— Create an orchestrated onboarding (creates entity + customer)GET /onboardings— List onboardingsGET /onboardings/{onboardingId}— Get onboarding statusGET /customers— List customersGET /customers/{customerId}— Get customer details and KYB status
Layer 1: FX Engine
Currency conversion services. The FX engine powers both standalone conversions (customer wants to convert USD to EUR) and cross-currency payments (payment originates in one currency and delivers in another).| Resource | Description | ID Prefix |
|---|---|---|
| FX Quote | A locked exchange rate valid for 60 seconds | qte_ |
| Conversion | An executed currency conversion on a customer’s account | cnv_ |
GET /fx/asset-pairs— Discover available asset pairs with min/max amountsGET /fx/rates/indicative— Get a non-binding indicative ratePOST /fx/quotes— Lock a rate for 60 secondsPOST /fx/conversions— Execute a conversion (debit one currency, credit another)
FX field naming uses
sellCurrency / buyCurrency and sellAmount / buyAmount
(not source/target). This matches industry conventions from CurrencyCloud and Airwallex.Layer 2: Banking
Accounts, balances, and the movement of money within the platform. The unified account model covers both fiat bank accounts and crypto accounts under a single resource.| Resource | Description | ID Prefix |
|---|---|---|
| Account | A fiat or crypto account belonging to a customer | acc_ |
| Account Number | A virtual account number for receiving fiat deposits (ACH, Fedwire, IBAN, Sort Code) | an_ |
| Blockchain Address | A deposit address for receiving crypto | ba_ |
| Transfer | An internal movement of funds between accounts owned by the same customer | trf_ |
| Transaction | A read-only ledger entry for any balance-affecting event | txn_ |
POST /accounts— Create a fiat (demand_deposit,virtual) or crypto (wallet) accountGET /accounts/{accountId}/balances— Check available, pending, held, and total balancesPOST /accounts/{accountId}/account-numbers— Create a virtual account number for depositsPOST /accounts/{accountId}/blockchain-addresses— Create a crypto deposit addressPOST /transfers— Move funds between your own accounts
Layer 3: Payments
The top layer. Moving money to external counterparties across payment rails and pulling funds via collections.| Resource | Description | ID Prefix |
|---|---|---|
| Counterparty | An external party you send money to or receive money from | cpt_ |
| Payment Method | Rail-specific delivery details for a counterparty (bank account, crypto address, etc.) | pm_ |
| Payment | An outbound payment across any supported rail | pmt_ |
| Collection | An initiated pull payment (e.g., ACH debit) | col_ |
POST /counterparties— Register an external partyPOST /counterparties/{counterpartyId}/payment-methods— Add delivery details (bank account, crypto address)POST /payments— Create a payment (unified endpoint, all rails)POST /payments/ach— Create an ACH payment (rail-specific, simpler schema)POST /collections— Initiate an ACH debit pull
How a cross-border payment flows
Here is the complete flow of a USD-to-GBP cross-border payment delivered via SWIFT. This is the canonical example that touches all four layers.Step-by-step breakdown
Establish identity (Layer 0)
Create an orchestrated onboarding with
autoSubmit: true. This creates a business
entity and customer in a single call and submits for KYB review. Wait for the
customer.kyb_status_changed webhook with approved status. In sandbox, KYB is
auto-approved.Fund the source account (Layer 2)
Create a USD
demand_deposit account for the customer. The customer deposits USD
via wire transfer or ACH to the account’s virtual account number. The balance is
now available for use.Lock the exchange rate (Layer 1)
Create an FX quote specifying
sellCurrency: USD and buyCurrency: GBP with
the desired amount. The rate is locked for 60 seconds. The quote ID will be
referenced in the payment.Register the counterparty (Layer 3)
Create a counterparty representing the payment recipient. Add a SWIFT
payment method with the counterparty’s bank details and SWIFT BIC.
Create the payment (Layer 3)
Create a payment specifying the source account, payment method, FX quote, and
rail: swift. The payment debits USD from the source account, converts to GBP
using the locked rate, and originates a SWIFT transfer to the
counterparty’s bank account.Local rail advantage
The cross-border payment example above uses SWIFT for delivery. But OpenFX enables a more powerful pattern: convert and deliver via local rails. Instead of sending a USD-to-GBP payment via SWIFT ($25-45, 2-5 business days), OpenFX can:- Convert USD to GBP via the FX Engine (Layer 1)
- Deliver GBP via UK Faster Payments from a local UK account (Layer 3)
Entity-Customer separation
One of the most important architectural decisions is the separation of entities and customers.| Concept | Entity | Customer |
|---|---|---|
| What it holds | Identity data: legal name, address, tax ID, documents, relationships | Program context: KYB status, capabilities, source of funds |
| Cardinality | One entity can map to many customers | Each customer links to exactly one entity |
| Lifecycle | Managed independently of programs | Tied to a specific OpenFX program |
| ID prefix | ent_ | cus_ |
- A single business entity can participate in multiple OpenFX programs, each with its own customer record, KYB status, and capability set.
- Identity data (documents, relationships, legal name) is managed once on the entity and shared across all customer enrollments.
In the MVP API, entities are created implicitly through orchestrated onboarding.
You interact with entities indirectly — the onboarding endpoint creates them for you
and returns the
entityId for reference.Unified account model
The API uses a single/accounts resource for both fiat and crypto accounts. There are no
separate wallet endpoints.
| Account type | type value | Currency field | Sub-resources |
|---|---|---|---|
| Fiat bank account | demand_deposit | currency (ISO 4217) | Account numbers (/account-numbers) |
| Virtual sub-account | virtual | currency (ISO 4217) | Account numbers (/account-numbers) |
| Crypto account | wallet | asset + chain | Blockchain addresses (/blockchain-addresses) |
Provider abstraction
OpenFX abstracts over multiple underlying banking and crypto infrastructure providers. The provider abstraction layer routes operations to the appropriate backend without exposing provider-specific details to API consumers. This means:- No provider lock-in. OpenFX can route through different providers for different rails, currencies, or corridors.
- Consistent interface. Whether a SWIFT payment routes through Provider A or Provider B, the API request and response schemas are identical.
- Transparent failover. If a provider experiences downtime, OpenFX can reroute to an alternative provider without any client-side changes.
- No internal details leaked. Settlement mechanics, liquidity sources, and routing decisions are internal implementation details that never appear in API responses.
The Swap Zone
Everything above the provider integration layer is under OpenFX’s control and can be built independently. Everything below it is partner-shaped and will change over time. The API boundary between them is the critical design decision — get this right and provider changes are integration work, not product rebuilds.Payment rails
The MVP API supports five payment rails, each with both a unified endpoint (POST /payments)
and a rail-specific endpoint (POST /payments/{rail}).
| Rail | Endpoint | Coverage | Speed | Description |
|---|---|---|---|---|
| ACH | /payments/ach | United States | 1-2 business days (standard), same-day | US domestic bank transfers via the ACH network |
| Fedwire | /payments/fedwire | United States | Same day | US domestic wire transfers via Fedwire |
| SWIFT | /payments/swift | 180+ countries | 1-5 business days | International wire transfers |
| Crypto | /payments/crypto | Global (where permitted) | Minutes | USDC, USDT, EURC, PYUSD on 7 chains |
| OPEN | /payments/open | OpenFX platform | Instant | Fee-free internal platform payments between OpenFX entities |
Use
GET /rails and GET /rails/{rail} to programmatically discover available rails,
their capabilities, supported currencies, and supported countries. Use
POST /rails/{rail}/validate for pre-flight payment validation before submission.Unified vs. rail-specific endpoints
There are two ways to create a payment:-
Unified endpoint (
POST /payments) — Specify therailfield in the request body. Works for all rails. Best for integrations that support multiple rails and want a single code path. -
Rail-specific endpoints (
POST /payments/ach,POST /payments/swift, etc.) — Simpler, rail-native request schemas without discriminator polymorphism. Best when you know which rail you are using and want the simplest possible request body.
Payment resource and return the same response schema.
The choice is purely a developer experience preference.