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.
POST /v1/fx/quotesrequires scopefx:quotePOST /v1/fx/tradesrequires scopefx:trade
FX quoting is unavailable on weekendsQuoting 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
| Field | Required | Description |
|---|---|---|
clientId | Yes | The client the trade will settle for. Must be a client your credential may act for. This may be a sub account client Id |
buyCurrency | Yes | ISO 4217 code of the currency you want to receive. |
sellCurrency | Yes | ISO 4217 code of the currency you want to spend. |
fixedSide | Yes | Buy or Sell. This selects which of the two amounts you are fixing. |
amount | Yes | The amount on the fixed side, in minor units. Must be greater than zero. |
valueDate | No | Settlement date. Omit it to get the earliest available date for the pair. |
amountis an integer in minor units
50000is 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"
}| Field | Description |
|---|---|
quoteId | Pass this to POST /v1/fx/trades to execute. |
buy, sell | Both 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. |
fixedSide | Echoes the side you fixed. |
valueDate | When the trade will settle. |
buyRate, sellRate | The rate in both directions, so you do not have to invert it yourself. |
expiresAt | The 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
| Field | Required | Description |
|---|---|---|
clientId | Yes | Must match the client the quote was requested for. |
quoteId | Yes | The quote to execute. It must still be active. |
internalReference | Yes | Your own reference for this trade, up to 128 characters. This is also the idempotency key. See below. |
accountId | Yes | The 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 usesinternalReference, notidempotencyKeyPayments are made retry-safe with
idempotencyKey. FX trades are not: the field is calledinternalReferenceand it does the same job. Retrying with the sameinternalReferenceand the same details returns the original trade instead of booking a second one. Reusing it with different details is a409. Generate it the same way you would generate an idempotency key, and store it before you send the request. See Idempotency.
Errors worth handling
| Code | Meaning | What to do |
|---|---|---|
400 | The request is malformed, or the request signature failed verification. | Fix the request. |
403 | clientId is not a client your credential may act for. | Check the client. |
404 | The client, quote or account does not exist. | Check the ids. Quotes belonging to another client are invisible to you and read as missing. |
409 | This internalReference already booked a different trade. | Use a fresh reference, or look up the trade you already booked. |
422 | The 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.
Updated about 1 month ago