> ## Documentation Index
> Fetch the complete documentation index at: https://public-openfx.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Amounts

> Format rules, prefix vocabulary, parsing examples, and precision behavior for monetary values.

Every monetary amount in v3 (request body, response field, query parameter) is a **JSON string** with a semantic prefix that tells you what the number refers to: `buyAmount`, `sellAmount`, `withdrawalAmount`, `depositAmount`, `withdrawableBalance`, and so on. No floats; no JSON numbers like `1000.50`. Sending a number instead of a string returns `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. Reading the response with a JSON parser that auto-converts to `float` re-introduces the bug v3 is designed to avoid.

This illustrative object combines amount fields from several resource schemas. It is not a standalone request or response and omits the other fields required by those resources.

```json theme={"theme":{"light":"poimandres","dark":"poimandres"}}
{
  "buyAmount": "1000",
  "sellAmount": "999.5",
  "withdrawableBalance": "5000",
  "totalBalance": "5150"
}
```

<Warning>
  **Strings, always.** A request body with `"withdrawalAmount": 1000` (JSON
  number, not string) returns `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. Sending `"1000"`
  works; sending `1000` doesn't.
</Warning>

## Why strings

Floats lose precision. `0.1 + 0.2` is `0.30000000000000004` in JavaScript and in most languages whose numbers are IEEE-754 floats, which is how JSON numbers are typically parsed. For monetary amounts that's unacceptable: a \$0.000000000004 discrepancy compounds across millions of trades.

Strings preserve every digit the API returns. FX responses can contain up to 16 fractional digits; parse them with a decimal representation such as `Decimal`, `BigDecimal`, or `bigint*scale`.

## Format

| Constraint | Behavior |
| - | - |
| FX request amount pattern | `PositiveRequestAmount`: up to 16 fractional digits; request amounts must be greater than zero. |
| Non-negative response pattern | `PositiveAmount`: `^[0-9]+(\.[0-9]{1,16})?$`, used for FX response and limit amounts. |
| Signed response pattern | `SignedAmount`: `^-?[0-9]+(\.[0-9]{1,16})?$`, used where a negative response value is meaningful. |
| FX response precision | Up to 16 fractional digits. FX amount schemas do not impose a per-currency fractional-digit cap. |
| Withdrawal precision | Withdrawal requests retain the documented per-currency business checks: 2 decimals for fiat and 6 for crypto. |
| Leading zero required | Yes (`"0.50"`, not `".50"`). |
| Scientific notation | Not allowed (`1e3` is invalid). |
| Decimal separator | Period (`.`) only. |
| Thousands separators | Not allowed. No commas, spaces, underscores, or apostrophes between digits. These fail with `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. |
| Trailing zeroes | FX responses do not preserve trailing zeroes; for example, an amount may be returned as `"10.5"`, not `"10.50"`. |
| Negative amounts | FX requests reject a leading `-` with `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. Response fields may be negative only where they use `SignedAmount`. |

An FX request body with a zero amount (`"0"` or `"0.00"`) or a negative amount (for example, `"-1.00"`) returns `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. The FX request schemas reject zero and negative amounts before business-rule checks.

## Prefix vocabulary

Amount fields are **always** prefixed to describe what they refer to. There is no bare top-level `amount` field on any resource in v3. Every monetary field carries a qualifier and ends in `Amount` or `Balance`. A `Deposit` carries `depositAmount`, a `Withdrawal` carries `withdrawalAmount`, a `Quote` and `Trade` carry `buyAmount` / `sellAmount`, and a `Balance` carries `withdrawableBalance` / `totalBalance`. The one exception is a nested object that's already qualified by its parent key: the exposure summary's `usageBreakdown.settlableNow.amount` and `.settlableLater.amount`.

| Prefix | Meaning |
| - | - |
| `buyAmount` | The amount being bought / received. On `POST /v3/fx/quotes`, supply this when you want to lock the **buy** side (you choose what to receive); the server computes `sellAmount`. |
| `sellAmount` | The amount being sold / debited. On `POST /v3/fx/quotes`, supply this when you want to lock the **sell** side (you choose what to spend); the server computes `buyAmount`. Supply exactly one of `buyAmount` or `sellAmount` on a quote request. |
| `withdrawalAmount` | The amount to withdraw, on a `POST /v3/fx/withdrawals` request body and on the `Withdrawal` response. Request amount must be greater than zero; the response uses `PositiveAmount`. |
| `depositAmount` | The amount that landed, on a `Deposit` response. |
| `withdrawableBalance` | Funds available for trading or withdrawing right now (on a `Balance` row, or on the response's `aggregate` block). Never negative: `0` when the corresponding `totalBalance` (or the org's net equity) is negative. |
| `totalBalance` | Total funds held in a currency, including any amount earmarked against pending operations (on a `Balance` row). May be negative when the org owes a settlement (`SignedAmount`); when it is, `withdrawableBalance` is `0` rather than `≤ totalBalance`. |
| `minTradeAmount` | Smallest tradable amount, denominated in that pair entry's `sellCurrency`. Each trade direction is listed as its own pair entry, so the limit tracks direction, not just the currency pair. |
| `maxTradeAmount` | Largest tradable amount, denominated in that pair entry's `sellCurrency`. Each trade direction is listed as its own pair entry, so the limit tracks direction, not just the currency pair. |

Every monetary field carries a qualifier and ends in `Amount` or `Balance`. There's no bare `amount` field to infer meaning from context.

## Parsing in clients

<CodeGroup>
  ```javascript Node theme={"theme":{"light":"poimandres","dark":"poimandres"}}
  // Parses buyAmount/sellAmount as Decimal, not float, to avoid the precision
  // loss floats introduce — the pattern from /v3/amounts#parsing-in-clients.
  const Decimal = require("decimal.js");
  // signRequest is the real helper from /v3/authentication — adjust this path
  // if you've copied it somewhere else in your own project.
  const { signRequest } = require("../../authentication/node/sign-request");
  // Divides two string amount fields as Decimal. Kept separate from the
  // network call so it's testable without a real request. Exactly one of
  // buyAmount/sellAmount is present on a trade — whichever side the quote
  // was anchored on — and executedAmount carries the other side.
  function computeRate(trade) {
    const buy = new Decimal(trade.buyAmount ?? trade.executedAmount);
    const sell = new Decimal(trade.sellAmount ?? trade.executedAmount);
    return buy.div(sell);
  }
  async function getTrade(id) {
    const url = `https://api.openfx.com/v3/fx/trades/${id}`;
    const { headers } = signRequest({ method: "GET", url });
    const res = await fetch(url, { headers });
    const { data } = await res.json();
    return data;
  }
  async function tradeRate(id) {
    const trade = await getTrade(id);
    return computeRate(trade);
  }
  module.exports = { computeRate, getTrade, tradeRate };
  if (require.main === module) {
    tradeRate("some-trade-id").catch((err) => {
      console.error("Error:", err.message);
      process.exitCode = 1;
    });
  }
  ```

  ```python Python theme={"theme":{"light":"poimandres","dark":"poimandres"}}
  """Parses buyAmount/sellAmount as Decimal, not float, to avoid the precision
  loss floats introduce — the pattern from /v3/amounts#parsing-in-clients."""
  import os
  import sys
  from decimal import Decimal, getcontext
  # sign_request is the real helper from /v3/authentication — adjust this path
  # if you've copied it somewhere else in your own project.
  sys.path.insert(
      0, os.path.join(os.path.dirname(__file__), "..", "..", "authentication", "python")
  )
  from sign_request import sign_request  # noqa: E402
  import requests  # noqa: E402
  getcontext().prec = 28
  def compute_rate(trade):
      """Divides two string amount fields as Decimal. Kept separate from the
      network call so it's testable without a real request. Exactly one of
      buyAmount/sellAmount is present on a trade — whichever side the quote
      was anchored on — and executedAmount carries the other side."""
      buy = Decimal(trade.get("buyAmount", trade["executedAmount"]))
      sell = Decimal(trade.get("sellAmount", trade["executedAmount"]))
      return buy / sell
  def get_trade(trade_id):
      url = f"https://api.openfx.com/v3/fx/trades/{trade_id}"
      headers, _ = sign_request("GET", url)
      res = requests.get(url, headers=headers)
      return res.json()["data"]
  def trade_rate(trade_id):
      trade = get_trade(trade_id)
      return compute_rate(trade)
  if __name__ == "__main__":
      print(trade_rate("some-trade-id"))
  ```

  ```java Java theme={"theme":{"light":"poimandres","dark":"poimandres"}}
  // Parses buyAmount/sellAmount as BigDecimal, not double, to avoid the
  // precision loss floats introduce — the pattern from
  // /v3/amounts#parsing-in-clients.
  import java.math.BigDecimal;
  import java.math.MathContext;
  import java.net.HttpURLConnection;
  import java.net.URI;
  import java.util.Map;
  public class ParseAmounts {
      // Divides two string amount fields as BigDecimal. Kept separate from
      // the network call so it's testable without a real request. Exactly
      // one of buyAmount/sellAmount is present on a trade — whichever side
      // the quote was anchored on — and executedAmount carries the other
      // side.
      static BigDecimal computeRate(Map<String, String> trade) {
          String buyAmount = trade.getOrDefault("buyAmount", trade.get("executedAmount"));
          String sellAmount = trade.getOrDefault("sellAmount", trade.get("executedAmount"));
          BigDecimal buy = new BigDecimal(buyAmount);
          BigDecimal sell = new BigDecimal(sellAmount);
          return buy.divide(sell, new MathContext(28));
      }
      static Map<String, String> getTrade(String id) throws Exception {
          String url = "https://api.openfx.com/v3/fx/trades/" + id;
          // SignRequest is the real helper from /v3/authentication, compiled
          // on the classpath alongside this file — see that page's example.
          Map<String, String> headers = SignRequest.signRequest("GET", url, new byte[0]);
          HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
          conn.setRequestMethod("GET");
          for (Map.Entry<String, String> h : headers.entrySet()) {
              conn.setRequestProperty(h.getKey(), h.getValue());
          }
          conn.getResponseCode();
          // A real implementation would parse the JSON response body's `data`
          // object into a Map here.
          return Map.of();
      }
      public static void main(String[] args) throws Exception {
          Map<String, String> trade = getTrade("some-trade-id");
          System.out.println(computeRate(trade));
      }
  }
  ```

  ```cpp C++ theme={"theme":{"light":"poimandres","dark":"poimandres"}}
  // Parses buyAmount/sellAmount as a fixed-point decimal, not double, to avoid
  // the precision loss floats introduce — the pattern from
  // /v3/amounts#parsing-in-clients. Requires OpenSSL and libcurl.
  #include <curl/curl.h>
  #include <cstdint>
  #include <iostream>
  #include <map>
  #include <string>
  // Matches the definition in ../../authentication/cpp/sign_request.cpp,
  // compiled together with this file — see that file for the real
  // implementation of signRequest.
  struct SignedRequest {
      std::string authHeader;
      std::string sigHeader;
  };
  SignedRequest signRequest(const std::string& method, const std::string& urlPath,
                             const std::string& query, const std::string& body);
  // A fixed-point decimal: value * 10^-scale. Avoids the precision loss a
  // double introduces when dividing decimal string amounts.
  struct Decimal {
      int64_t value;
      int scale;
  };
  static Decimal parseDecimalString(const std::string& s) {
      size_t dot = s.find('.');
      if (dot == std::string::npos) return {std::stoll(s), 0};
      std::string digits = s.substr(0, dot) + s.substr(dot + 1);
      return {std::stoll(digits), static_cast<int>(s.size() - dot - 1)};
  }
  // std::to_string does not support __int128 — convert by hand.
  static std::string int128ToString(__int128 value) {
      if (value == 0) return "0";
      std::string result;
      while (value > 0) {
          result.insert(result.begin(), '0' + static_cast<int>(value % 10));
          value /= 10;
      }
      return result;
  }
  // Returns trade[key] if present, else trade["executedAmount"]. Exactly one
  // of buyAmount/sellAmount is present on a trade — whichever side the quote
  // was anchored on — and executedAmount carries the other side.
  static const std::string& amountOrExecuted(const std::map<std::string, std::string>& trade,
                                              const std::string& key) {
      auto it = trade.find(key);
      return it != trade.end() ? it->second : trade.at("executedAmount");
  }
  // Divides two string amount fields as fixed-point decimals, scaled to 20
  // fractional digits. Kept separate from the network call so it's testable
  // without a real request.
  std::string computeRate(const std::map<std::string, std::string>& trade) {
      Decimal buy = parseDecimalString(amountOrExecuted(trade, "buyAmount"));
      Decimal sell = parseDecimalString(amountOrExecuted(trade, "sellAmount"));
      const int outScale = 20;
      __int128 numerator = static_cast<__int128>(buy.value);
      for (int i = 0; i < outScale + sell.scale - buy.scale; i++) numerator *= 10;
      __int128 quotient = numerator / sell.value;
      std::string digits = int128ToString(quotient);
      while (static_cast<int>(digits.size()) <= outScale) digits = "0" + digits;
      return digits.substr(0, digits.size() - outScale) + "." + digits.substr(digits.size() - outScale);
  }
  static size_t discard(char*, size_t s, size_t n, void*) { return s * n; }
  std::map<std::string, std::string> getTrade(const std::string& id) {
      std::string url = "https://api.openfx.com/v3/fx/trades/" + id;
      SignedRequest req = signRequest("GET", "/v3/fx/trades/" + id, "", "");
      CURL* curl = curl_easy_init();
      curl_slist* headers = nullptr;
      headers = curl_slist_append(headers, ("Authorization: " + req.authHeader).c_str());
      headers = curl_slist_append(headers, ("X-Request-Signature: " + req.sigHeader).c_str());
      curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discard);
      curl_easy_perform(curl);
      curl_slist_free_all(headers);
      curl_easy_cleanup(curl);
      // A real implementation would parse the JSON response body's `data`
      // object here.
      return {};
  }
  #ifndef PARSE_AMOUNTS_NO_MAIN
  int main() {
      try {
          auto trade = getTrade("some-trade-id");
          std::cout << computeRate(trade) << std::endl;
          return 0;
      } catch (const std::exception& e) {
          std::cerr << "Error: " << e.what() << std::endl;
          return 1;
      }
  }
  #endif
  ```

  ```go Go theme={"theme":{"light":"poimandres","dark":"poimandres"}}
  // Parses buyAmount/sellAmount as big.Rat, not float64, to avoid the
  // precision loss floats introduce — the pattern from
  // /v3/amounts#parsing-in-clients.
  package main
  import (
  	"fmt"
  	"math/big"
  	"net/http"
  	// auth is the real signRequest helper from /v3/authentication — see
  	// go.mod's replace directive if you've copied it somewhere else.
  	auth "openfx-v3-authentication-example"
  )
  // amountOrExecuted returns trade[key] if present, else trade["executedAmount"].
  // Exactly one of buyAmount/sellAmount is present on a trade — whichever side
  // the quote was anchored on — and executedAmount carries the other side.
  func amountOrExecuted(trade map[string]string, key string) string {
  	if value, ok := trade[key]; ok {
  		return value
  	}
  	return trade["executedAmount"]
  }
  // Divides two string amount fields as big.Rat. Kept separate from the
  // network call so it's testable without a real request.
  func computeRate(trade map[string]string) (*big.Rat, error) {
  	buyAmount := amountOrExecuted(trade, "buyAmount")
  	sellAmount := amountOrExecuted(trade, "sellAmount")
  	buy, ok := new(big.Rat).SetString(buyAmount)
  	if !ok {
  		return nil, fmt.Errorf("invalid buyAmount: %q", buyAmount)
  	}
  	sell, ok := new(big.Rat).SetString(sellAmount)
  	if !ok {
  		return nil, fmt.Errorf("invalid sellAmount: %q", sellAmount)
  	}
  	return new(big.Rat).Quo(buy, sell), nil
  }
  func getTrade(id string) (map[string]string, error) {
  	url := "https://api.openfx.com/v3/fx/trades/" + id
  	signed, err := auth.SignRequest("GET", url, nil)
  	if err != nil {
  		return nil, err
  	}
  	req, err := http.NewRequest("GET", url, nil)
  	if err != nil {
  		return nil, err
  	}
  	for k, v := range signed.Headers {
  		req.Header.Set(k, v)
  	}
  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return nil, err
  	}
  	defer res.Body.Close()
  	// A real implementation would parse the JSON response body's `data`
  	// object here.
  	return map[string]string{}, nil
  }
  func main() {
  	trade, err := getTrade("some-trade-id")
  	if err != nil {
  		fmt.Println("Error:", err)
  		return
  	}
  	rate, err := computeRate(trade)
  	if err != nil {
  		fmt.Println("Error:", err)
  		return
  	}
  	fmt.Println(rate.FloatString(20))
  }
  ```
</CodeGroup>

## Sending amounts

Stringify the value before adding it to a JSON body. Most JSON serializers will turn a string into a JSON string with no special handling.

```javascript theme={"theme":{"light":"poimandres","dark":"poimandres"}}
await fetch("https://api.openfx.com/v3/fx/withdrawals", {
  method: "POST",
  headers: {/* ... */},
  body: JSON.stringify({
    withdrawalAccountId: "...",
    withdrawalAmount: "1000.00", // string, not 1000.00
    currency: "USD",
  }),
});
```

## Withdrawal precision rules per currency type

For withdrawal requests, the server enforces:

* **Fiat:** max 2 decimal places. `"100.00"` ✓, `"100.123"` → `422` <span class="ofx-enum">WITHDRAWAL\_INVALID\_AMOUNT\_PRECISION</span> with `details: { maxDecimals: 2 }`
* **Crypto:** max 6 decimal places. `"100.000001"` ✓, `"100.0000001"` → same error with `maxDecimals: 6`

These per-currency withdrawal checks are business rules; they do not limit FX request or response precision.

## Currency codes

Amounts always travel with a currency code (`currency`, `buyCurrency`, `sellCurrency`).

| Type | Format | Examples |
| - | - | - |
| Fiat | ISO 4217 (3 uppercase letters) | <span class="ofx-enum">USD</span>, <span class="ofx-enum">EUR</span>, <span class="ofx-enum">GBP</span> |
| Crypto | Canonical ticker | <span class="ofx-enum">USDC</span>, <span class="ofx-enum">USDT</span>, <span class="ofx-enum">EURC</span> |

Pattern: `^[A-Z0-9]{2,15}$` (some tokens contain digits, e.g. `1INCH`).

## Common mistakes

* **Sending** `withdrawalAmount: 1000.50 `**(number) instead of** `"1000.50" `**(string).** The number-vs-string distinction is enforced server-side and returns `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. JSON serializers in some languages quietly emit numbers if you give them a `Decimal` or `BigDecimal`; explicitly `.toString()` before serializing.
* **Parsing into a native float.** Round-tripping `"1.10"` through `JSON.parse` followed by `Number()` lands you back in IEEE-754 territory. Wrap in `Decimal` / `BigDecimal` immediately on read.
* **Formatting with locale separators.** Use the period (`.`) decimal separator and no thousands separators. A locale-aware formatter that emits `"1,000.50"` or `"1.000,50"` fails with `400` <span class="ofx-enum">VALIDATION\_BODY\_FAILED</span>. Serialize amounts with an invariant/US formatter, not the user's locale.
* **Stripping trailing zeros for display before persisting.** `"1.00"` and `"1"` parse to the same value but are not byte-equivalent strings. If you compare amounts as strings (e.g. for idempotent retries), normalize first.
* **Sending too many decimals on a withdrawal.** Withdrawal requests allow 2 decimals for fiat and 6 for crypto; over-precision returns `422` <span class="ofx-enum">WITHDRAWAL\_INVALID\_AMOUNT\_PRECISION</span> with `details.maxDecimals` and `details.currency`. Round (don't truncate) to the right scale before sending. FX amount schemas allow up to 16 fractional digits.
* **Sending a bare** `amount `**field.** No request body accepts a bare `amount` field. A `Quote` carries `buyAmount` / `sellAmount` / `quoteAmount`; a `Trade` carries `buyAmount` / `sellAmount` / `executedAmount`; a `Withdrawal` request and response carry `withdrawalAmount`; a `Deposit` carries `depositAmount`. There is no `rate` field on either `Quote` or `Trade`. `quoteAmount` / `executedAmount` is the computed counter-leg amount, not a standalone exchange rate. A bare `amount` key in a request is rejected (unknown field on the request body).

## Balance lifecycle: available vs total

Every `Balance` row carries two amounts. Treat them as a state machine, not a snapshot.

* `withdrawableBalance`: spendable now. Never negative.
* `totalBalance`: total held in the currency, including any amount earmarked against PENDING operations (submitted withdrawals, in-flight settlements). Ordinarily `withdrawableBalance` ≤ `totalBalance`, but it can go negative when the org owes a settlement in that currency; `withdrawableBalance` is then `0` rather than a negative-adjacent figure.

The held (earmarked) portion is the difference `totalBalance − withdrawableBalance` (when `totalBalance` is non-negative). It is not broken out as a separate field in v3; a richer per-state breakdown may be added later.

<Note>
  **Quotes do not reserve balance.** Only EXECUTED trades and submitted
  withdrawals do. A quote that never fires is free.
</Note>

### Net equity aggregate

`GET /v3/fx/balances` also returns an `aggregate` block (nested under `data.aggregate`) alongside the per-currency `data.balances` array:

```json theme={"theme":{"light":"poimandres","dark":"poimandres"}}
{
  "currency": "USD",
  "withdrawableBalance": "85000.00"
}
```

`aggregate.withdrawableBalance` is the org's net equity across **every** currency, converted to USD. It's the ceiling on what can actually be withdrawn overall, as distinct from any single currency's `withdrawableBalance`. It follows the same clamping rule: never negative, `0` when the org's overall net position is negative.

### Walkthrough: a trade through its lifecycle

Starting balance: USD `5000.00` total, `5000.00` available.

| Action | `withdrawableBalance` | `totalBalance` | Note |
| - | - | - | - |
| Initial | `5000.00` | `5000.00` | Idle |
| `POST /v3/fx/quotes` (USD 1000 → EUR) | `5000.00` | `5000.00` | Quotes price; they don't hold balance |
| `POST /v3/fx/trades` (executes the quote) | `4000.00` | `4000.00` | Atomic single-hop: USD debited and EUR credited together |
| Quote expires unused | `5000.00` | `5000.00` | If you never EXECUTED |

Trades are single-hop atomic. They debit one currency and credit another in the same operation. There is no intermediate held state for trades themselves, so `withdrawableBalance` and `totalBalance` move together.

### Walkthrough: a withdrawal through its lifecycle

Starting balance: USD `4000.00` total, `4000.00` available.

| Action | `withdrawableBalance` | `totalBalance` | Held (`total − available`) | Withdrawal status |
| - | - | - | - | - |
| Initial | `4000.00` | `4000.00` | `0.00` | — |
| `POST /v3/fx/withdrawals` (USD 1500) | `2500.00` | `4000.00` | `1500.00` | <span class="ofx-enum">PENDING</span> |
| Rail confirms settlement | `2500.00` | `2500.00` | `0.00` | <span class="ofx-enum">COMPLETED</span>: funds left OpenFX |
| (Counterfactual) withdrawal doesn't go through | `4000.00` | `4000.00` | `0.00` | <span class="ofx-enum">CANCELLED</span>: hold released |

Withdrawals earmark funds at POST and release them at completion or cancellation. While <span class="ofx-enum">PENDING</span>, funds are out of `withdrawableBalance` but still counted in `totalBalance`.

### Which view to read

| Use case | Look at |
| - | - |
| "Can I trade USD 3000 right now?" | `withdrawableBalance` |
| "How much USD do I own in total?" | `totalBalance` |
| "What's tied up in in-flight ops?" | `totalBalance − withdrawableBalance` |
| End-of-day reconciliation | `totalBalance` (or both) |

## What's next

<CardGroup cols={2}>
  <Card title="Quickstart" icon="https://public-assets.openfx.com/docs/icons/quickstart.svg" href="/v3/quickstart#3-quote-a-trade">
    A worked quote-then-trade flow that uses these amount fields.
  </Card>

  <Card title="Errors" icon="https://public-assets.openfx.com/docs/icons/errors.svg" href="/v3/errors">
    <span class="ofx-enum">WITHDRAWAL\_INVALID\_AMOUNT\_PRECISION</span> and other amount-related errors.
  </Card>

  <Card title="FX quotes" icon="https://public-assets.openfx.com/docs/icons/quotes.svg" href="/v3/quotes">
    Where `buyAmount`, `sellAmount`, and `quoteAmount` come from.
  </Card>
</CardGroup>
