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. Use Migration from v2 for the full delta.

1. Mint a JWT bearer token

Every request to /v3/fx/* carries an Authorization: Bearer <jwt> header and an X-Request-Signature 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 carry only Authorization — they omit X-Request-Signature because cURL alone can’t compute an ES256 signature. They illustrate the wire shape, not a request the API will accept as-is; copy-pasting one verbatim returns 401 AUTH_TOKEN_INVALID. For a request the API actually 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 env var below is 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. State-changing operations need an 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, alongside metadata. 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

POST /v3/fx/trades executes against the quote ID. You have until 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. Generate two keys. Reusing the same key for two different trades returns 422 IDEMPOTENCY_MISMATCH. See Idempotency.

5. Check your balance

GET /v3/fx/balances returns a Balance per currency. The USDC you just bought should show up in availableBalance. In v2 the trade response bundled balances; in v3 you fetch them separately.
cURL
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. Your own balances will differ from your trade history. 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, real signRequest and all — mint a fresh JWT and signature per call, 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.

Trade Settlement

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.