Description
Overview
The FX package provides functionality for managing currency exchange rates, FX tariffs, and fee structures. It supports multiple exchange rate sources (Swiss National Bank, European Bank, Currency Cloud, CoinMarketCap, CRP, Open Exchange Rates) and includes a comprehensive tariff management system.
Key Features
- Multiple rate providers - SNB, EB, CC, CoinMarketCap, CRP, and Open Exchange Rates integrations
- Dual scheduling modes - Time-based (daily) and interval-based (frequent) updates
- Crypto support - CoinMarketCap as the primary scheduled crypto feed, with CRP available as a config-driven rollback option
- Runtime configuration - Dynamic interval adjustments without restarts
- Comprehensive tariff system - Fee structures and customer-specific rates
Exchange Rate Sources
Swiss National Bank (SNB)
The SNB integration fetches official exchange rates from the Swiss National Bank. These rates use CHF (Swiss Franc) as the base currency.
Features:
- Automatic daily rate updates
- Support for all major currencies against CHF
- Both direct (CHF to foreign currency) and inverse (foreign currency to CHF) rates
- BID and ASK rates calculation
Implementation Details:
- Uses the SNB data API (
https://data.snb.ch/api/cube/DEVKUM/data/json) - Rates are cached for 24 hours to minimize API calls
- Automatically retries on transient failures
- Maintains a history of rates for historical conversion needs
European Bank (EB)
Official rates from the European Central Bank in EUR. Imported rows use source = ECB on currency_pairs (legacy display name European Central Bank is mapped at query time).
Open Exchange Rates (OER)
Open Exchange Rates imports fiat market rates through https://openexchangerates.org/api/latest.json. Imported rows use source = OER on currency_pairs.
oerships as disabled by default in sample config. Enable it explicitly only after configuringfx.providers.oer.app_id.
Features:
- USD default base - Leaves the OER
basequery parameter empty unlessfx.providers.oer.baseis configured. - Optional base override - Supports configured base currencies such as
CHFwhen the OER plan allows non-USD bases. - MID-only persistence - Stores validated market
MIDrows and lets existing FX consumers deriveBUY/SELL. - Inverse and cross-pair generation - Persists direct, inverse, and cross-currency pairs for active currencies returned by OER.
- Retry/backoff controls - Uses config-driven timeout, retry count, and retry delay values.
- Safe scheduler gate - Startup, scheduler, and OPS imports skip OER when it is enabled without an
app_id.
Scheduling Configuration:
oer:
enabled: false # explicit opt-in until app_id is configured
schedule_mode: time
api_base_url: https://openexchangerates.org/api
app_id: ${FX_OER_APP_ID:-}
base: "" # optional; omit to use OER's default USD base
timeout_seconds: 30
max_retries: 3
retry_delay_seconds: 300
prettyprint: true
show_alternative: true
update_hour: 3
update_minute: 0Currency Cloud (CC)
Commercial rates from Currency Cloud with more frequent updates. Persisted currency_pairs.source is CC.
CoinMarketCap (CMC)
CoinMarketCap is the primary scheduled crypto-price provider for FX. It resolves active crypto assets to canonical CoinMarketCap IDs first and then fetches latest quotes against a configured fiat currency (typically EUR).
cmcships as disabled by default in sample config. Enable it explicitly only after configuringfx.providers.cmc.api_key(orfx.providers.cmc.mock=true).
Features:
- Canonical ID resolution - Resolves symbols through
/v1/cryptocurrency/mapbefore requesting quotes - Conservative ambiguity handling - Prefers the unique top-ranked canonical asset when CoinMarketCap returns low-ranked bridge wrappers for the same symbol, while leaving genuinely ambiguous collisions unresolved
- MID-only persistence - Stores validated market
MIDrows and lets existing FX consumers deriveBUY/SELL - Inverse pair generation - Automatically persists both
CRYPTO/FIATandFIAT/CRYPTOpairs - Mock-mode support - When
fx.mockorfx.providers.cmc.mockis enabled, derives deterministic CoinMarketCapMIDpairs fromfx.default_ratesand skips external HTTP/API-key requirements - Network-aware symbol normalization - Maps currencies such as
USDCERC20,USDCTRC20,USDCMATIC, andUSDCBASEto the canonicalUSDCquote symbol while preserving the original currency codes in stored pairs - Compression-aware HTTP - Accepts CoinMarketCap's recommended compressed responses and transparently decodes
gzip/deflatepayloads before JSON parsing - Exponential retry/backoff - Retries transient
429and5xxupstream failures with config-driven retry bounds and exponential delays - Append-only history - Partial quote gaps never delete previous rows, preserving last known good prices
Implementation Details:
- Uses backend-only CoinMarketCap Pro API authentication with
X-CMC_PRO_API_KEY - Fetches canonical asset IDs from
/v1/cryptocurrency/map - Supports optional
currencies.metadata.cmc_id/currencies.metadata.cmc_slugoverrides for deterministic symbol-to-asset mapping when operators want to pin a specific CoinMarketCap asset - Fetches latest quotes from
/v3/cryptocurrency/quotes/latest - Persists
MIDpairs with sourceCMC - Supports interval-based scheduling to match CoinMarketCap's minute-level market updates
Scheduling Configuration:
cmc:
enabled: false # explicit opt-in until api_key or mock mode is configured
mock: false
schedule_mode: interval
api_base_url: https://pro-api.coinmarketcap.com
api_key: ${FX_CMC_API_KEY:-}
convert_currency: EUR
timeout_seconds: 15
max_retries: 2
retry_delay_seconds: 2
update_interval_minutes: 1CRP (Crypto Payment Provider)
The CRP integration fetches indicative exchange rates from the CRP service via gRPC. These rates support both crypto-to-fiat and fiat-to-crypto currency pairs and remain available as a config-driven rollback path.
Features:
- Bidirectional rate support - Fetches both crypto-to-fiat and fiat-to-crypto rates
- Interval-based scheduling - Supports frequent updates for volatile crypto markets
- Flexible update intervals - Configurable in seconds, minutes, or hours
- Runtime interval updates - Change update frequency without service restart
- Automatic ticker deduplication - Handles multiple networks for same token (e.g., USDC on ERC20, TRC20, MATIC)
- Comprehensive coverage - Maximum rate pairs by requesting both directions
- Uses currency seed data from the database
- Stores MID rates only (as provided by CRP service)
- Handles both crypto and fiat currencies from database
Implementation Details:
- Uses gRPC client for communication with CRP service
- Default interval mode - Updates every 30 minutes (configurable)
- Smart ticker extraction - Deduplicates multi-network tokens automatically
- Dual API calls - Requests both crypto→fiat and fiat→crypto rates
- Extracts clean crypto tickers from database (e.g., "USDCERC20" → "USDC")
- Supports metadata.ticker field for custom ticker mapping
- CRP service returns only MID rates (mid-market rates)
- Rates are cached between updates to minimize API calls
- Automatically processes all active currencies from the database
- Maintains a history of rates for historical conversion needs
Scheduling Configuration:
# Crypto rates update every 30 minutes
crp:
enabled: true
schedule_mode: interval
update_interval_minutes: 30Bidirectional Rate Fetching:
CRP service supports both directions of currency conversion, providing comprehensive coverage:
API Request 1 (Crypto → Fiat):
{
"from_currencies": ["BTC", "USDC", "ETH"],
"to_currencies": ["USD", "EUR", "CHF"]
}API Request 2 (Fiat → Crypto):
{
"from_currencies": ["USD", "EUR", "CHF"],
"to_currencies": ["BTC", "USDC", "ETH"]
}Combined Response Processing:
Received 9 crypto-to-fiat rates from CRP
Received 9 fiat-to-crypto rates from CRP
Total rates received from CRP: 18
Processed 18 CRP rates: 9 crypto-to-fiat, 9 fiat-to-crypto, 0 skippedTicker Deduplication Example:
When you have multiple networks for the same token in your database:
Database currencies:
- USDCERC20 (metadata.ticker: "USDC") # Ethereum network
- USDCTRC20 (metadata.ticker: "USDC") # Tron network
- USDCMATIC (metadata.ticker: "USDC") # Polygon network
- USDCBASE (metadata.ticker: "USDC") # Base network
- BTC (metadata.ticker: "BTC") # BitcoinBefore deduplication:
Clean crypto tickers: [USDC, USDC, USDC, USDC, BTC] # 5 requestsAfter deduplication:
Unique crypto tickers: [USDC, BTC] # 2 requests
Skipped duplicate crypto ticker 'USDC' from currency 'USDCTRC20'
Skipped duplicate crypto ticker 'USDC' from currency 'USDCMATIC'
Skipped duplicate crypto ticker 'USDC' from currency 'USDCBASE'API Endpoints
Exchange Rates
GET /v1/fx/rates
Get available exchange rates for a specific date.
Query Parameters
base- Base currency code (e.g., "EUR")target- Target currency code (e.g., "USD")date- Rate business/value date (YYYY-MM-DD). When omitted, FX anchors lookup to the current business date in the configured timezone.type- Rate type (BUY/SELL/MID)sort- Comma-separated sort fields such as-date,-imported_at,source
date is a value date, not an intraday timestamp. When you need the real import order for multiple rates from the same day, sort by imported_at (or include it as a tie-breaker after date).
When FX lookback is enabled, the response date shows the actual resolved business date of the returned rate, which can be earlier than the originally requested or defaulted anchor date.
rate_at is the original provider quote timestamp when the upstream source exposes it. For day-level providers that do not expose an intraday quote timestamp, the API returns the start of the business day in UTC (00:00:00.000Z) derived from the rate date, so the field is always present and never null.
rate_at and imported_at are serialized as UTC RFC3339 / ISO 8601 timestamps in API responses. rate_at uses millisecond precision (for example, 2026-04-23T13:58:44.000Z).
Response
{
"rates": [
{
"base_currency": "EUR",
"target_currency": "USD",
"rate": "1.0923",
"date": "2024-03-20T00:00:00.000Z",
"type": "SELL",
"source": "european_bank",
"rate_at": "2024-03-20T00:00:00.000Z",
"imported_at": "2024-03-20T10:00:00Z"
},
{
"base_currency": "EUR",
"target_currency": "USD",
"rate": "1.1023",
"date": "2024-03-20T00:00:00.000Z",
"type": "BUY",
"source": "european_bank",
"rate_at": "2024-03-20T00:00:00.000Z",
"imported_at": "2024-03-20T10:00:10Z"
},
{
"base_currency": "EUR",
"target_currency": "USD",
"rate": "1.0973",
"date": "2024-03-20T00:00:00.000Z",
"type": "MID",
"source": "european_bank",
"rate_at": "2024-03-20T00:00:00.000Z",
"imported_at": "2024-03-20T10:00:20Z"
}
]
}GET /v1/fx/rates/convert/{customer_id}
Convert amount between currencies with applied tariffs using customer's FX tariff.
:::note Legacy endpoint
V1 returns monetary amounts (amount, total_fee, fee fields) as plain numbers in major units. For minor-unit CcyAmtWithPrecision responses, use the V2 endpoint instead.
:::
Query Parameters
amount- Amount to convert (numeric, in major units, required)base- Base currency code (e.g., "EUR", required)target- Target currency code (e.g., "USD", required)type- Rate type (BUY/SELL/MID)indicative- Return amount without fee (bool)
Response
{
"currency": "USD",
"amount": 109.23,
"total_fee": 1.50,
"fee_range": {
"id": "uuid",
"fx_tariff_id": "uuid",
"min_range": 0,
"max_range": 1000,
"fixed_fee": 1.50,
"percent_fee": 0.5,
"min_fee": 1.00,
"max_fee": 10.00,
"method": "greater",
"date_start": "2024-01-01T00:00:00Z",
"date_end": "2024-12-31T23:59:59Z"
},
"tariff": {
"id": "uuid",
"name": "Standard FX Tariff",
"description": "Standard foreign exchange tariff for retail customers",
"active": true
},
"currency_pair": {
"id": "uuid",
"base_currency": "EUR",
"target_currency": "USD",
"rate": 1.0923,
"type": "SELL",
"date": "2024-03-20",
"source": "ECB",
"imported_at": "2024-03-20T10:00:00Z"
},
"spread_rule_id": "uuid",
"applied_rule_ids": ["uuid1", "uuid2"],
"fallback_tariff_used": false,
"fallback_tariff_id": null
}GET /v2/fx/rates/convert
Convert an amount between currencies. Only amount is strictly required — all other parameters fall back to operator-configured defaults in AppConfig when omitted. When customer_id is also absent, the endpoint resolves a system default tariff automatically. See AppConfig defaults and Tariff fallback precedence below.
Query Parameters
amount- Amount to convert (required). Minor units by default; useamount_unit=majorfor decimal.base- Base currency code (e.g., "EUR"). Falls back toconvert.default_baseAppConfig entry when omitted.target- Target currency code (e.g., "USD"). Falls back toconvert.default_targetAppConfig entry when omitted.customer_id- Customer UUID. When provided, the customer's assigned FX tariff is used as a fallback if no tariff is resolved from earlier steps.fx_tariff_id- FX tariff UUID. See Tariff fallback precedence.type- Transaction type for fee-range matching (BUY/SELL). Falls back toconvert.default_typeAppConfig when omitted.rate_type- Rate type for the exchange-rate quote lookup (BUY/SELL/MID, default: MID).source- Rate source/provider (e.g., "SNB", "EB", "CMC", "CRP", "OER").date- Lookup business date in YYYY-MM-DD format. When omitted, FX anchors lookup to the current business date in the configured timezone.indicative- Return indicative rate without fees (bool, default: false).amount_unit- Unit of theamountparameter:minor(default) ormajor.fee_ccy- Return fees in this currency instead of the base currency.
AppConfig Defaults
When a query parameter is absent, the handler reads its default from AppConfig (fx module). Configure these via the Configurator UI.
| Parameter | AppConfig path | Value format |
|---|---|---|
base | convert.default_base | Currency object {"code":"EUR","precision":2} — precision is injected into the conversion context |
target | convert.default_target | Same currency object format |
type | convert.default_type | Plain string, e.g. "BUY" |
fx_tariff_id | convert.default_tariff_id | Plain UUID string |
Query parameters always take precedence over AppConfig entries.
FX Rate Lookup Policy
FX owns the business-date lookup policy used by conversion, transfer quote pricing, and executor-side system lookups.
fx:
convert:
default_rate_lookup_policy:
anchor: business_date
max_lookback_days: 0anchor: business_dateanchors missingdatevalues to the configured business timezone.max_lookback_days: 0keeps lookup strict on the anchored/requested business date.max_lookback_dayscounts prior calendar dates from that anchor date; weekend/holiday rows remain eligible when providers store them.max_lookback_days: 2is an example fallback mode that lets FX reuse the most recent stored rate found within that configured calendar-day window.- When lookback resolves an earlier row,
currency_pair.datein the response contains that actual resolved business date.
Tariff Fallback Precedence
The tariff used for fee calculation is resolved in this order:
fx_tariff_idquery param — highest priority; used directly if present.customer_id→ customer's assigned tariff — whencustomer_idis provided and the customer has an FX tariff assigned. Customer-specific pricing wins over the system default.convert.default_tariff_idAppConfig — operator-configured system default UUID, applied by the service when the prior steps yield nothing.- Marked default tariff — the active
fx.tariffsrow withis_default = TRUE; last-resort DB lookup when no AppConfig default UUID is set or it cannot be resolved. A partial unique index enforces at most one active default. The flag is seeded by migration on the existing default tariff row and is currently read-only via the API; tariff create/update endpoints do not acceptis_default.
When no tariff is resolved at all, the conversion proceeds without fees (spread-only). The AppConfig default is intentionally applied after the customer lookup so that customer-bearing requests always honour the customer's assigned tariff.
Response
Monetary amounts (amount, total_fee) are returned in CcyAmtWithPrecision format — minor/atomic units with currency code and precision metadata. Exchange rates remain numeric.
{
"amount": {
"amount": "10923",
"currency": "USD",
"precision": 2
},
"total_fee": {
"amount": "150",
"currency": "EUR",
"precision": 2
},
"fee_range": {
"id": "uuid",
"fx_tariff_id": "uuid",
"min_range": 0,
"max_range": 1000,
"fixed_fee": 1.50,
"percent_fee": 0.5,
"min_fee": 1.00,
"max_fee": 10.00,
"method": "greater",
"date_start": "2024-01-01T00:00:00Z",
"date_end": "2024-12-31T23:59:59Z"
},
"tariff": {
"id": "uuid",
"name": "Standard FX Tariff",
"description": "Standard foreign exchange tariff for retail customers",
"active": true,
"fallback_tariff_id": "uuid"
},
"currency_pair": {
"id": "uuid",
"base_currency": "EUR",
"target_currency": "USD",
"rate": 1.0923,
"type": "SELL",
"date": "2024-03-20",
"source": "SNB",
"imported_at": "2024-03-20T10:00:00Z"
},
"mid_rate": 1.0923,
"spread_rule_id": "uuid",
"applied_rule_ids": ["uuid1", "uuid2"],
"fallback_tariff_used": false,
"fallback_tariff_id": null
}| Field | Type | Description |
|---|---|---|
amount | CcyAmtWithPrecision | Converted amount in target currency (minor units) |
total_fee | CcyAmtWithPrecision | Total fee in base currency (minor units) |
mid_rate | number | Mid-market exchange rate before spread |
fee_range | object | Matched fee range (fee values in major units) |
currency_pair | object | Exchange rate pair used for conversion |
:::tip Converting to major units
To convert any CcyAmtWithPrecision value to major units: major = parseInt(amount) / 10^precision.
For example, {"amount": "10923", "currency": "USD", "precision": 2} = 109.23 USD.
:::
Key Differences from v1:
- No
customer_idpath parameter required —customer_idis an optional query param;base,target,type, andfx_tariff_idall have AppConfig defaults so the endpoint can be called with onlyamount - Tariff resolved via four-step fallback chain:
fx_tariff_id→ customer tariff → AppConfig default → marked-default tariff (is_default = TRUE); see Tariff fallback precedence - Monetary amounts (
amount,total_fee) useCcyAmtWithPrecisionformat (minor units) instead of plain numbers - Supports
indicative=truemode for rate previews without fees - Includes
applied_rule_idsarray showing all spread rules applied - Includes
fallback_tariff_usedandfallback_tariff_idwhen fallback tariff resolution occurs - Supports
sourceparameter to specify rate provider - Supports
rate_type(BUY/SELL/MID, default MID) for rate lookup, separate fromtype(transaction type for fee-range matching) - Uses the FX module's business-date lookup policy, so
currency_pair.datemay resolve to an earlier stored rate date when lookback is enabled
GET /v1/fx/rates/sources
Get a distinct list of all available rate sources in the system with their dynamically determined supported methods.
Response
[
{
"source": "SNB",
"methods": ["SELL", "BUY", "MID"]
},
{
"source": "ECB",
"methods": ["MID"]
},
{
"source": "CC",
"methods": ["SELL", "BUY"]
},
{
"source": "CMC",
"methods": ["MID"]
},
{
"source": "CRP",
"methods": ["SELL", "BUY", "MID"]
},
{
"source": "OER",
"methods": ["MID"]
}
]Note: The methods array is dynamically determined from the database based on what rate types are actually available for each source.
FX Tariffs
The FX Tariffs API provides functionality for managing foreign exchange pricing and fee calculations:
- FX Tariff management (CRUD operations)
- Fee range definitions for transaction amounts
- Spread rules for BUY/SELL rate configuration (v2)
- Multi-currency support
- Currency-pair specific fees
Core Concepts
Fee Calculation Methods
fixed: Fixed amount feepercentage: Percentage of transaction amountgreater: Greater of fixed or percentagelesser: Lesser of fixed or percentagesum: Sum of fixed and percentage
Fee Range Search Priority
When resolving which fee range to apply for a transaction, the system follows a strict priority order:
- Narrow down by date ranges - Only fee ranges where
date_startminutes>hours` (if multiple are specified, the most granular unit takes precedence)
Examples
High-frequency crypto updates (every 5 minutes):
cmc:
enabled: false # set to true after configuring api_key or mock mode
mock: false
schedule_mode: interval
update_interval_minutes: 5Moderate frequency updates (every 2 hours):
cmc:
enabled: false # set to true after configuring api_key or mock mode
mock: false
schedule_mode: interval
update_interval_hours: 2Fine-tuned updates (every 90 seconds):
cmc:
enabled: false # set to true after configuring api_key or mock mode
mock: false
schedule_mode: interval
update_interval_seconds: 90Runtime Configuration
You can change CRP interval during runtime using the scheduler API:
// Update CRP interval to 15 minutes
scheduler.UpdateCRPInterval(15)
// Or set different time units
scheduler.SetCRPIntervalWithUnit(2, "hours")
scheduler.SetCRPIntervalWithUnit(300, "seconds")Scheduling Modes Comparison
| Feature | Time-Based | Interval-Based |
|---|---|---|
| Use Case | Traditional forex (SNB, EB, CC) | Volatile assets (Crypto) |
| Update Frequency | Once daily at specific time | Continuous at regular intervals |
| Configuration | update_hour, update_minute | update_interval_* |
| Timezone Support | ✅ Supports timezone settings | ❌ Uses elapsed time |
| First Update | Next scheduled time | 1 minute after startup |
| Runtime Changes | ❌ Requires restart | ✅ Dynamic interval updates |
| Best For | Stable markets, daily updates | High-frequency, volatile markets |
Troubleshooting
Common Configuration Issues
Wrong interval unit detection:
# ❌ Wrong - will use minutes priority
cmc:
update_interval_minutes: 30
update_interval_hours: 2 # Ignored
# ✅ Correct - use only one unit
cmc:
update_interval_hours: 2Mixed scheduling modes:
# ❌ Wrong - contradictory settings
cmc:
schedule_mode: interval
update_hour: 2 # Ignored in interval mode
update_interval_minutes: 30 # Used in interval mode
# ✅ Correct - consistent settings
cmc:
schedule_mode: interval
update_interval_minutes: 30Dependencies
common/errs- Error handlingcommon/auth- Authenticationcommon/logger- Logging functionalitycommon/rbac- Role-based access controlcommon/utils- Utility functionsconnectors/ram- Caching functionalitymodules/currencies- Currency managementmodules/customers- Customer management
Debug Endpoints
GET /v1/debug/fx/fetch-snb
Manually trigger fetching of the latest exchange rates from Swiss National Bank. This is useful for testing or when immediate rate updates are needed outside the scheduled updates.
Response
{
"status": "success",
"message": "SNB rates fetched successfully"
}GET /v1/debug/fx/fetch-eb
Manually trigger fetching of the latest exchange rates from European Bank.
GET /v1/debug/fx/fetch-cc
Manually trigger fetching of the latest exchange rates from Currency Cloud.
GET /v1/debug/fx/fetch-crp
Manually trigger fetching of the latest exchange rates from CRP service.
Error Handling
All errors follow a standard format:
{
"code": "error_code",
"message": "Error description"
}Error Codes
General FX Errors
| Code | Description |
|---|---|
| fx_m.exchange_rate_not_found | Exchange rate not found |
| fx_m.empty_default_rates | No default exchange rates configured |
| fx_m.invalid_base_currency | Invalid base currency provided |
| fx_m.invalid_target_currency | Invalid target currency provided |
| fx_m.invalid_rate | Invalid rate value |
| fx_m.invalid_type | Invalid rate type |
| fx_m.invalid_source | Invalid rate source |
| fx_m.failed_to_prepare_fetch | Failed to prepare request to provider |
| fx_m.failed_to_fetch | Failed to fetch rates from provider |
| fx_m.failed_to_read_response | Failed to read provider response |
| fx_m.currency_codes_required | Base and target currency codes are required |
| fx_m.fetching_exchange_rate_failed | Provider returned error while fetching rate |
| fx_m.failed_to_fetch_rate | Failed to fetch rate from database |
| fx_m.invalid_rate_source | Invalid rate source specified |
FX Tariff Errors
| Code | Description |
|---|---|
| fx_m.fx_tariff_not_found | FX tariff not found |
| fx_m.fx_tariff_already_exists | FX tariff with this name already exists |
| fx_m.invalid_fx_tariff_data | Invalid FX tariff data provided |
| fx_m.insufficient_rights | Insufficient permissions |
Fee Range Errors
| Code | Description |
|---|---|
| fx_m.fee_range_not_found | Fee range not found |
| fx_m.invalid_fee_range | Invalid fee range data |
| fx_m.overlapping_ranges | Fee ranges overlap |
| fx_m.invalid_date_range | Invalid date range |
| fx_m.invalid_calculation_method | Invalid fee calculation method |
Spread Rule Errors (v2)
| Code | Description |
|---|---|
| fx_m.fx_spread_not_found | Spread rule not found |
| fx_m.fx_spread_invalid_payload | Invalid spread rule data provided |
| fx_m.fx_spread_overlap | Spread rules overlap in time range |
| fx_m.fx_spread_resolver_failure | Failed to resolve applicable spread rules |
Environment Configuration
The FX module can be configured through the following YAML configuration settings:
# AppConfig defaults for /v2/fx/rates/convert
# Set these via the Configurator UI (fx module) to power a "default conversion page".
# Currency values use the object format {"code":"EUR","precision":2}.
# Leaving a value empty means callers must supply that parameter explicitly.
convert:
default_base: "" # fx/convert.default_base — e.g. {"code":"EUR","precision":2}
default_target: "" # fx/convert.default_target — e.g. {"code":"USDC","precision":6}
default_type: "" # fx/convert.default_type — e.g. "BUY"
default_tariff_id: "" # fx/convert.default_tariff_id — UUID of the default FX tariff
default_rate_lookup_policy:
anchor: "business_date" # fx/convert.default_rate_lookup_policy.anchor
max_lookback_days: 0 # fx/convert.default_rate_lookup_policy.max_lookback_days (prior calendar dates)
# Enable/disable mock data for development
mock: true
# Provider-specific settings
providers:
snb:
enabled: true
update_hour: 2
update_minute: 30
max_retries: 3
retry_delay_seconds: 300
eb:
enabled: true
update_hour: 16
update_minute: 0
cc:
enabled: false
update_hour: 1
update_minute: 30
cmc:
enabled: false # explicit opt-in until api_key or mock mode is configured
mock: false # Optional: derive CoinMarketCap MID pairs locally from fx.default_rates instead of calling CMC
schedule_mode: interval
api_base_url: "https://pro-api.coinmarketcap.com"
api_key: "${FX_CMC_API_KEY:-}"
convert_currency: "EUR"
timeout_seconds: 15
max_retries: 2
retry_delay_seconds: 2
update_interval_minutes: 1
crp:
enabled: false
schedule_mode: interval
update_interval_minutes: 2
oer:
enabled: false # explicit opt-in until app_id is configured
schedule_mode: time
api_base_url: "https://openexchangerates.org/api"
app_id: "${FX_OER_APP_ID:-}"
base: "" # optional; empty uses OER's default USD base
timeout_seconds: 30
max_retries: 3
retry_delay_seconds: 300
prettyprint: true
show_alternative: true
update_hour: 3
update_minute: 0
# Provider API URLs
snb_rates_url: "https://data.snb.ch/api/cube/DEVKUM/data/json"
eb_rates_url: "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"Error Codes
| Code | Description |
|---|---|
| fx_m.exchange_rate_not_found | Exchange rate not found |
| fx_m.empty_default_rates | No default exchange rates configured |
| fx_m.invalid_base_currency | Invalid base currency provided |
| fx_m.invalid_target_currency | Invalid target currency provided |
| fx_m.invalid_rate | Invalid rate value |
| fx_m.invalid_type | Invalid rate type |
| fx_m.invalid_source | Invalid rate source |
| fx_m.failed_to_prepare_fetch | Failed to prepare request to provider |
| fx_m.failed_to_fetch | Failed to fetch rates from provider |
| fx_m.failed_to_read_response | Failed to read provider response |
| fx_m.currency_codes_required | Base and target currency codes are required |
| fx_m.fetching_exchange_rate_failed | Provider returned error while fetching rate |
| fx_m.failed_to_fetch_rate | Failed to fetch rate from database |
| fx_m.invalid_rate_source | Invalid rate source specified |