AI agents: this site is available as Markdown at /llms.txt. Each page has a Markdown version at its URL with a .md suffix, and the full API is described in /api/v3/openapi.json.

FX

Query rates and execute FX trades.

Last updated 13 September 2026

Introduction

Flutterwave's FX service allows you to convert funds between supported currencies by requesting an FX quote and executing a trade at the approved rate. This provides easy access to liquidity for your payment operations.

The FX service is not configured on accounts by default. Request this feature to be enabled on your account before using it.

Converting foreign currency with this service involves four key steps:

  1. Request a quote(RFQ) for your desired currency exchange.
  2. Review the pricing received via webhooks or by querying the quote status endpoint.
  3. Initiate the trade using the quote_id to execute the quoted exchange.
  4. Confirm the trade execution via webhook notifications.

FX quotes and trading operations are available on weekdays only (Monday through Friday).

Requesting an FX Quote (RFQ).

A quote defines the exchange rate and details between a specific currency pair. Every trade requires a fresh quote; once a quote is used to execute a trade, it is locked and cannot be reused for subsequent transactions.

Supported currency pairs

The FX service currently supports the following currency pairs:

Base CurrencyTarget Currency
NGNUSD
GHSUSD
USDNGN

To get a quote, send a request to the RFQ endpoint with the following parameters:

  • base_currency: The currency you are selling, e.g. NGN, USD, GHS.
  • target_currency: The currency you want to receive, e.g. NGN, USD.
  • quantity: The amount of the base_currency you want to transact.
    • Example: If base_currency is NGN and target_currency is USD, a quantity of 2,000,000 means you are spending 2,000,000 NGN to buy USD.
    • Constraint: The minimum trade amount is $1,000 USD (or its equivalent in the base currency).
  • reference: A unique identifier for the transaction, used for reconciliation. If omitted, the system will automatically generate one.

RFQ Request

Javascript

1
2
3
4
5
6
7
8
9
curl --location 'https://api.flutterwave.com/v3/rfq' \
--header 'Authorization: Bearer {{YOUR_SECRET_KEY}}' \
--header 'Content-Type: application/json' \
--data '{
  "base_currency": "NGN",
  "target_currency": "USD",
  "quantity": 15000000,
  "reference": "{{YOUR_UNIQUE_REFERENCE}}"
}'

Check Quote Status

When a quote is first generated, its initial status isNEW. To fetch the latest status of a quote, send a GET request to the check quote status endpoint using the quote id (data.id) returned from the RFQ response.

Javascript

1
2
curl --location 'https://api.flutterwave.com/v3/rfq/{{YOUR_QUOTE_ID}}' \
--header 'Authorization: Bearer {{YOUR_SECRET_KEY}}'

Quote Statuses

A quote can transition into one of the following statuses:

  • PROCESSING – The quote is being processed.
  • READY – The quote is active and available to execute a trade.
  • EXPIRED – The quote's 5-minute validity window has passed; it can no longer be used.
  • FAILED – The quote generation failed. This typically occurs due to unsupported currency pairs or failing to meet the minimum trade amount ($1,000 USD or its equivalent).

The quote Object

If the quote's status is READY, the response includes a quote object with the following details:

  • best_available_rate: The real-time exchange rate applied to the trade. This rate converts the approved_quantity into the total_value.
  • approved_quantity: The actual amount in the base_currency approved for the trade. Note: This may differ from your requested quantity based on liquidity or account limits.
  • total_value: The final converted amount you will receive in the target_currency. This is calculated as: approved_quantity × best_available_rate.
  • expiry: The exact date and time the quote expires.

If you are satisfied with the quote details, you can proceed to initiate the trade.

Response (200 OK)

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
   "status":"success",
   "message":"Quote fetched successfully.",
   "data":{
      "id":"c085aab5-5938-46a7-90a9-22420c2a8d6e",
      "reference":"c9f6539747ef11f1",
      "quantity":1617759,
      "instrument":"NGN/USD",
      "quote":{
         "best_available_rate":"0.000659",
         "approved_quantity":1517759,
         "total_value":1000.203181,
         "expiry":"2026-02-06T10:12:02.990Z"
      },
      "status":"READY",
      "created_at":"2026-02-06T10:03:22.000Z",
      "complete_message":"Your RFQ is ready"
   }
}

Quotes expire after 5 minutes. Always check that your quote is still active before initiating a trade to prevent your request from failing.

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "status": "success",
    "message": "Quote fetched successfully.",
    "data": {
        "id": "c085aab5-5938-46a7-90a9-22420c2a8d6e",
        "reference": "c9f6539747ef11f1",
        "quantity": 1617759,
        "instrument": "NGN/USD",
        "quote": {
            "best_available_rate": "0.000659",
            "approved_quantity": 1517759,
            "total_value": 1000.203181,
            "expiry": "2026-02-06T10:12:02.990Z"
        },
        "status": "EXPIRED",
        "created_at": "2026-02-06T10:03:22.000Z",
        "complete_message": "Your RFQ is expired"
    }
}

Initiating a Trade

Once you have a valid quote, you can proceed to execute your trade. To initiate the transaction, send a POST request to the create trade endpoint with the following parameters:

  • quote_id: The ID of the quote you want to execute.
  • narration: A short note for your own records describing the purpose of the transaction.

Javascript

1
2
3
4
5
6
7
curl --location 'https://api.flutterwave.com/v3/trade' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{YOUR_SECRET_KEY}}' \
--data '{
  "quote_id": "570e21b4-7cf2-4048-bd1b-284bd45762c6",
  "narration": "sample exchange"
}'

Response fields

A successfully initiated trade response includes the following key details within the data object:

  • quantity: The initial amount passed when requesting a quote.
  • approved_quantity: The actual amount in the base_currency approved for execution.
  • price: The final converted amount you will receive in the target currency.

200 OK

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "status": "success",
    "message": "Trade initiated successfully.",
    "data": {
        "id": "30b85c03-9124-4418-b30b-0495ffbdc633",
        "quantity": 1617759,
        "approved_quantity": 1517759,
        "price": 1000.203181,
        "instrument": "NGN/USD",
        "status": "NEW",
        "recipient": {
            "id": "200004751",
            "name": "MythCo."
        },
        "narration": "sample exchange",
        "reference": "TRD-86ade016977958e2",
        "callback_url": "https://www.helloajadi.com/api/flw/dev",
        "created_at": "2026-02-06T10:08:54.000Z"
    }
}

Fetching Trade Status

All newly initiated trades start with a status of NEW. To fetch the latest status of a trade, send a GET request to the check trade status endpoint using the trade_id.

Javascript

1
2
curl --location 'https://api.flutterwave.com/v3/trade/96086092-fca3-4f9b-9ce1-90633a9d300a' \
--header 'Authorization: Bearer {{secret_key}}'

During its lifecycle, a trade can transition into one of the following statuses:

  • PENDING – The trade is still being executed.

  • FAILED – The trade could not be completed.

  • SETTLED – The trade completed successfully, and funds have been exchanged.

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "status": "success",
    "message": "Trade item fetched.",
    "data": {
        "id": "30b85c03-9124-4418-b30b-0495ffbdc633",
        "quantity": 1617759,
        "price": 1000.203181,
        "instrument": "NGN/USD",
        "status": "SETTLED",
        "response_message": "Transfer was Successful",
        "recipient": {
            "id": "200004751",
            "name": "MythCo."
        },
        "narration": "sample exchange",
        "reference": "TRD-86ade016977958e2",
        "callback_url": "https://www.helloajadi.com/api/flw/dev",
        "created_at": "2026-02-06T10:08:54.000Z"
    }
}

Handling Webhooks

When a quote or trade status changes, you will receive webhook notifications. Use these notifications to track the progress of your FX transactions without repeatedly polling the API.

Below are examples of expected webhook structures.

Quote events

Status - Processing

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "event": "quote.completed",
  "event.type": "QUOTE",
  "data": {
    "id": "342cd6e7-9015-48c7-92a6-7a81e70bb6f4",
    "reference": "5bddd594b3025ce9",
    "quantity": 100000,
    "instrument": "GHS/USD",
    "quote": null,
    "status": "PROCESSING",
    "created_at": "2026-02-03T11:35:55.000Z",
    "complete_message": "Your RFQ is being processed"
  }
}

Trade events

Status - Successful

Javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    "event": "transfer.completed",
    "event.type": "Transfer",
    "data": {
        "id": 255169,
        "account_number": "42769239",
        "bank_name": "wallet",
        "bank_code": "wallet",
        "fullname": "uzoma kenkwo",
        "created_at": "2026-02-03T12:03:59.000Z",
        "currency": "USD",
        "debit_currency": "NGN",
        "amount": 629.705485,
        "fee": 0,
        "status": "SUCCESSFUL",
        "reference": "TRD-7fbcf39210578e4d",
        "meta": {
            "quote_id": "526366c8-93e7-7dcc-0dd8-00006d4d54f1",
            "AccountId": 1668495,
            "merchant_id": "300408963"
        },
        "narration": "sample exchange",
        "approver": null,
        "complete_message": "Transfer was successful",
        "requires_approval": 0,
        "is_approved": 1
    }
}

After completing a successful trade, converted funds are instantly settled into your target currency's payout wallet.

Testing your integration

scenario should not be used in production requests for successful RFQ processing.

You can simulate several RFQ outcomes to test different paths in your FX workflow using the scenario field.

Javascript

1
2
3
4
5
6
7
8
9
10
curl --location 'https://api.flutterwave.com/v3/rfq' \
--header 'Authorization: Bearer {{YOUR_SECRET_KEY}}' \
--header 'Content-Type: application/json' \
--data '{
  "base_currency": "NGN",
  "target_currency": "USD",
  "quantity": 1500000,
  "reference": "{{YOUR_UNIQUE_REFERENCE}}",
  "scenario": "ready"
}'

The following scenarios can be mocked in test mode:

  1. ready - Mocks a valid quote ready for trading.
  2. expired - Mocks an expired quote.
  3. invalid_amount - Mocks the invalid amount error in the quote request process.
  4. invalid_currency_pair - Mocks the invalid currency pair in the quote request.
  5. exceeded_limit - Simulates when the limit of an account is reached.


FL.AI

Answers from the Flutterwave docs

Hi, I’m FL.AI

Ask anything about Flutterwave’s APIs, guides and coverage.

FL.AI can make mistakes. Check important details in the docs.

Get API Keys