FX
FX converts one fiat currency to another, such as NGN → USD or GBP → EUR.
If one side of the conversion is a digital currency such as USDC, see Ramps .
FX follows the quote → execution → settlement flow.
Example: pay a supplier in US dollars
A business pays a USD 10,000 invoice using NGN. Think of its vaults as two accounts: one holds NGN, the other USD. Select them with input.vaultId and output.vaultId. Both must belong to the same sub-organization.
The example fixes output.amount at 10,000.00 USD and quotes 15,000,000.00 NGN as the required source amount.
The figures and IDs are illustrative. Use approved accounts and an enabled currency pair and payment rails.
Workflow
- Request available quotes.
- Select and persist a quoteId.
- Execute the selected quote.
- If funding is required, follow depositInstructions and complete the applicable deposit workflow.
- Retrieve the transaction to track its final status.
Get quotes and execute a selected route
Use output.amount to fix the USD recipient amount; omit input.amount to quote the NGN cost.
The source and destination are bank accounts using NIP and ACH respectively. Omit source.chain and destination.chain.
Step 1 — Get quotes
POST /api/external/transactions/v2/quote/
REST request body
{
"intent": {
"input": {
"asset": "NGN",
"vaultId": "vault_ngn_business"
},
"output": {
"asset": "USD",
"amount": "10000.00",
"vaultId": "vault_usd_settlement"
},
"source": {
"type": "BANK_ACCOUNT",
"id": "bank_account_ngn_business",
"paymentRail": "NIP"
},
"destination": {
"type": "BANK_ACCOUNT",
"id": "bank_account_usd_supplier",
"paymentRail": "ACH"
}
}
}Field | Type | Required | Description |
|---|---|---|---|
intent | TransactionIntentRequest | Yes | Quote criteria for the requested ramp or FX transaction. |
intent.source | TransferPartyData | Yes | Required source endpoint. Put its payment rail on this party. |
intent.destination | TransferPartyData | Yes | Required destination endpoint. Put its payment rail on this party. |
input.asset / output.asset | string | See description | Both asset fields are required. input is spent; output is received. |
input.amount / output.amount | string | See description | For quotes, provide exactly one non-null amount: input.amount or output.amount. Omit the other amount. |
source.chain / destination.chain | string | No | Blockchain identifiers for crypto legs. |
source.paymentRail / destination.paymentRail | string | No | Payment-rail identifiers for fiat or provider legs. |
Response
{
"quotes": [
{
"quoteId": "quote_fx_invoice_1042",
"fees": {
"amount": "22500.00",
"asset": "NGN"
},
"input": {
"asset": "NGN",
"amount": "15000000.00",
"vaultId": "vault_ngn_business"
},
"output": {
"asset": "USD",
"amount": "10000.00",
"vaultId": "vault_usd_settlement"
}
}
]
}JavaScript SDK
const quoteResponse = await apiClient.getQuote({
"intent": {
"input": {
"asset": "NGN",
"vaultId": "vault_ngn_business"
},
"output": {
"asset": "USD",
"amount": "10000.00",
"vaultId": "vault_usd_settlement"
},
"source": {
"type": "BANK_ACCOUNT",
"id": "bank_account_ngn_business",
"paymentRail": "NIP"
},
"destination": {
"type": "BANK_ACCOUNT",
"id": "bank_account_usd_supplier",
"paymentRail": "ACH"
}
}
});
console.log(quoteResponse.quotes);Omitted fields stay omitted. For quote-only execution, the SDK sends intent as null.
Step 2 — Execute the selected quote
POST /api/external/transactions/intent/create/
REST request body
{
"intent": null,
"quoteId": "quote_fx_invoice_1042",
"externalId": "supplier-invoice-1042",
"memo": "Pay USD 10000 supplier invoice from NGN account"
}For quote execution, send the selected quoteId without repeating intent. Do not send category, routeAccounts, or subOrgId to the intent quote/create APIs.
Response
The API returns HTTP 201 with a full Transaction; the SDK returns Promise<Transaction>. This example shows selected fields. Optional fields depend on the payment route and status.
{
"id": "transaction_fx_invoice_1042",
"status": "PENDING",
"externalId": "supplier-invoice-1042",
"memo": "Pay USD 10000 supplier invoice from NGN account",
"quoteResponse": {
"quoteId": "quote_fx_invoice_1042",
"fees": {
"amount": "22500.00",
"asset": "NGN"
},
"input": {
"asset": "NGN",
"amount": "15000000.00",
"vaultId": "vault_ngn_business"
},
"output": {
"asset": "USD",
"amount": "10000.00",
"vaultId": "vault_usd_settlement"
}
},
"depositInstructions": {
"type": "BANK_ACCOUNT",
"paymentRail": "NIP",
"bankDetails": {
"bankName": "Example Settlement Bank",
"beneficiaryName": "Example settlement beneficiary",
"accountNumber": "<ACCOUNT_NUMBER_FROM_RESPONSE>"
},
"asset": "NGN",
"memo": "<PAYMENT_REFERENCE_FROM_RESPONSE>"
},
"intent": {
"input": {
"asset": "NGN",
"vaultId": "vault_ngn_business",
"amount": "15000000.00"
},
"output": {
"asset": "USD",
"amount": "10000.00",
"vaultId": "vault_usd_settlement"
},
"source": {
"type": "BANK_ACCOUNT",
"id": "bank_account_ngn_business",
"paymentRail": "NIP"
},
"destination": {
"type": "BANK_ACCOUNT",
"id": "bank_account_usd_supplier",
"paymentRail": "ACH"
}
}
}JavaScript SDK
const payment = await apiClient.createTransactionFromIntent({
"quoteId": "quote_fx_invoice_1042",
"externalId": "supplier-invoice-1042",
"memo": "Pay USD 10000 supplier invoice from NGN account"
});
console.log(payment.id, payment.status);If execution returns a PENDING transaction, the SDK signs and submits the associated change approval and then retrieves the updated transaction.
Fund the payment if a bank deposit is required
In this example, fund 15,000,000.00 NGN using the exact bank details and reference in depositInstructions.
Only call mark_deposit_done after funding a separate APPROVED settlement deposit. Use the deposit ID transaction_fx_deposit_1042; the parent payment is transaction_fx_invoice_1042.
POST /api/external/transactions/mark_deposit_done/
REST request body
{
"transactionId": "transaction_fx_deposit_1042"
}This endpoint applies only to an APPROVED TRANSFER / DEPOSIT in a quote-driven settlement flow. The response is a deposit excerpt; SUBMITTED does not complete the parent conversion.
Response
{
"id": "transaction_fx_deposit_1042",
"status": "SUBMITTED",
"category": "TRANSFER",
"subCategory": "DEPOSIT",
"asset": "NGN",
"amount": "15000000.00"
}JavaScript SDK
await apiClient.markDepositDone("transaction_fx_deposit_1042");Track the payment to completion
Retrieve the parent payment through GET /api/external/transactions/transaction_fx_invoice_1042/ or the SDK until COMPLETED, FAILED, or DECLINED.
const payment = await apiClient.getTransactionById("transaction_fx_invoice_1042");
console.log(payment.id, payment.status);