FX Rates Scraper: ECB Reference Rates & History
Pricing
from $0.37 / 1,000 rate scrapeds
FX Rates Scraper: ECB Reference Rates & History
Scrape foreign-exchange rates published by the European Central Bank: latest rates, historical series and time-series ranges across 30+ currencies. No key, no limits.
Pricing
from $0.37 / 1,000 rate scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
20 days ago
Last modified
Categories
Share

FX Rates Scraper pulls the European Central Bank's daily reference rates, the latest fixing, any single historical date, or a full time series, across the 30+ currencies the ECB publishes, with per-observation change and period high/low already computed.
These are the rates auditors, tax authorities and finance teams accept as the neutral source for currency conversion. This Actor reads them through the Frankfurter API, which republishes the ECB series as JSON: no proxy setup, no browser, no credentials to manage.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/fx-rates-scraper.md
What you get
| Output field | Meaning |
|---|---|
date | The fixing date the ECB actually quoted, which may differ from the date you asked for |
baseCurrency | The currency everything is quoted against |
targetCurrency | The quoted currency |
rate | Units of targetCurrency per one baseCurrency |
series | Convenience pair label, e.g. EUR/USD |
changePercent | Percent move from the previous quoted date in this run's range; null on the first date of a series |
periodHigh, periodLow | Highest and lowest rate for this pair across the whole requested range |
scrapedAt | Run timestamp |
A RUN_SUMMARY record in the key-value store holds the mode used, the date span actually returned, per-series counts, and any currency that could not be resolved.
Common use cases
- Restate financial reports across currencies. One fixing date, the currencies you report in.
{"baseCurrency": "EUR","targetCurrencies": ["USD", "GBP", "CHF", "JPY"],"date": "2025-12-31"}
- Backtest FX exposure. A multi-year daily series for a single pair.
{"baseCurrency": "EUR","targetCurrencies": ["USD"],"fromDate": "2020-01-01","toDate": "2026-01-01"}
- Multi-currency invoicing. The latest published rates for your billing currencies, on a schedule.
{"baseCurrency": "USD","targetCurrencies": ["EUR", "GBP", "SEK", "PLN", "CZK"]}
Quick start
Latest rates, four majors against the euro:
{"baseCurrency": "EUR","targetCurrencies": ["USD", "GBP", "JPY", "CHF"]}
A year of daily EUR/USD and EUR/GBP:
{"baseCurrency": "EUR","targetCurrencies": ["USD", "GBP"],"fromDate": "2025-01-01","toDate": "2025-12-31"}
One historical fixing, every currency the ECB publishes:
{"baseCurrency": "EUR","date": "2025-12-31"}
Input
| Field | Type | Default | Notes |
|---|---|---|---|
baseCurrency | string | EUR | Three-letter ISO code. EUR is the ECB's own base; any other base is derived from the EUR cross rates. |
targetCurrencies | array | [] | Codes to quote. Empty = all 30+ published currencies. |
date | string | "" | One historical fixing, YYYY-MM-DD. Empty = latest. |
fromDate | string | "" | Series start, YYYY-MM-DD. Earliest available is 1999-01-04. |
toDate | string | "" | Series end, YYYY-MM-DD. Empty = through the latest publication. |
maxResults | integer | 0 | Row cap. 0 = no limit. |
Which combinations make sense. The three date fields pick a mode, in this order of precedence:
| You set | Mode | What you get |
|---|---|---|
| nothing | latest | The most recent published fixing, one row per currency |
date | single date | That day's fixing, one row per currency |
fromDate and/or toDate | time series | One row per currency per quoted day in the range |
Setting fromDate or toDate overrides date, the range wins. targetCurrencies is the multiplier to watch: a two-year range with all 30 currencies is roughly 15,000 rows, the same range with two currencies is about 1,000.
changePercent and periodHigh/periodLow are computed within the range you requested, not against all history. In latest or single-date mode there is only one observation per series, so changePercent is null and high and low both equal the rate.
Output example
A real record from a time-series run:
{"date": "2026-08-03","baseCurrency": "EUR","targetCurrency": "USD","rate": 1.1535,"series": "EUR/USD","changePercent": 0.4354,"periodHigh": 1.1554,"periodLow": 1.1367,"scrapedAt": "2026-08-06T11:45:44.114Z"}
changePercent of 0.4354 is the move from the previous quoted date, 2026-07-31, not from 2026-08-02, because the ECB does not quote at weekends.
RUN_SUMMARY looks like this:
{"mode": "timeseries","baseCurrency": "EUR","currenciesReturned": 2,"datesReturned": 8,"firstDate": "2026-07-27","lastDate": "2026-08-05","seriesSaved": 16,"failures": [],"perSeries": [{ "series": "EUR/GBP", "dates": 8, "saved": 8, "periodHigh": 0.8572, "periodLow": 0.85524 },{ "series": "EUR/USD", "dates": 8, "saved": 8, "periodHigh": 1.1554, "periodLow": 1.1367 }],"filters": { "baseCurrency": "EUR", "targetCurrencies": ["USD", "GBP"], "date": "", "fromDate": "2026-07-27", "toDate": "2026-08-05", "maxResults": 0 },"finishedAt": "2026-08-06T11:45:44.114Z"}
Supported currencies
The ECB publishes 30 currencies against the euro: AUD, BRL, CAD, CHF, CNY, CZK, DKK, GBP, HKD, HUF, IDR, ILS, INR, ISK, JPY, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, SEK, SGD, THB, TRY, USD, ZAR, plus EUR itself as a base.
Any of these can be the base; the API rebases the euro cross rates for you. The Actor checks your codes against the live list before it fetches anything, because the API silently drops an unrecognised code rather than complaining, so a typo would otherwise just produce a missing series with no explanation.
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~fx-rates-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"baseCurrency": "EUR","targetCurrencies": ["USD", "GBP"],"fromDate": "2026-01-01","toDate": "2026-06-30"}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/fx-rates-scraper').call({baseCurrency: 'EUR',targetCurrencies: ['USD', 'GBP', 'JPY'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const r of items) console.log(`${r.series} @ ${r.date}: ${r.rate}`);
Limits and behaviour
- The ECB quotes on working days only. Rates are published once each TARGET business day around 16:00 CET. Ask for a Saturday, a Sunday or a euro-area holiday and you silently get the previous working day's fixing back, under that earlier date. Always read the
datefield rather than assuming you got the date you asked for. In a time series, weekends and holidays are simply absent, no gap-filling, no carry-forward. - Schedule after 16:00 CET. Running earlier returns the previous day's figures. A run scheduled on a weekend produces the same rows as Friday's run.
- History starts 1999-01-04. Anything earlier returns HTTP 404, as does any future date. The Actor reports both with that boundary spelled out in the error.
- Unknown currency codes are caught up front. The API drops a symbol it does not recognise without any error, so the Actor validates against the live currency list first and records rejects in
RUN_SUMMARY.failuresinstead of leaving you with a mysteriously missing series. - The base is never quoted against itself. Asking for
EURintargetCurrencieswithEURas the base drops it; the row would always be exactly 1. - Transient errors are retried. 429 and 5xx get three attempts with linear backoff. 404 is not retried, because an out-of-range date will never succeed.
- Reference rates are not trading rates. They are the ECB's daily snapshot for statistical and accounting use, not executable quotes. Do not price a trade off them.
FAQ
Do I need a proxy? No. Proxy configuration is not required to run this Actor.
Do I need an account or API key? No. You supply no credentials.
Why is the returned date not the date I asked for? Because the ECB did not publish on that date, weekend, holiday, or a date before that day's 16:00 CET fixing. You get the most recent working day at or before your date.
What happens if a currency is unavailable? It is logged, added to RUN_SUMMARY.failures, and the run continues with the rest. The Actor only errors out if nothing at all could be fetched.
Can I get intraday or tick data? No. The ECB publishes one reference rate per currency per working day. Nothing finer exists in this source.
Why is changePercent null on some rows? It is the first quoted date in your requested range, so there is no previous observation to compare against. Widen fromDate by a few days if you need a change figure on your first date of interest.
Are periodHigh/periodLow all-time? No, they are the high and low within the range you requested, repeated on every row of that pair so you can filter to a single row and still see the range.
Can I schedule it? Yes. A daily run after 16:00 CET on weekdays keeps a rates table current; weekend runs are harmless but duplicate Friday.
Can I integrate it with something else? Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.