If you need official exchange rates, central banks are the primary source, and many of them publish without an API key. They do not publish them the same way, though. This post covers three endpoints we use, checked with curl on 5 November 2026, and the traps that cost us time while building an open dataset of 12 months of central bank rates.
The dataset is CC BY 4.0 and is on Zenodo and GitHub. It holds two CSV files (snapshot 3 November 2026): the tenge against the US dollar, euro, rouble and yuan, and the dollar and euro priced in 16 national currencies.
1. ECB: SDMX API, one trap in the query
The European Central Bank serves reference rates through its Data Portal API. The series key for US dollars per euro is EXR/D.USD.EUR.SP00.A:
curl -s "https://data-api.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?format=csvdata&startPeriod=2026-11-02&endPeriod=2026-11-03" Add &detail=dataonly to drop the metadata columns and you get a compact CSV:
KEY,FREQ,CURRENCY,CURRENCY_DENOM,EXR_TYPE,EXR_SUFFIX,TIME_PERIOD,OBS_VALUE EXR.D.USD.EUR.SP00.A,D,USD,EUR,SP00,A,2026-11-02,1.1216 EXR.D.USD.EUR.SP00.A,D,USD,EUR,SP00,A,2026-11-03,1.1179 Gotcha: check for an empty body. One of our first requests with detail=dataonly came back empty, and an identical retry minutes later returned the rows above. A collector should treat an empty 200 as an error and retry, not as "no data".
Gotcha: the quote direction. The ECB publishes units of foreign currency per 1 euro, so 1.1179 above means 1 euro = 1.1179 US dollars. If you want euros per dollar, you invert: 1 / 1.1179. We store the inverted value and say so in a note column, so nobody reads 0.8945 as an ECB figure.
Our file has the dollar at 1.1514 per euro on 2025-11-03 and 1.1179 on 2026-11-03, a low of 0.835143 euros per dollar on 2026-01-28 and a high of 0.898473 on 2026-10-29 (ECB reference rates).
2. NBP (Poland): clean JSON
The Narodowy Bank Polski API is the easiest of the three:
curl -s "https://api.nbp.pl/api/exchangerates/rates/a/usd/2026-11-03/?format=json" {"table":"A","currency":"dolar amerykański","code":"USD","rates":[{"no":"214/A/NBP/2026","effectiveDate":"2026-11-03","mid":3.9526}]} The mid field is the average rate from table A. Each response carries a bulletin number and an effectiveDate. Store both, since they let you prove later which publication a number came from.
Gotcha: no rate on weekends and holidays. Requesting a date with no publication (we tried Sunday 2026-11-01) returns HTTP 404, not the previous day's rate. Walk back day by day, or request a range and take the last element.
3. National Bank of Kazakhstan: RSS-style XML
The National Bank of Kazakhstan offers its official rates as XML, per date:
curl -s "https://nationalbank.kz/rss/get_rates.cfm?fdate=03.11.2026" Each <item> holds title (the currency code), description (the rate), quant (the nominal) and change. For USD on 3 November 2026, description was 453.64.
import re, subprocess xml = subprocess.run( ["curl", "-s", "https://nationalbank.kz/rss/get_rates.cfm?fdate=03.11.2026"], capture_output=True, text=True, check=True).stdout m = re.search(r"<title>USD</title>\s*<description>([\d.]+)</description>", xml) print(float(m.group(1))) # 453.64 (For production use a real XML parser; the regex keeps the example short.)
Gotchas that apply here:
- The date format is
DD.MM.YYYY, not ISO. - The rate is for a calendar day, including weekends. Unlike the ECB or NBP, the series has a value every day (we got 453.87 for Sunday 2026-11-01), so you do not need the walk-back logic.
- The
quantfield matters. Some currencies are quoted per 10, 100 or more units (on 3 November 2026: HUF per 10, KRW per 100, VND per 1000). Divide byquantor your cross rates will be off by an order of magnitude. - The feed announces rates ahead. The RSS feed of current rates (
https://nationalbank.kz/rss/rates_all.xml) can carry a date that is tomorrow: on the evening of 5 November 2026 everypubDatein it read06.11.2026. Do not label a row "latest" without checking its date against today's.
In our file the tenge went from 530.47 per dollar on 2025-11-03 to 453.64 on 2026-11-03 (a low of 439.37 on 2026-09-30), and from 613.75 to 508.17 per euro over the same period (National Bank of Kazakhstan). Computed from those figures, that is -14.5% against the dollar and -17.2% against the euro. Both are central bank reference rates, not what a bank or an exchange office would give you.
Gotchas that are not about one API
Rates are not comparable across banks. Central banks use different methods: an official rate for the date, a weighted bank average, a central rate, an indicative rate. Compare changes over time within one source, not levels between countries. We say this in the dataset README because it is the most likely misuse.
Different units, different dates. The Danish central bank publishes kroner per 100 units, and we convert to 1 unit. Dates differ by a day or two between currencies, since each bank skips weekends and holidays on its own calendar.
Some sources cannot be collected automatically. We left out the Philippine peso, because the central bank's website does not allow automated collection, and the Uzbek and Peruvian som and sol, whose central bank sites did not answer automated requests on the day of the snapshot. Dropping them and documenting why was better than patching them from a secondary source.
Keep the source URL in every row. Each row of our CSV has source_url, retrieved_at and central_bank. Someone who doubts a number can click through, and so can you in six months.
Use the data
- Dataset: doi.org/10.5281/zenodo.23933050, also on GitHub. CC BY 4.0, credit "Tasamira".
- The tenge study with all tables (in Russian): Курс тенге за год.
- The euro and dollar study (in Spanish): Evolución del euro y el dólar en 12 meses.
If you find a rate that does not match its central bank source, open an issue on the GitHub repository and we will re-check it against the source.
Tasamira data team

