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 locked sourceAmount and targetAmount. The request body amount is 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 NGN on 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 amount only. When a quoteId is supplied the amount comes from the quote, which already applied these limits at creation time.

Note: sourceCurrency and targetCurrency must 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.