Exchange rates & FX policy
When the catalog price is in one currency and the invoice in another, Kontorion converts using a stored exchange-rate snapshot. The choice of which rate to use - the rate at issue, at period start, or per segment - is controlled by an FX policy setting. Every finalized invoice line pins the rate it was converted with, so replay reproduces the original totals to the cent regardless of later policy changes.
Why this matters. Multi-currency billing introduces three kinds of risk: rate volatility between order and capture, divergent finance preferences across markets, and replay correctness when rates change after finalization. Kontorion pins three FX-rate candidates per invoice (issue, period-start, per-segment) and lets you set the active policy via settings keys, so the books are always reproducible and "what-if" rendering against alternate policies is one query away.
Snapshots and overrides
The FX surface has two kinds of records:
- Snapshots are the daily rate set, sourced from an upstream feed (typically ECB). They are read-only at the API layer - new snapshots arrive through the scheduled sync job.
- Overrides are workspace-scoped manual rates that override the snapshot for a specific currency pair and validity window. Use these when finance needs to pin a contractual rate or correct a feed glitch.
Both shapes carry source_currency and target_currency (ISO 4217). A snapshot additionally carries a decimal-string rate, a snapshot_date (the calendar day it represents, plain-date — no clock, no zone), and a source (e.g. ecb); it has no validity window. An override has no rate of its own - it pins to a snapshot_date (the day whose published rate it reuses) and carries a valid_from/valid_to activation window (TIMESTAMPTZ instants) plus an optional reason. Internally each resolved rate becomes a candidate when the invoice pipeline resolves an effective rate at finalize time.
FX policy
FX policy lives in settings under two keys, one per family of operations:
fx_policy_invoice- applies to invoice-finalize, preview, and proration operations. Defaultinvoice_issue. Accepts any of the four values below.fx_policy_reserve- applies to reserve and wallet-transfer operations. Defaultperiod_start. Forward-only:invoice_issueis forbidden (a reserve predates the invoice it backs), so this key accepts onlyperiod_start,per_segment, ordaily_snapshot.
The four policy values:
| Policy | Rate used | Best for |
|---|---|---|
invoice_issue (invoice default) | Rate active when the invoice is finalized | Simplest; minimal volatility surface. (Not valid for fx_policy_reserve.) |
period_start (reserve default) | Rate active at the billing period start | Pins all lines on the invoice to one rate captured up front. |
per_segment | Rate active at each line's segment start | Mid-period FX moves get line-level granularity. |
daily_snapshot | Daily-rotated snapshot | Operationally similar to invoice_issue but with off-hours stability. |
Switching either policy applies to future operations only. Existing invoices stay pinned to the rate that finalised them.
To support replay across policy changes, every finalized invoice records three candidate exchange-rate ids:
Invoice.issue_exchange_rate_id- theinvoice_issuecandidate.Invoice.period_start_exchange_rate_id- theperiod_startcandidate.InvoiceLine.segment_exchange_rate_id- theper_segmentcandidate (per line).
Field (on Invoice) | Candidate |
|---|---|
issue_exchange_rate_id | The invoice_issue candidate (rate at finalize) |
period_start_exchange_rate_id | The period_start candidate (rate at billing-period open) |
Reading rates
Get the latest snapshot for a currency pair:
Code
Both from and to are required (3-letter ISO codes). The response is the snapshot row that was active at request time.
List all currency-pair snapshots for a date:
Code
date is optional and accepts YYYY-MM-DD. When omitted, today (UTC) is used.
Check the sync job's freshness:
Code
Returns latest_snapshot_date and distinct_dates_last_year so you can alert on stale feeds.
Workspace overrides
Add a manual override for a currency pair:
Code
An override does not carry a free-form rate. Instead snapshot_date (a plain YYYY-MM-DD calendar day) pins the override to the published snapshot for that day - the override reuses that day's rate for the chosen currency pair. source_currency and target_currency are required 3-letter ISO 4217 codes and must differ; valid_from/valid_to define the activation window (omit valid_from to start now, omit valid_to to leave it open-ended, and valid_to must be after valid_from); reason is optional.
List, patch, or delete overrides via GET /v1/fx/overrides, PATCH /v1/fx/overrides/{id}, DELETE /v1/fx/overrides/{id}. A PATCH accepts snapshot_date, valid_from, valid_to, and reason (currency pair is immutable). Overrides are scoped to the calling workspace; they do not affect other workspaces in the same org.
Setting the FX policy
FX policy is a settings-catalog value, not a field on any other resource. Upsert it through the organization settings endpoint, keyed by fx_policy_invoice and/or fx_policy_reserve. Settings values are always strings:
Code
fx_policy_invoice accepts invoice_issue, period_start, per_segment, or daily_snapshot. fx_policy_reserve accepts the same set minus invoice_issue (forward-only).
Next steps
- Currencies - minor units, supported codes, rounding.
- Invoices - where pinned rates surface.
- Organization settings - where the
fx_policy_invoice/fx_policy_reservekeys live.