FxRateLoad
Load the customer's own exchange rates (the ones it closes its books with) from an ERP. The reports convert invoice amounts with them; where the customer has no rate for a currency, the reports use Nuntiq's platform rates for that currency. The customer's rate wins, also when it is for another period (a monthly rate beats a platform day rate).
Use the get_fx_rates entrypoint. Its job token may read and write exchange
rates and the delta state, nothing else.
Import
from lib.objects.fx_rate import FxRateLoad
How a rate is expressed
Against the tenant's base currency: 1 base = per_base units of the currency, for a period:
period_type | period_start | rate_type |
|---|---|---|
day (default) | the day | always close |
week | the Monday | average or close |
month | the 1st | average or close |
Day rates also build the week and month average and closing rates of their
periods (source derived), unless you load a week or month rate yourself.
Loading a rate for an existing currency, period and rate type replaces it.
A rate can also be given as a pair: 1 from_currency = rate to_currency
on date, when the base currency is one of the two. Any other pair is
refused. Most ERPs publish pairs against the company currency, so this is
usually the easiest shape.
Methods
| Method | What it does |
|---|---|
base_currency() | The tenant's base currency, e.g. 'USD' |
new() | Queue a rate against the base (currency_code, period_type, period_start, rate_type, per_base) |
new_pair(from, to, rate, date) | Queue a day rate given as a pair |
save_all() | Send the queued rates, 5000 per request. Each request is all or nothing: one rate that is not valid fails that request (400 with the errors). Returns the number saved |
get(page=1, page_size=None, **filters) / get_all(**filters) | Read the tenant's rates, newest period first. Filters: currency, period_type, from, to (period starts). from is a Python keyword, so pass it as **{'from': '2026-09-01'} |
delete_period(currency, period_start, period_type=None, rate_type=None) | Delete one period's rates of a currency |
delete_range(currency, date_from, date_to, period_type=None, rate_type=None) | Delete every period start in a range, e.g. a month of day rates you restate. Week and month rates derived from deleted days are rebuilt from the days left |
effective(currencies, date=None, target=None, rate_type='average') | The rate the reports use per currency on a date, and its source: tenant, platform, mixed (yours for one currency, the platform's for the other), same or fallback_1 (no rate: converted at 1 and flagged). average is what spend reports use, close what open-invoice reports (aging, accrual) use |
The tenant's rates have no external id: the key is currency, period type,
period start and rate type. There is no full_load (nothing is removed
because it is missing from a load); restate with delete_range() first.
Example
from datetime import date
from lib.objects.fx_rate import FxRateLoad
def run(context):
load = FxRateLoad(context)
base = load.base_currency()
# 1 EUR = 1.0855 USD on 30 September (pair, base on one side)
load.new_pair('EUR', base, 1.0855, '2026-09-30')
# the September average for GBP, against the base
r = load.new()
r.currency_code = 'GBP'
r.period_type = 'month'
r.period_start = '2026-09-01'
r.rate_type = 'average'
r.per_base = 0.7461 # 1 USD = 0.7461 GBP
load.save_all()
for row in load.effective(['EUR', 'GBP'], date='2026-09-30'):
context.logger.info(f"1 {row['currency_code']} = {row['rate']} {base} ({row['source']})")
A full entrypoint with a delta cursor and a restate option is in
connectors/get_fx_rates.py.
Local fixtures
fixtures/fx_rate.json: the tenant's rates.fixtures/fx_settings.json: one row withbase_currencyandfx_period_type(the period the reports use: day, week or month).fixtures/platform_fx_rate.json: a few platform rates (per_usd), soeffective()can show theplatformandmixedsources locally.
Menu option r restores them from fixtures/_seed/.