CorebanqCorebanq Developer Docs
FX

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.

oer ships as disabled by default in sample config. Enable it explicitly only after configuring fx.providers.oer.app_id.

Features:

  • USD default base - Leaves the OER base query parameter empty unless fx.providers.oer.base is configured.
  • Optional base override - Supports configured base currencies such as CHF when the OER plan allows non-USD bases.
  • MID-only persistence - Stores validated market MID rows and lets existing FX consumers derive BUY / 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: 0

Currency 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).

cmc ships as disabled by default in sample config. Enable it explicitly only after configuring fx.providers.cmc.api_key (or fx.providers.cmc.mock=true).

Features:

  • Canonical ID resolution - Resolves symbols through /v1/cryptocurrency/map before 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 MID rows and lets existing FX consumers derive BUY / SELL
  • Inverse pair generation - Automatically persists both CRYPTO/FIAT and FIAT/CRYPTO pairs
  • Mock-mode support - When fx.mock or fx.providers.cmc.mock is enabled, derives deterministic CoinMarketCap MID pairs from fx.default_rates and skips external HTTP/API-key requirements
  • Network-aware symbol normalization - Maps currencies such as USDCERC20, USDCTRC20, USDCMATIC, and USDCBASE to the canonical USDC quote symbol while preserving the original currency codes in stored pairs
  • Compression-aware HTTP - Accepts CoinMarketCap's recommended compressed responses and transparently decodes gzip / deflate payloads before JSON parsing
  • Exponential retry/backoff - Retries transient 429 and 5xx upstream 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_slug overrides for deterministic symbol-to-asset mapping when operators want to pin a specific CoinMarketCap asset
  • Fetches latest quotes from /v3/cryptocurrency/quotes/latest
  • Persists MID pairs with source CMC
  • 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: 1

CRP (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: 30

Bidirectional 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 skipped

Ticker 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")       # Bitcoin

Before deduplication:

Clean crypto tickers: [USDC, USDC, USDC, USDC, BTC] # 5 requests

After 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; use amount_unit=major for decimal.
  • base - Base currency code (e.g., "EUR"). Falls back to convert.default_base AppConfig entry when omitted.
  • target - Target currency code (e.g., "USD"). Falls back to convert.default_target AppConfig 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 to convert.default_type AppConfig 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 the amount parameter: minor (default) or major.
  • 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.

ParameterAppConfig pathValue format
baseconvert.default_baseCurrency object {"code":"EUR","precision":2} — precision is injected into the conversion context
targetconvert.default_targetSame currency object format
typeconvert.default_typePlain string, e.g. "BUY"
fx_tariff_idconvert.default_tariff_idPlain 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: 0
  • anchor: business_date anchors missing date values to the configured business timezone.
  • max_lookback_days: 0 keeps lookup strict on the anchored/requested business date.
  • max_lookback_days counts prior calendar dates from that anchor date; weekend/holiday rows remain eligible when providers store them.
  • max_lookback_days: 2 is 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.date in the response contains that actual resolved business date.

Tariff Fallback Precedence

The tariff used for fee calculation is resolved in this order:

  1. fx_tariff_id query param — highest priority; used directly if present.
  2. customer_id → customer's assigned tariff — when customer_id is provided and the customer has an FX tariff assigned. Customer-specific pricing wins over the system default.
  3. convert.default_tariff_id AppConfig — operator-configured system default UUID, applied by the service when the prior steps yield nothing.
  4. Marked default tariff — the active fx.tariffs row with is_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 accept is_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
}
FieldTypeDescription
amountCcyAmtWithPrecisionConverted amount in target currency (minor units)
total_feeCcyAmtWithPrecisionTotal fee in base currency (minor units)
mid_ratenumberMid-market exchange rate before spread
fee_rangeobjectMatched fee range (fee values in major units)
currency_pairobjectExchange 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_id path parameter required — customer_id is an optional query param; base, target, type, and fx_tariff_id all have AppConfig defaults so the endpoint can be called with only amount
  • 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) use CcyAmtWithPrecision format (minor units) instead of plain numbers
  • Supports indicative=true mode for rate previews without fees
  • Includes applied_rule_ids array showing all spread rules applied
  • Includes fallback_tariff_used and fallback_tariff_id when fallback tariff resolution occurs
  • Supports source parameter to specify rate provider
  • Supports rate_type (BUY/SELL/MID, default MID) for rate lookup, separate from type (transaction type for fee-range matching)
  • Uses the FX module's business-date lookup policy, so currency_pair.date may 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 fee
  • percentage: Percentage of transaction amount
  • greater: Greater of fixed or percentage
  • lesser: Lesser of fixed or percentage
  • sum: 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:

  1. Narrow down by date ranges - Only fee ranges where date_start minutes>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: 5

Moderate 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: 2

Fine-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: 90

Runtime 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

FeatureTime-BasedInterval-Based
Use CaseTraditional forex (SNB, EB, CC)Volatile assets (Crypto)
Update FrequencyOnce daily at specific timeContinuous at regular intervals
Configurationupdate_hour, update_minuteupdate_interval_*
Timezone Support✅ Supports timezone settings❌ Uses elapsed time
First UpdateNext scheduled time1 minute after startup
Runtime Changes❌ Requires restart✅ Dynamic interval updates
Best ForStable markets, daily updatesHigh-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: 2

Mixed 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: 30

Dependencies

  • common/errs - Error handling
  • common/auth - Authentication
  • common/logger - Logging functionality
  • common/rbac - Role-based access control
  • common/utils - Utility functions
  • connectors/ram - Caching functionality
  • modules/currencies - Currency management
  • modules/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

CodeDescription
fx_m.exchange_rate_not_foundExchange rate not found
fx_m.empty_default_ratesNo default exchange rates configured
fx_m.invalid_base_currencyInvalid base currency provided
fx_m.invalid_target_currencyInvalid target currency provided
fx_m.invalid_rateInvalid rate value
fx_m.invalid_typeInvalid rate type
fx_m.invalid_sourceInvalid rate source
fx_m.failed_to_prepare_fetchFailed to prepare request to provider
fx_m.failed_to_fetchFailed to fetch rates from provider
fx_m.failed_to_read_responseFailed to read provider response
fx_m.currency_codes_requiredBase and target currency codes are required
fx_m.fetching_exchange_rate_failedProvider returned error while fetching rate
fx_m.failed_to_fetch_rateFailed to fetch rate from database
fx_m.invalid_rate_sourceInvalid rate source specified

FX Tariff Errors

CodeDescription
fx_m.fx_tariff_not_foundFX tariff not found
fx_m.fx_tariff_already_existsFX tariff with this name already exists
fx_m.invalid_fx_tariff_dataInvalid FX tariff data provided
fx_m.insufficient_rightsInsufficient permissions

Fee Range Errors

CodeDescription
fx_m.fee_range_not_foundFee range not found
fx_m.invalid_fee_rangeInvalid fee range data
fx_m.overlapping_rangesFee ranges overlap
fx_m.invalid_date_rangeInvalid date range
fx_m.invalid_calculation_methodInvalid fee calculation method

Spread Rule Errors (v2)

CodeDescription
fx_m.fx_spread_not_foundSpread rule not found
fx_m.fx_spread_invalid_payloadInvalid spread rule data provided
fx_m.fx_spread_overlapSpread rules overlap in time range
fx_m.fx_spread_resolver_failureFailed 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

CodeDescription
fx_m.exchange_rate_not_foundExchange rate not found
fx_m.empty_default_ratesNo default exchange rates configured
fx_m.invalid_base_currencyInvalid base currency provided
fx_m.invalid_target_currencyInvalid target currency provided
fx_m.invalid_rateInvalid rate value
fx_m.invalid_typeInvalid rate type
fx_m.invalid_sourceInvalid rate source
fx_m.failed_to_prepare_fetchFailed to prepare request to provider
fx_m.failed_to_fetchFailed to fetch rates from provider
fx_m.failed_to_read_responseFailed to read provider response
fx_m.currency_codes_requiredBase and target currency codes are required
fx_m.fetching_exchange_rate_failedProvider returned error while fetching rate
fx_m.failed_to_fetch_rateFailed to fetch rate from database
fx_m.invalid_rate_sourceInvalid rate source specified

On this page