Skip to content
MultigateAPI
Support Staff

Products

FX conversions

Convert money between two of your accounts in different currencies, at a rate you see and lock before any money moves. Use it to hold USD from NGN, or to fund a payout in a currency you don't hold yet.

In short:

  1. Get a quote: ask for a rate to receive an amount in the target currency. The quote is valid for about 60 seconds.
  2. Lock it: confirm the quote with the two accounts. The source account is debited and the target account credited, usually during the call.
  3. Confirm: check the lock response's status, or wait for the trade_completed webhook.
Term Meaning
Source currency / account What you convert from: the account that is debited.
Target currency / account What you convert to: the account that is credited.
Quote A rate Multigate offers you, with an ID, valid until endTime.
Lock Accepting a quote. This is the step that moves money.
Liquidity provider The bank or partner Multigate buys the currency from.

How it works

sequenceDiagram
    autonumber
    participant You as Your backend
    participant MG as Multigate
    You->>MG: GET /fx-rates/best-rate (source, target, amount to receive)
    MG-->>You: Quote id, buyRate, sellRate, endTime (about 60 seconds)
    You->>MG: POST /fx-rates/lock-rate (quote id, accounts, amount)
    Note over MG: Check balance, debit source account,<br/>book with the liquidity provider,<br/>credit target account
    MG-)You: trade / trade_completed (can arrive before the response)
    MG-->>You: Conversion: status completed (or processing)
    Note over MG,You: processing: trade_completed follows when the credit settles

Two calls, no separate fee: Multigate's margin is built into the rate.

Before you start

  • MG_BASE_URL, MG_API_KEY and MG_CLIENT_ID are set, as in the Quickstart.
  • One active account in each currency, both yours, or both the same customer's. You need their accountId values (not account numbers). Put them in variables:
curl -s "$MG_BASE_URL/client-service/api/v1/accounts?pageSize=50" \
  -H "mp-api-key: $MG_API_KEY" \
  | jq '.data[] | {accountId, currencyIso3Code, status}'
export MG_NGN_ACCOUNT_ID="accountId of your NGN account"
export MG_USD_ACCOUNT_ID="accountId of your USD account"
  • The source account funded with more than the amount that will be debited. Check it with Get an account balance before you lock.
  • The entityId header set to your client ID on every conversion request. Without it, the service uses the business your API key belongs to.
  • Permissions: lock rates (can_lock_rates) to quote and lock; view rates (can_view_rates) or lock rates to read conversions.
  • For a customer: pass onBehalfOf when you get the quote. See Acting for customers.
Known issue: conversions for customers are refused

Today, a quote or lock with onBehalfOf is refused with 400 "Invalid onBehalfOf id", even for your own active customers. Until Multigate fixes this, convert in your own business's accounts, or contact Multigate.

The sandbox doesn't check your balance

In production, locking a quote the source account can't cover returns 402 "Insufficient balance", and a failed debit returns 402 "The settlement has failed due to an issue with our banking partners." In the sandbox, neither stops the conversion: an unfunded lock succeeds. Handle 402 in your code anyway. See Sandbox vs production.

Convert

Get a quote

amount is what you want to receive, in the target currency: here, 1,000 USD paid for in NGN. Save the quote's id in MG_QUOTE_ID:

QUOTE=$(curl -s "$MG_BASE_URL/trade-service/api/v1/fx-rates/best-rate?sourceCurrency=NGN&targetCurrency=USD&amount=1000" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "entityId: $MG_CLIENT_ID")
echo "$QUOTE"
export MG_QUOTE_ID=$(echo "$QUOTE" | jq -r .id)
{
  "message": "The FX rate has been successfully fetched.",
  "id": "FX-261010-150000482913",
  "sourceCurrency": "NGN",
  "targetCurrency": "USD",
  "buyRate": 1530.5,
  "sellRate": 0.0006533812479581836,
  "validityPeriod": 60,
  "startTime": "2026-10-10T15:00:00.000Z",
  "endTime": "2026-10-10T15:01:00.000Z",
  "createdAt": "2026-10-10T15:00:00.482Z",
  "updatedAt": "2026-10-10T15:00:00.482Z"
}
  • buyRate: source units per one target unit (here, NGN per USD), to 6 decimal places. Show this one to your user.
  • sellRate: target units per one source unit (1 / buyRate).
  • The quote expires at endTime, usually about 60 seconds after startTime. Show a countdown and lock straight after the user confirms.
  • Every call creates a new quote. Getting another one doesn't cancel the first.
Lock it

Store the quote ID in your database first: it is your retry key, and the webhook can arrive before the response. Then send the quote id, the two accounts and the same amount you quoted, before endTime. Add a reference, unique across Multigate, that you can look the conversion up by.

curl -s -X POST "$MG_BASE_URL/trade-service/api/v1/fx-rates/lock-rate" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "entityId: $MG_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "id": "$MG_QUOTE_ID",
  "sourceAccountId": "$MG_NGN_ACCOUNT_ID",
  "targetAccountId": "$MG_USD_ACCOUNT_ID",
  "amount": 1000,
  "reference": "acme_conv_$(date +%s)"
}
EOF
{
  "message": "The FX rate has been successfully locked.",
  "id": "FX-261010-150000482913",
  "status": "completed",
  "sourceCurrency": "NGN",
  "sourceAmount": "1530500",
  "targetCurrency": "USD",
  "targetAmount": "1000",
  "rate": "0.0006533812479581836",
  "tradeReference": "acme_conv_1791640820",
  "sourceAccountNumber": "1029384756",
  "targetAccountNumber": "4410023391",
  "valueDate": "2026-10-10T15:00:20.114Z",
  "createdAt": "2026-10-10T15:00:20.114Z",
  "updatedAt": "2026-10-10T15:00:21.902Z"
}
  • The source account is debited sourceAmount (2 decimal places); the target account receives targetAmount. Record sourceAmount: it is close to amount × buyRate but not always equal.
  • rate is the quote's sellRate.
  • Amounts may be strings or numbers: see Amounts and currencies.
  • sourceAccountNumber, targetAccountNumber and valueDate appear only in this response. Store them if you need them.
Wait for settlement if needed

If status is completed, the target account is already credited. You may already have received trade / trade_completed for it.

If it's processing, wait for trade_completed, or check with Get a conversion using the quote ID or your reference.

Conversion statuses

stateDiagram-v2
    direction LR
    [*] --> completed: credited straight away
    [*] --> processing: debited, credit waiting for settlement
    [*] --> awaiting_balance: post-funded, short of funds
    [*] --> failed: provider refused at lock time
    awaiting_balance --> processing: account funded and debited
    processing --> completed: settled and credited
    processing --> failed: provider failed (pairs priced through USD)
    completed --> [*]
    failed --> [*]
Status Final Meaning
completed Yes The target account is credited. trade_completed is sent.
processing No The source is debited; the credit is waiting for the liquidity provider or Multigate's settlement. trade_completed follows.
awaiting_balance No Only for post-funded accounts (agreed with Multigate): the account was short of funds, so the debit is deferred. Fund the account.
failed Yes The liquidity provider refused the conversion, usually when you locked it (the lock call returned 400 "Failed to execute trade, please reach out to support"). The source debit is reversed.
Confirm before releasing funds

Today, if the liquidity provider fails a processing conversion later, Multigate reverses the source debit but the conversion can still end as completed (or stay processing), and trade_completed can still be sent. Get a conversion then shows the same status. Before you release money to your own customers based on a conversion that was processing, check that the target account received targetAmount (for example with Get an account balance or its statement).

Handling trade_completed

  • Match the event to your conversion by id, which is the quote ID. tradeReference is your reference (or one Multigate generated).
  • Store the quote ID before you call lock: the event can arrive before the lock response.
  • Read the amounts from your lock response or Get a conversion. For pairs priced through USD, an event sent after a processing conversion settles can carry the amounts of one leg (to or from USD) and a Multigate-generated tradeReference.
  • Deduplicate by Idempotency-Key and by quote ID: the same conversion can be notified more than once.

Retrying safely

Lock has no idempotency key, but a quote can be locked only once. That makes the quote ID your retry key.

If a lock call times out, the connection drops, or you get a 5xx:

  1. Call Get a conversion with the quote ID. If it's found, that is your conversion: follow its status.
  2. If it isn't found, send the same lock request again (same quote ID, same reference):
    • 409 "Rate already locked": the first attempt is recorded or still running. Wait a few seconds and call Get a conversion again.
    • 400 "Rate expired": no conversion was recorded for this quote. Check the source account balance for a debit you can't explain before you get a new quote, and contact support with the quote ID if you find one.
    • 200: converted.
  3. Never lock a new quote for the same purpose until you've confirmed the first one didn't go through.

503 "Failed to execute trade, please reach out to support" means Multigate couldn't reserve the quote: nothing was debited, so retry the same request shortly. The same message with 400 means the provider refused and the conversion failed, so always check the HTTP status, not just the message.

Conversions for customers

Get a conversion and List conversions only return conversions made for your own business. A conversion locked for a customer (onBehalfOf) returns 404. Track customer conversions with the trade_completed webhook (it comes to you) and the customer's account balances.

Use a new reference for every conversion, including after a failed one: a reused value returns 409 "Duplicate reference". References must be unique across Multigate, so add your own prefix or use a UUID.

Which pairs you can convert

Pairs depend on the liquidity Multigate has configured. Get a quote to check a pair: an unavailable pair returns 404 (or 400) "Rates for this currency pair are currently unavailable."

  • Pairs that include USD are quoted directly. Other pairs are priced through USD.
  • USD to NGN, USD to GHS and USD to ETB are quoted for information only. Locking them returns 404 "The rate is not currently tradeable". They exist because many treasury systems expect a buy and a sell rate for every pair: the rate is derived from the opposite direction (for example NGN to USD) with a margin, so you can display it, not trade on it.

Errors

Message Status Call Fix
Validation error body 400 Both Fix the field named in errors.
Rates for this currency pair are currently unavailable. 400, 404 Quote The pair isn't available right now. Try later or ask Multigate.
Invalid onBehalfOf id 400, 404 Both onBehalfOf isn't your customer, doesn't exist, or doesn't match the quote's customer. On lock, leave it out or send the quote's customer.
Unauthorized access 403 Both Your API user lacks the permission, or the entityId header isn't one of your businesses.
The rate could not be found. 404 Lock Unknown quote ID.
The rate is not currently tradeable 404 Lock The pair is quoted for information only.
Rate already locked 409 Lock The quote was already used, or a lock is in progress. Look it up with Get a conversion.
Rate expired 400 Lock Get a new quote and lock it before endTime.
Duplicate reference 409 Lock Send a new reference.
The customer viban could not be found. 400 Lock sourceAccountId or targetAccountId doesn't exist. Use accountId values.
Invalid source or target account 400 Lock Accounts must be yours (or the quote's customer's) and match the quote currencies.
The multi-gate viban could not be found. 400 Lock The pair isn't set up on Multigate's side. Contact support.
Insufficient balance 402 Lock Production only. The available balance must be more than the debit. Fund the account. Nothing was converted.
The settlement has failed due to an issue with our banking partners. 402 Lock Production only. The debit didn't go through. Check the source balance before retrying: in rare cases a debit that timed out completes later.
Failed to execute trade, please reach out to support 400 Lock The liquidity provider refused. The conversion is failed and the debit was reversed. Get a new quote.
Reversal failed. Please try again later. 417 Lock The provider refused and the automatic refund failed: the money is still debited. Contact support with the quote ID. Don't retry.
Failed to execute trade, please reach out to support 503 Lock Multigate couldn't reserve the quote. Nothing was debited. Retry the same request.

Next steps