Convert Currency
Execute a currency conversion. You can optionally provide a quoteId to use a previously locked-in rate.
POST /v1/conversions
Headers
| Header | Required | Description |
|---|---|---|
X-Api-Key |
Yes | Your API key |
X-Idempotency-Key |
Yes | Unique key to prevent duplicate conversions |
Content-Type |
Yes | application/json |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | Conditional | Amount in source currency (see minimum amounts below). Required only when quoteId is omitted. When a quoteId is supplied it is ignored — the quote's locked source amount is used instead. |
sourceCurrency |
string | Yes | Source currency: NGN, USD, EUR, GBP, BTC, USDT, USDC |
targetCurrency |
string | Yes | Target currency: NGN, USD, EUR, GBP, BTC, USDT, USDC |
quoteId |
string | No | UUID of an existing quote to execute with its locked-in rate. When provided, the quote is authoritative for both the source amount debited and the target amount credited; any amount in the request body is ignored. sourceCurrency/targetCurrency must still match the quote's currencies. |
accountId |
number | No | ID of the virtual account to debit when sourceCurrency is NGN. Ignored for non-NGN sources. If omitted on an NGN-source conversion, the first available NGN account is used. |
Quote overrides amount: If you pass a
quoteId, the conversion executes against the quote's lockedsourceAmountandtargetAmount. The request bodyamountis neither required nor used, so it cannot diverge from the quoted rate. Balance is checked against the quote's source amount.
NGN bank account requirement: Any conversion involving NGN (as either the source or the target currency) requires the account to have an existing NGN virtual account. Create one via the virtual account endpoints before calling this endpoint with
NGNon either side.
Minimum Amounts
| Source Currency | Minimum Amount |
|---|---|
NGN |
150,000 |
BTC |
0.00033 |
USD, EUR, GBP, USDT, USDC |
15 |
Note: Minimum amounts are enforced on the request
amountonly. When aquoteIdis supplied the amount comes from the quote, which already applied these limits at creation time.
Note:
sourceCurrencyandtargetCurrencymust be different.
Example Request
Without a quote
curl -X POST "https://api.esca.finance/v1/conversions" \
-H "X-Api-Key: your_api_key_here" \
-H "X-Idempotency-Key: conv-550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"sourceCurrency": "NGN",
"targetCurrency": "USD"
}'
With a quote
amount is omitted — the quote's locked source amount is used. Any amount sent alongside a quoteId is ignored.
curl -X POST "https://api.esca.finance/v1/conversions" \
-H "X-Api-Key: your_api_key_here" \
-H "X-Idempotency-Key: conv-550e8400-e29b-41d4-a716-446655440001" \
-H "Content-Type: application/json" \
-d '{
"sourceCurrency": "NGN",
"targetCurrency": "USD",
"quoteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}'
With a specific account
curl -X POST "https://api.esca.finance/v1/conversions" \
-H "X-Api-Key: your_api_key_here" \
-H "X-Idempotency-Key: conv-550e8400-e29b-41d4-a716-446655440002" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"sourceCurrency": "NGN",
"targetCurrency": "USD",
"accountId": 12345
}'
Example Response
{
"status": true,
"message": "Conversion Successful",
"data": {
"conversionId": "f7a8b9c0-d1e2-3456-abcd-ef7890123456",
"sourceCurrency": "NGN",
"targetCurrency": "USD",
"sourceAmount": 500000,
"targetAmount": 312.50,
"appliedRate": 1600,
"status": "COMPLETED",
"quoteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
conversionId |
string | UUID of the conversion. Use this to check the conversion status. |
sourceCurrency |
string | Source currency |
targetCurrency |
string | Target currency |
sourceAmount |
number | Amount debited in source currency |
targetAmount |
number | Amount credited in target currency |
appliedRate |
number | Exchange rate applied |
status |
string | PROCESSING, COMPLETED, or FAILED |
quoteId |
string | Quote UUID (only present if a quote was used) |
Error Responses
Same currency
{
"statusCode": 400,
"error": "Bad Request",
"message": "Source and target currencies must be different."
}
No NGN account
{
"statusCode": 400,
"error": "Bad Request",
"message": "No NGN virtual account found. Please create one before converting."
}
Account not found
{
"statusCode": 400,
"error": "Bad Request",
"message": "The specified NGN virtual account was not found."
}
Quote currency mismatch
{
"statusCode": 400,
"error": "Bad Request",
"message": "Quote currencies (NGN -> EUR) do not match request (NGN -> USD)."
}
Missing amount
Returned when neither amount nor quoteId is supplied (a value must come from one of them):
{
"statusCode": 400,
"error": "Bad Request",
"message": ["amount must be a number conforming to the specified constraints"]
}
Insufficient balance
The exact message depends on which constraint failed. For NGN-source conversions (debited from the virtual account):
{
"statusCode": 400,
"error": "Bad Request",
"message": "Insufficient Bank Balance."
}
For wallet-source conversions:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Insufficient balance"
}
When part of the balance is available now and the rest is pending settlement:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Insufficient balance. You can swap up to 500.00 USDT at this time."
}
When the requested amount is fully covered by the ledger but a pending transaction is still settling:
{
"statusCode": 400,
"error": "Bad Request",
"message": "You can swap up to 500.00 USDT now, or 1000.00 USDT once your pending transaction settles."
}
Duplicate request
{
"statusCode": 409,
"message": "Duplicate request"
}
Webhooks
When a conversion is executed, the following webhook events may be dispatched:
| Event | When |
|---|---|
exchange.processing |
Conversion is being processed asynchronously |
exchange.completed |
Conversion completed successfully |
exchange.failed |
Conversion failed |
See Webhook Event Payloads for payload details.