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/quote→POST /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.
| Pair | Rate (approximate) |
|---|---|
| USD to EUR | 0.924 |
| EUR to USD | 1.082 |
| USD to GBP | 0.792 |
| GBP to USD | 1.263 |
| EUR to GBP | 0.858 |
| GBP to EUR | 1.166 |
Spread: 50 bps. Fee: 0.5% of the from_amount.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
unsupported_pair | 400 | No rate available for the requested currency pair. |
quote_expired | 422 | Quote TTL has passed. |
quote_already_consumed | 409 | Quote has already been executed. |
quote_not_found | 404 | Quote UUID not found. |