Skip to main content
This page is the fastest path from an empty terminal to an executed v3 trade. By the end you’ll have minted a JWT, called an authenticated endpoint, quoted a 1000 USD → USDC conversion, executed it, and seen the resulting USDC reflected in your balances. Every example chains into the next: the IDs returned in one step are the inputs to the next.

Before you start

  • An OpenFX API key: generate one from the dashboard and download the JSON file (contains name and privateKey). See Authentication for the file format and how to sign tokens.
  • Node.js, Python, or any HTTP client (cURL works fine)
  • About 5 minutes
Coming from v2? The high-level flow is unchanged: pairs → quote → trade → balances. The wire contract changes materially: URL prefix, response envelope, amount types, quote request shape, pagination, and error handling.

1. Mint a JWT bearer token

Every API call to /v3/* uses a fresh JWT and X-Request-Signature: an Authorization: Bearer <jwt> header and an ES256 signature over the request. JWTs are ES256-signed, single-use, and expire 60 seconds after issuance. Mint both together, per request, with the signRequest helper. See Mint a JWT and sign the request for the implementation in Node, Python, Java, C++, and Go.
The cURL snippets below show the wire shape, not a request the API will accept as-is. Every request needs a fresh JWT in Authorization and a matching X-Request-Signature, and cURL alone can’t compute an ES256 signature. Copy-pasting one verbatim returns 401 AUTH_TOKEN_INVALID. For a request the API accepts, call signRequest from Authentication, or run Putting it together below.
JWTs are single-use (server-enforced nonce uniqueness) and have a 60-second TTL. Copy-pasting one twice is rejected with 401 AUTH_TOKEN_INVALID (details.reason: REPLAYED). Re-mint between every example below. See Authentication for the helper and signing flow.
The $OPENFX_JWT and $OPENFX_SIGNATURE env vars below are reused across steps for readability. Production code mints and signs per request. The linear walkthrough (pairs → quote → trade → balances) reads more naturally with one exported token, but any sequence that includes user think-time, retries, polling, or pagination must mint a fresh JWT and a fresh X-Request-Signature per HTTP call. The canonical pattern is in Rate limiting → Handling rate limits pattern; every retry/poll/pagination sample elsewhere in v3 takes a signRequest({ method, url, body }) => { headers, body } parameter rather than a captured token. Putting it together below shows the real per-call version, in every supported language.

2. Verify connectivity

Hit GET /v3/fx/pairs (authenticated, no body). If your JWT is good, you’ll get back the list of tradable pairs. This is the smoke test that confirms key, signing, and network path all work end-to-end.
cURL
A successful response contains the complete bounded list of Pair resources nested under data.pairs. The actual response wraps the object below in data; the PairList shape itself is:
Every response carries X-Trace-Id and RateLimit-* in the headers. Capture X-Trace-Id on every response (success too) so support can reconstruct the request later. See Metadata & tracing.

3. Quote a trade

POST /v3/fx/quotes returns a price guaranteed until expiresAt (default 3 seconds). Quotes are a separate step from execution so you can show the user a rate, get confirmation, and execute without surprising them on the price. Give the request its own Idempotency-Key header: a client-generated string that lets you safely retry the call. Generate one UUID v4 per logical operation. See Idempotency. Pick which side of the trade you want to anchor: supply sellAmount if you want to spend an exact amount, or buyAmount if you need to receive an exact amount. Supply exactly one. The server computes the other side at the quoted rate. This example anchors the sell side (1000 USD out).
cURL
A successful response wraps the Quote in data. The Quote resource itself is:
Keep the id; you’ll pass it to the next step. sellAmount is exact ($1000.00) and quoteAmount is the server-computed counter-leg. Notice both amounts are strings, not JSON numbers; see Amounts.

4. Execute the trade

Submit the quote ID to POST /v3/fx/trades before expiresAt (about 3 seconds from the quote’s createdAt); after that, the call returns 409 QUOTE_EXPIRED and you go back to step 3 for a fresh price.
cURL
A successful response wraps the Trade in data, with status: "EXECUTED". The Trade resource itself is:
One Idempotency-Key per logical operation. The quote and the trade are two operations, so generate two keys. Reusing a key for a different trade (same endpoint, different body) returns 422 IDEMPOTENCY_MISMATCH. Keys are scoped per endpoint, so reusing one across the quote and the trade is safe but confusing. See Idempotency.

5. Check your balance

Read your balances to confirm the USDC purchased by the trade: GET /v3/fx/balances returns a Balance per currency, plus an aggregate block with your org-wide net equity. The USDC you just bought should show up in withdrawableBalance. Trade execution doesn’t return balances; fetch them separately.
cURL
The response wraps this in data; the BalancesData object itself is:
Assuming a starting USD balance of 5150.00, the trade above debited 1000.00 USD and credited 999.50 USDC, leaving 4150.00 USD total. In this example, aggregate.withdrawableBalance is the sum of the two withdrawableBalance rows (4000.00 + 999.50), with USDC counted at 1 USD. Your own balances will differ. That’s the full round-trip: pairs → quote → trade → balances. The same shape and headers apply to every other v3 endpoint.

Putting it together

The complete round-trip below, real signRequest and all, mints a fresh JWT and signature per call with no reused jwt symbol:

What’s next

Resource IDs

Readable, typed-prefix ID format, stability guarantees, and cross-resource references.

Errors

Typed error codes, retryable flags, and the catch-and-branch pattern for your next 5xx.

Settlement models

Deposits, withdrawals, and the unified fiat + stablecoin endpoint.

API reference

Every endpoint, every field, every response shape.

Live checklist

Pre-launch checklist, key rotation, IP allowlisting.