FX Rates Scraper: ECB Reference Rates & History avatar

FX Rates Scraper: ECB Reference Rates & History

Pricing

from $0.37 / 1,000 rate scrapeds

Go to Apify Store
FX Rates Scraper: ECB Reference Rates & History

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

Arman Hossain

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

20 days ago

Last modified

Share

FX Rates Scraper: European Central Bank reference rates for 30+ currencies, with change percent and period high/low computed for you

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 fieldMeaning
dateThe fixing date the ECB actually quoted, which may differ from the date you asked for
baseCurrencyThe currency everything is quoted against
targetCurrencyThe quoted currency
rateUnits of targetCurrency per one baseCurrency
seriesConvenience pair label, e.g. EUR/USD
changePercentPercent move from the previous quoted date in this run's range; null on the first date of a series
periodHigh, periodLowHighest and lowest rate for this pair across the whole requested range
scrapedAtRun 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

FieldTypeDefaultNotes
baseCurrencystringEURThree-letter ISO code. EUR is the ECB's own base; any other base is derived from the EUR cross rates.
targetCurrenciesarray[]Codes to quote. Empty = all 30+ published currencies.
datestring""One historical fixing, YYYY-MM-DD. Empty = latest.
fromDatestring""Series start, YYYY-MM-DD. Earliest available is 1999-01-04.
toDatestring""Series end, YYYY-MM-DD. Empty = through the latest publication.
maxResultsinteger0Row cap. 0 = no limit.

Which combinations make sense. The three date fields pick a mode, in this order of precedence:

You setModeWhat you get
nothinglatestThe most recent published fixing, one row per currency
datesingle dateThat day's fixing, one row per currency
fromDate and/or toDatetime seriesOne 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 date field 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.failures instead of leaving you with a mysteriously missing series.
  • The base is never quoted against itself. Asking for EUR in targetCurrencies with EUR as 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.