Skip to main content

FX

CashXChain provides two FX surfaces:

  • Public stateless quote (POST /v1/fx/quotes) — no auth, no persistence, suitable for price discovery.
  • Persisted quote + execute (POST /v1/fx/quotePOST /v1/fx/execute) — locked rate with a 30-second TTL. Sandbox only for now.

POST /v1/fx/quotes — Public stateless quote

No authentication required. Returns an indicative rate for display purposes. The quote is not persisted and cannot be executed.

POST /v1/fx/quotes
Content-Type: application/json
{
"from_currency": "EUR",
"to_currency": "USD",
"from_amount": "1000.00"
}

Response (200 OK)

{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"from_currency": "EUR",
"to_currency": "USD",
"from_amount": "1000.00",
"to_amount": "1082.50",
"rate": "1.0825",
"spread_bps": 50,
"source": "ecb",
"issued_at": "2026-06-08T12:00:00Z",
"expires_at": "2026-06-08T12:05:00Z"
}

Supported pairs (sandbox): USD/EUR, USD/GBP, EUR/GBP, and their inverses.


POST /v1/fx/quote — Persisted quote

Creates a rate-locked quote valid for 30 seconds. Returns a quote_id that must be passed to /v1/fx/execute to settle the conversion.

Auth: Any valid credential. Scope required for rk_ keys: fx:read.
Availability: Sandbox only. Returns 404 in production until a live FX provider is integrated.

POST /v1/fx/quote
Authorization: Bearer <token>
Content-Type: application/json
{
"from_currency": "USD",
"to_currency": "EUR",
"from_amount": "500.00"
}

Response (201 Created)

{
"quote_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"from_currency": "USD",
"from_amount": "500.00",
"to_currency": "EUR",
"to_amount": "462.15",
"rate": "0.9243",
"fee": "2.50",
"fee_currency": "USD",
"expires_at": "2026-06-08T12:00:30Z",
"expires_in_seconds": 30
}

The quote TTL is 30 seconds. After expiry, executing the quote returns 422 quote_expired.


POST /v1/fx/execute — Execute a quote

Atomically settles the FX conversion: debits the source-side balance, credits the destination-side balance, persists a transfers row with transfer_type = 'fx_conversion', and marks the quote consumed. All steps run in a single database transaction.

Auth: Any valid credential. Scope required for rk_ keys: payments:write.
Availability: Sandbox only.

POST /v1/fx/execute
Authorization: Bearer <token>
Content-Type: application/json
{
"quote_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Response (200 OK)

{
"transfer_id": "...",
"status": "completed",
"from_currency": "USD",
"from_amount": "500.00",
"to_currency": "EUR",
"to_amount": "462.15",
"rate": "0.9243",
"fee": "2.50"
}

Quote + execute flow

POST /v1/fx/quote          → receive quote_id, rate, expires_in_seconds
|
| (must execute within 30 seconds)
|
POST /v1/fx/execute → atomic balance swap + transfers row

A quote can only be executed once. Executing an already-consumed quote returns 409 quote_already_consumed. Executing an expired quote returns 422 quote_expired.


Sandbox rates

Rates in the sandbox are hardcoded for deterministic testing. They do not reflect live market rates.

PairRate (approximate)
USD to EUR0.924
EUR to USD1.082
USD to GBP0.792
GBP to USD1.263
EUR to GBP0.858
GBP to EUR1.166

Spread: 50 bps. Fee: 0.5% of the from_amount.


Error codes

CodeHTTPMeaning
unsupported_pair400No rate available for the requested currency pair.
quote_expired422Quote TTL has passed.
quote_already_consumed409Quote has already been executed.
quote_not_found404Quote UUID not found.