Buying and selling currency

Converting between currencies is a two-step flow: you ask for a quote, then you execute it. The quote pins a rate for a short window; nothing moves and nothing is committed until you book the trade.

  1. POST /v1/fx/quotes requires scope fx:quote
  2. POST /v1/fx/trades requires scope fx:trade
⚠️

FX quoting is unavailable on weekends

Quoting and trading close at 5:00 PM New York time (ET) on Friday and reopen at 5:00 PM ET on Sunday. Requests made during this window will fail.

Step 1: request a quote

FieldRequiredDescription
clientIdYesThe client the trade will settle for. Must be a client your credential may act for. This may be a sub account client Id
buyCurrencyYesISO 4217 code of the currency you want to receive.
sellCurrencyYesISO 4217 code of the currency you want to spend.
fixedSideYesBuy or Sell. This selects which of the two amounts you are fixing.
amountYesThe amount on the fixed side, in minor units. Must be greater than zero.
valueDateNoSettlement date. Omit it to get the earliest available date for the pair.
⚠️

amount is an integer in minor units

50000 is 500.00 GBP, not 50,000. See Amounts, currencies and dates.

fixedSide is the field people get wrong. Buy means "give me exactly this much buyCurrency, take whatever sellCurrency that costs". Sell means "spend exactly this much sellCurrency, give me whatever buyCurrency that produces". The amount you send always refers to the side you fixed.

GET /v1/reference/fx-value-dates/{pair} lists the value dates available for a pair, if you need to settle on a particular day.

The quote you get back

{
  "quoteId": "e5000000-0000-0000-0000-000000000005",
  "buy":  { "currency": "EUR", "amountInMinorUnits": 2500, "value": 25.00 },
  "sell": { "currency": "GBP", "amountInMinorUnits": 2138, "value": 21.38 },
  "fixedSide": "Buy",
  "valueDate": "2026-09-10",
  "buyRate": 1.170000,
  "sellRate": 0.854700,
  "expiresAt": "2026-09-08T15:31:22Z"
}
FieldDescription
quoteIdPass this to POST /v1/fx/trades to execute.
buy, sellBoth sides of the deal, each returned as an Amount object with currency, amountInMinorUnits and value. The side you fixed matches what you asked for; the other side is what the rate produced.
fixedSideEchoes the side you fixed.
valueDateWhen the trade will settle.
buyRate, sellRateThe rate in both directions, so you do not have to invert it yourself.
expiresAtThe quote is executable until this instant. After it, booking fails with 422. Quotes expire 10 seconds after creation

Requesting a quote commits you to nothing. Quoting and then not trading is a normal thing to do. Use it to price a conversion for a user before they confirm.

Step 2: book the trade

FieldRequiredDescription
clientIdYesMust match the client the quote was requested for.
quoteIdYesThe quote to execute. It must still be active.
internalReferenceYesYour own reference for this trade, up to 128 characters. This is also the idempotency key. See below.
accountIdYesThe account the trade settles through.

The response is 201 with the trade's id and its URL in the Location header. Fetch the detail with GET /v1/fx/trades/{id}, or list trades with GET /v1/fx/trades (scope fx:read). The detail read also returns an events array holding every event raised for the trade.

📘

FX uses internalReference, not idempotencyKey

Payments are made retry-safe with idempotencyKey. FX trades are not: the field is called internalReference and it does the same job. Retrying with the same internalReference and the same details returns the original trade instead of booking a second one. Reusing it with different details is a 409. Generate it the same way you would generate an idempotency key, and store it before you send the request. See Idempotency.

Errors worth handling

CodeMeaningWhat to do
400The request is malformed, or the request signature failed verification.Fix the request.
403clientId is not a client your credential may act for.Check the client.
404The client, quote or account does not exist.Check the ids. Quotes belonging to another client are invisible to you and read as missing.
409This internalReference already booked a different trade.Use a fresh reference, or look up the trade you already booked.
422The quote has expired or has already been executed.Request a new quote and book that. Do not retry the same quoteId.

A 422 on booking is the normal outcome of a slow user: the quote lapsed between pricing and confirmation. Treat it as a re-quote, not as a failure.

Testing in the sandbox

The sandbox prices quotes and books trades exactly as production does. To exercise the unhappy path, POST /v1/simulations/fx-trades/{id}/reject rejects a trade you have booked, so you can prove your reconciliation handles it. To test expiry, request a quote and wait past expiresAt before booking.

See Sandbox Outcome Triggers for the rest of the deterministic sandbox behaviour.


Did this page help you?