# BNR exchange rates > Daily RON reference exchange rates for roughly 35 foreign currencies, gold (XAU) and the IMF's Special Drawing Right (XDR), published each banking day by the National Bank of Romania (BNR). One row per (rate_date, currency); rate_per_unit is the RON value of a single unit of the currency, already divided by the source's multiplier so currencies compare directly. - Dataset id: `bnr-exchange-rates` - Update cadence: daily - Upstream source: https://curs.bnr.ro/nbrfxrates10days.xml - Tags: exchange-rates, currency, finance, romania, bnr, fx, macroeconomics - Current version: ingest `01M540G97KT75XXBX557HCK0NC`, snapshot 2026-10-17, generated 2026-10-17T04:04:23.799Z ## Licence and attribution - Licence: BNR Termeni și condiții — redistribution permitted with attribution (`bnr-terms`) - Licence text: https://www.bnr.ro/2281-termeni-si-conditii - Required attribution: Exchange rates: Banca Națională a României (BNR). - Notes: BNR's terms permit redistributing this information in any form and by any means, provided the source is indicated; if the data is used within a paid service, the terms specifically require that end clients be told the figures originate from BNR's own website, where they are published free of charge. If the published figures are altered in any way — converted, rounded, interpolated, recombined — the terms require that alteration to be stated explicitly; do not present a derived number as BNR's own reported rate without saying so. The XML feeds this connector reads moved from the www.bnr.ro domain to the curs.bnr.ro subdomain on 6 August 2026 — the old www.bnr.ro/nbrfxrates.xml address no longer serves them. One caveat worth stating plainly, without taking a side: curs.bnr.ro/robots.txt disallows crawling the entire subdomain ("User-agent: * / Disallow: /"), which sits in tension with BNR's own published instruction that software applications fetch these rates from this same subdomain. Reusers should be aware of that conflict and form their own judgment about it. Reproduce the attribution line verbatim anywhere you republish this data. ## Manifest - Start here for how this directory works: https://data.bunnytech.app/llms.txt - This dataset: https://data-directory.fsn1.your-objectstorage.com/public/bnr-exchange-rates/manifest.json - All datasets: https://data-directory.fsn1.your-objectstorage.com/public/index.json - Human page: https://data.bunnytech.app/datasets/bnr-exchange-rates The manifest is the contract: file URLs, row counts, column types, min/max statistics and example queries all live there. Re-read it to discover a new version. ## Fastest path — Parquet exports Immutable, physically sorted snapshot exports. No catalog fetch, best row-group pruning, safe to cache forever. Register one view per table: ```sql INSTALL httpfs; LOAD httpfs; CREATE OR REPLACE VIEW rates AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/bnr-exchange-rates/v/01M540G97KT75XXBX557HCK0NC/rates.parquet'); ``` Example queries: -- EUR/RON over the last 90 days: The date filter comes first because rate_date is the leading sort column. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); SELECT rate_date, rate_per_unit FROM datadirectory.bnr_exchange_rates.rates WHERE currency = 'EUR' AND rate_date >= current_date - INTERVAL 90 DAY ORDER BY rate_date ``` -- The currencies that moved the most this month: First vs. last available rate_per_unit so far this month, ranked by absolute percentage change. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); WITH bounds AS ( SELECT currency, arg_min(rate_per_unit, rate_date) AS first_rate, arg_max(rate_per_unit, rate_date) AS last_rate FROM datadirectory.bnr_exchange_rates.rates WHERE rate_date >= date_trunc('month', current_date) GROUP BY currency ) SELECT currency, first_rate, last_rate, round(100.0 * (last_rate - first_rate) / first_rate, 2) AS pct_change FROM bounds ORDER BY abs(pct_change) DESC LIMIT 10 ``` ## Live lake — DuckLake catalog with time travel Attach the published catalog read-only over plain https. This works in native DuckDB AND in duckdb-wasm in the browser — the reader catalog resolves its data files through public https URLs, so no credentials and no backend are involved. ```sql INSTALL ducklake; LOAD ducklake; ATTACH 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/bnr-exchange-rates/catalog.ducklake' AS bnr_exchange_rates (READ_ONLY); SELECT * FROM bnr_exchange_rates.main.rates LIMIT 5; USE bnr_exchange_rates; ``` - Latest catalog: https://data-directory.fsn1.your-objectstorage.com/catalog/bnr-exchange-rates/catalog.ducklake - This exact version: https://data-directory.fsn1.your-objectstorage.com/catalog/bnr-exchange-rates/v/01M540G97KT75XXBX557HCK0NC/catalog.ducklake (sha256 d549b111eac88bacb421de2fe4e8a34db1892abced596c93555690dab3ab5ce3) - Written by DuckDB v1.5.5 - Time travel: this ingest is snapshot 6 — e.g. `SELECT * FROM bnr_exchange_rates.rates AT (VERSION => 6)` ## Wholesale — S3 access For bulk downloads, read the bucket prefix directly over S3 with httpfs: ```sql INSTALL httpfs; LOAD httpfs; CREATE SECRET hetzner ( TYPE s3, ENDPOINT 'fsn1.your-objectstorage.com', URL_STYLE 'path', KEY_ID '', SECRET '' ); SELECT * FROM read_parquet('s3://data-directory/public/bnr-exchange-rates/v/01M540G97KT75XXBX557HCK0NC/**/*.parquet'); ``` ## Schema ### rates One row per (rate_date, currency): the RON reference rate the National Bank of Romania (BNR) published for that currency on that banking day. BNR publishes a rate once per banking day and does not revise it afterwards, so a row here never changes once written. - 160.1K rows · 836 KB · write mode `append` - Physically sorted by: `rate_date`, `currency` - Primary key: `rate_date`, `currency` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/bnr-exchange-rates/v/01M540G97KT75XXBX557HCK0NC/rates.parquet (160.1K rows) Columns: - `rate_date` DATE NOT NULL — The banking day this rate applies to — the source feed's Cube/@date — as a UTC calendar date. This is the day the rate was VALID for, not the day it was fetched. (range: "2005-01-03" … "2026-10-16") (~4770 distinct) - `currency` VARCHAR NOT NULL — ISO 4217 code of the foreign currency being priced against RON, e.g. "EUR", "USD", "JPY". BNR also publishes two non-ISO-4217 codes this way: "XAU" (one troy ounce of gold) and "XDR" (the IMF Special Drawing Right). (range: "AED" … "ZAR") (~35 distinct) - `rate` DECIMAL(12,4) NOT NULL [RON] — The figure BNR published, verbatim: how many RON equal `multiplier` units of `currency` — NOT the price of one unit. For most currencies multiplier is 1 and this already is the per-unit price, but for HUF, JPY, KRW and a few others it is priced per 100 units. Use rate_per_unit to compare currencies against each other; use this column only when you specifically want the number exactly as BNR printed it. - `multiplier` INTEGER NOT NULL [units] — How many units of `currency` the `rate` column prices — 100 for HUF, IDR, ISK, JPY, KRW; 1 for almost everything else. The source XML omits this attribute for a multiplier of 1 rather than writing it out; this column is never left null for that — an absent attribute is normalised here to the value 1, so every row states its multiplier explicitly rather than leaving a reader to assume one. - `rate_per_unit` DECIMAL(18,10) NOT NULL [RON] — RON for exactly ONE unit of `currency` (rate / multiplier). This is the number almost everyone actually wants, and the only one that is safe to compare directly across currencies — comparing raw `rate` values mixes units, since a JPY rate is priced per 100 yen and a EUR rate per 1 euro. - `publishing_date` DATE — The date BNR stamped on the whole document (Header/PublishingDate) — the Europe/Bucharest business day the feed itself was issued (BNR publishes once, around midday Bucharest time). In practice this equals the most recent rate_date carried in the same payload, but it is kept as its own column because it is a property of the document, not of any individual currency's rate. - `base_currency` VARCHAR — The currency every rate in this row is expressed against (Body/OrigCurrency in the source). Always "RON" in practice, but read from the feed on every run rather than hardcoded, in case BNR ever changes it. ## Performance notes - There are no indexes in a lakehouse. The physical sort order plus Parquet row-group min/max statistics ARE the index. Predicates on the leading sort columns prune whole row groups; everything else is a full column scan. - `rates` prunes on `rate_date` → `currency`. - `ILIKE '%needle%'` cannot prune: it decompresses that whole column. Prefer prefix predicates on the lowercased search companion columns where they exist. - Every file URL under `/v//` is immutable. Cache it as aggressively as you like. - Files are never rewritten in place — a new ingest writes a new version path. Re-read the manifest to pick up updates rather than polling the file URLs. - Pull only the columns you need. Parquet is columnar and the reader range-GETs what it reads. ## Do not - Do not scrape https://data.bunnytech.app HTML. Every number on those pages comes from the manifest above. - Do not hit the upstream source (https://curs.bnr.ro/nbrfxrates10days.xml) in bulk. This mirror exists so you do not have to. - Do not read DuckLake internal data files with a plain Parquet reader. They come with positional delete files, and ignoring those silently returns deleted rows. Use the ducklake extension, or use the published exports above. - Do not republish without the attribution line, and do not imply endorsement.