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:
┌──────────────┐     ┌──────────────────┐     ┌──────────────┐     ┌─────────┐
│  Frontend UI │ ──→ │  Public Backend   │ ──→ │ Platform API │ ──→ │ Banking │
│  (React/Next)│     │  API (app layer,  │     │ (risk, rules,│     │Provider │
│              │     │  light logic,     │     │  routing,    │     │  (API)  │
│              │     │  session auth)    │     │  ledgering)  │     │         │
└──────────────┘     └──────────────────┘     └──────────────┘     └─────────┘

                      Provider webhooks ──────→│
                      (payment status,         │
                       KYB, inbound)           │
The Platform API is the internal orchestration layer. It owns risk assessment, compliance rules, provider routing, and ledger operations. The public backend API is a thin translation layer — it does not duplicate business logic. The Platform API is internal today but is designed to become the public BaaS API for external partners.
“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.
ResourceDescriptionID Prefix
OnboardingAn orchestrated onboarding flow that creates entity + customer in a single trackable resourceonb_
CustomerA program-scoped wrapper that binds an entity to a specific OpenFX program with KYB statuscus_
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.
Key endpoints:
  • POST /onboardings — Create an orchestrated onboarding (creates entity + customer)
  • GET /onboardings — List onboardings
  • GET /onboardings/{onboardingId} — Get onboarding status
  • GET /customers — List customers
  • GET /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).
ResourceDescriptionID Prefix
FX QuoteA locked exchange rate valid for 60 secondsqte_
ConversionAn executed currency conversion on a customer’s accountcnv_
Key endpoints:
  • GET /fx/asset-pairs — Discover available asset pairs with min/max amounts
  • GET /fx/rates/indicative — Get a non-binding indicative rate
  • POST /fx/quotes — Lock a rate for 60 seconds
  • POST /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.
ResourceDescriptionID Prefix
AccountA fiat or crypto account belonging to a customeracc_
Account NumberA virtual account number for receiving fiat deposits (ACH, Fedwire, IBAN, Sort Code)an_
Blockchain AddressA deposit address for receiving cryptoba_
TransferAn internal movement of funds between accounts owned by the same customertrf_
TransactionA read-only ledger entry for any balance-affecting eventtxn_
Key endpoints:
  • POST /accounts — Create a fiat (demand_deposit, virtual) or crypto (wallet) account
  • GET /accounts/{accountId}/balances — Check available, pending, held, and total balances
  • POST /accounts/{accountId}/account-numbers — Create a virtual account number for deposits
  • POST /accounts/{accountId}/blockchain-addresses — Create a crypto deposit address
  • POST /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.
ResourceDescriptionID Prefix
CounterpartyAn external party you send money to or receive money fromcpt_
Payment MethodRail-specific delivery details for a counterparty (bank account, crypto address, etc.)pm_
PaymentAn outbound payment across any supported railpmt_
CollectionAn initiated pull payment (e.g., ACH debit)col_
Key endpoints:
  • POST /counterparties — Register an external party
  • POST /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

1

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.
2

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.
3

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.
4

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.
5

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.
6

Track the outcome

Monitor the payment status via webhooks (payment.processing, payment.completed) or poll GET /payments/{paymentId}.
Why this is powerful: The customer’s integration sends one API call to create the payment. OpenFX handles the FX conversion from USD to GBP and the delivery via SWIFT.

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:
  1. Convert USD to GBP via the FX Engine (Layer 1)
  2. Deliver GBP via UK Faster Payments from a local UK account (Layer 3)
The result: instant delivery, under 1 GBP in fees — instead of days and $25-45 for SWIFT. This local rail advantage is a key competitive differentiator and is only possible because OpenFX combines FX conversion and multi-rail payment origination in a single platform.

Entity-Customer separation

One of the most important architectural decisions is the separation of entities and customers.
ConceptEntityCustomer
What it holdsIdentity data: legal name, address, tax ID, documents, relationshipsProgram context: KYB status, capabilities, source of funds
CardinalityOne entity can map to many customersEach customer links to exactly one entity
LifecycleManaged independently of programsTied to a specific OpenFX program
ID prefixent_cus_
Never conflate entity fields with customer fields. Entities hold identity information. Customers hold program-scoped enrollment and compliance status.
Why this matters:
  • 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 typetype valueCurrency fieldSub-resources
Fiat bank accountdemand_depositcurrency (ISO 4217)Account numbers (/account-numbers)
Virtual sub-accountvirtualcurrency (ISO 4217)Account numbers (/account-numbers)
Crypto accountwalletasset + chainBlockchain addresses (/blockchain-addresses)
Fiat accounts receive deposits through virtual account numbers (ACH routing + account number, IBAN, UK sort code). Crypto accounts receive deposits through blockchain addresses on supported chains (Ethereum, Solana, Tron, Base, Polygon, Ink, Sui).
Use GET /accounts/{accountId}/balances to check balances regardless of account type. The response includes available, pending, held, and total balance entries.

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.
  OpenFX-controlled
  ─────────────────────────────────
  Frontend UI
  Public Backend API
  Platform API (this API)
  ─── Swap Zone ──────────────────
  Provider adapters
  Banking Provider A (current)
  Banking Provider B (future)
  OpenFX MTL (future own rails)
  ─────────────────────────────────
The provider abstraction is not theoretical — it is the core architectural bet. A customer’s accounts might be at one provider today and another tomorrow. The consumer sees one unified API surface.

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}).
RailEndpointCoverageSpeedDescription
ACH/payments/achUnited States1-2 business days (standard), same-dayUS domestic bank transfers via the ACH network
Fedwire/payments/fedwireUnited StatesSame dayUS domestic wire transfers via Fedwire
SWIFT/payments/swift180+ countries1-5 business daysInternational wire transfers
Crypto/payments/cryptoGlobal (where permitted)MinutesUSDC, USDT, EURC, PYUSD on 7 chains
OPEN/payments/openOpenFX platformInstantFee-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:
  1. Unified endpoint (POST /payments) — Specify the rail field in the request body. Works for all rails. Best for integrations that support multiple rails and want a single code path.
  2. 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.
Both approaches create the same Payment resource and return the same response schema. The choice is purely a developer experience preference.

What is next