Draft API (v0.3.0-draft) — The transfers surface is currently in draft. Schemas, statuses,
and lifecycle behavior may change before the stable release.
What Are Internal Transfers?
Internal transfers move funds between accounts on the same platform. They are the simplest way to rebalance funds across your OpenFX accounts — no external payment rail is involved, and settlement is near-instant.
Source Account (acc_) Destination Account (acc_)
│ │
│ ──► Transfer (trf_) ──► │
│ Debit $2,500.00 USD │ Credit $2,500.00 USD
Internal transfers are ideal for:
- Treasury rebalancing — Moving funds from a collections account to an operating account.
- Pre-funding payments — Moving funds to an account before initiating an outbound payment.
- Segregation — Moving client funds to a dedicated virtual account.
- Multi-currency positioning — After an FX conversion, moving the converted funds to the appropriate account.
Same currency only. Internal transfers require that the source and destination accounts hold
the same currency. If you need to move funds between accounts in different currencies, perform
an FX conversion first, then transfer the converted funds.
Transfer Lifecycle
Transfers follow a simple lifecycle. Most transfers complete near-instantly.
| Status | Description |
|---|
pending | Transfer created. Funds are being moved between accounts. |
completed | Transfer settled. Source debited and destination credited. |
failed | Transfer failed (e.g., insufficient funds, account suspended). |
Internal transfers typically complete within seconds. Unlike external payment rails, there are
no settlement delays or business-day dependencies.
Creating an Internal Transfer
curl -X POST https://sandbox.api.openfx.com/v1/transfers \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"sourceAccountId": "acc_01953e1a5f4b7002",
"destinationAccountId": "acc_01953e1a5f4b7003",
"amount": {
"currency": "USD", "value": "250000"
},
"reference": "Internal rebalance"
}'
The response returns the full transfer resource:
{
"id": "trf_01953e1a5f4b7008",
"sourceAccountId": "acc_01953e1a5f4b7002",
"destinationAccountId": "acc_01953e1a5f4b7003",
"amount": {
"currency": "USD", "value": "250000"
},
"status": "completed",
"reference": "Internal rebalance",
"metadata": {},
"createdAt": "2026-02-25T10:00:00Z",
"completedAt": "2026-02-25T10:00:01Z"
}
Request Fields
| Field | Type | Required | Description |
|---|
sourceAccountId | string | Yes | Account to debit (must have sufficient balance) |
destinationAccountId | string | Yes | Account to credit |
amount | Money | Yes | Amount to transfer ({ "currency": "USD", "value": "250000" }) |
reference | string | No | Your reference for the transfer |
metadata | object | No | Custom key-value pairs (max 50 keys) |
Listing Transfers
curl -X GET "https://sandbox.api.openfx.com/v1/transfers?limit=20" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"
Getting a Transfer
curl -X GET https://sandbox.api.openfx.com/v1/transfers/trf_01953e1a5f4b7008 \
-H "Authorization: Bearer $API_KEY" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"
Transfers vs OPEN Payments
Internal transfers and OPEN payments both move funds within the OpenFX platform, but they serve different purposes:
| Internal Transfers | OPEN Payments |
|---|
| Endpoint | POST /transfers | POST /payments/open |
| Direction | Between your own accounts | To another platform customer |
| Counterparty | Not required (own accounts) | Required (another entity) |
| Use case | Treasury rebalancing, pre-funding | Paying another OpenFX customer |
| Fees | None | None |
| Settlement | Near-instant | Instant |
Use transfers to move money between accounts you own. Use OPEN payments to send
money to a different customer on the OpenFX platform.
Error Handling
| HTTP Status | Error Code | Description |
|---|
409 | duplicate_idempotency_key | Same idempotency key used with different request body |
409 | idempotency_key_in_flight | Same idempotency key still processing. Retry after Retry-After header |
422 | insufficient_funds | Source account does not have enough available balance |
422 | invalid_account_combination | Accounts are not compatible (e.g., different currencies, same account) |
Webhook Events
Subscribe to these events to track transfer lifecycle changes:
| Event | Description |
|---|
transfer.created | Transfer has been created and is pending. |
transfer.completed | Transfer settled. Source debited and destination credited. |
transfer.failed | Transfer failed. |
Transaction Ledger Entries
Each transfer creates two transaction entries in the ledger — a debit on the source account and a credit on the destination account. These entries share the same transactionGroupId and are linked via linkedTransactionId.
[
{
"id": "txn_01953e1a5f4b7602",
"accountId": "acc_01953e1a5f4b7002",
"type": "transfer_out",
"direction": "debit",
"value": "250000",
"currency": "USD",
"balance": "7500.00",
"transactionGroupId": "grp_01953e1a5f4b7603",
"referenceType": "transfer",
"referenceId": "trf_01953e1a5f4b7008"
},
{
"id": "txn_01953e1a5f4b7604",
"accountId": "acc_01953e1a5f4b7003",
"type": "transfer_in",
"direction": "credit",
"value": "250000",
"currency": "USD",
"balance": "12500.00",
"transactionGroupId": "grp_01953e1a5f4b7603",
"referenceType": "transfer",
"referenceId": "trf_01953e1a5f4b7008"
}
]
See Transaction Ledger for full details on how transfers are recorded.
API Reference