Historical Exchange Rates & Currency Converter avatar

Historical Exchange Rates & Currency Converter

Pricing

from $0.75 / 1,000 exchange rate rows

Go to Apify Store
Historical Exchange Rates & Currency Converter

Historical Exchange Rates & Currency Converter

Official ECB historical exchange rates for any currency pair and date: exact cross rates, weekend and holiday handling with clear notes, optional last-published fill and amount conversion. 30+ currencies since 1999, via the Frankfurter API.

Pricing

from $0.75 / 1,000 exchange rate rows

Rating

0.0

(0)

Developer

Cliqto Media

Cliqto Media

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

2 days ago

Last modified

Share

Get official European Central Bank (ECB) exchange rates for any currency pair and any date since 1999, and convert amounts with them. Add a pair like USD/JPY and a date, and you get one clear row per pair and day. Weekends, ECB holidays and days not published yet are marked, not hidden. No API key is needed, and a typical run (2 pairs, last 7 days) costs about $0.01.

Historical Exchange Rates: add pairs, dates and amounts, get the exact ECB rate and converted amount, and see why a day has no rate

Table of contents

What it does

The Actor reads the euro foreign exchange reference rates that the ECB publishes every business day at about 16:00 CET. It returns one dataset row for each pair × date (and for each amount, if you add amounts).

  • Any pair. The ECB publishes rates against the euro. For pairs without the euro, like USD/JPY, the Actor computes the cross rate from the two euro rates and shows both of them (eurToBase, eurToQuote). You can check the math yourself.
  • Any date since 1999-01-04. You can use a list of dates, a date range or the last N days.
  • Every day explained. A day without a rate has status: no_rate and a reason in note: weekend, ecb_holiday, not_yet_published or currency_not_quoted.
  • Optional fill. With missingDatePolicy: last_published, a missing day gets the last published rate before it. effectiveDate always shows which day's rate was used.
  • Conversion. Add amounts and get convertedAmount = amount × rate, rounded the way you choose.

Good fit

  • Accounting and invoices: the official ECB rate for the invoice date.
  • Finance and audit: a full rate history for a pair, with holidays clearly marked.
  • Reports and BI: a clean daily table of rates for several pairs.

Not a fit / limits

  • No live or intraday rates. The ECB publishes once a day; today's rate appears after about 16:00 CET.
  • About 30 currencies are quoted today (45 in the full history). No crypto, no exotic currencies outside the ECB list.
  • These are reference rates for information. They are not bank buy/sell rates.

Quick start

  1. Open the Input tab.
  2. In Currency pairs (pairs), keep the prefill EUR/USD and EUR/GBP or add your own, for example USD/JPY.
  3. Keep Last days (lastDays) at 7, or add Specific dates (dates), for example 2024-12-31.
  4. Click Start.
  5. Open the Output tab. Each row has pair, requestedDate, status, rate and note.

Expected result: one row per pair and day. Business days have status: ok and a rate. Weekends have status: no_rate and note: weekend. A 7-day run finishes in a few seconds.

Input

Input fields

FieldPlain nameTypeRequiredDefault / prefillAllowed valuesEffect
pairsCurrency pairslist of textyesprefill EUR/USD, EUR/GBP1–10 pairs as BASE/QUOTE; EURUSD and eur-usd also workRate = how many QUOTE units one BASE unit buys
datesSpecific dateslist of textno—1–100 dates YYYY-MM-DD, 1999-01-04 to today (UTC)One row per date, in your order
dateFromDate fromdateno—YYYY-MM-DDStart of a range; use with dateTo
dateToDate todateno—YYYY-MM-DD, not after todayEnd of a range; at most 366 days
lastDaysLast daysnumberno7 (prefill 7)1–366Every day from N−1 days ago to today (UTC)
providerRate sourceselectnoECBECBOfficial source of the rates
missingDatePolicyDays without a rateselectnononenone, last_published, skipWhat to do on weekends, holidays and days not published yet
amountsAmounts to convertlist of numbersno—0–10 amounts in the BASE currencyOne row per amount with convertedAmount
roundingDecimal placesnumberno20–8Rounding of convertedAmount (half away from zero)
  • Duplicate pairs, dates and amounts are removed. The run summary shows a warning.
  • The run is limited to 10,000 rows (pairs × dates × amounts). A bigger input stops before any request with INVALID_INPUT.
  • An unknown currency code (for example XYZ) does not stop the run. Its rows have status: unsupported_currency.

Interacting settings

WhenEffective behavior
dates is setdates is used. dateFrom, dateTo and lastDays are ignored (with a warning).
dateFrom and dateTo are set, dates is emptyThe range is used. lastDays is ignored. Both range fields are needed.
Nothing is setlastDays is used (7 if empty).
missingDatePolicy: noneDays without a rate get a no_rate row with the reason.
missingDatePolicy: last_publishedDays without a publication get the last rate from up to 10 days before, with note: last_published_used. A currency the ECB stopped quoting is never carried forward.
missingDatePolicy: skipDays without a rate are not written. The run summary counts them.

Input examples

Convert invoice amounts on fixed dates

{
"pairs": ["USD/JPY", "GBP/EUR"],
"dates": ["2024-12-31", "2025-12-25"],
"missingDatePolicy": "last_published",
"amounts": [1000, 2500.5]
}

Expected behavior: 2 pairs × 2 dates × 2 amounts = 8 rows. 2025-12-25 is an ECB holiday, so those rows use the rate of 2025-12-24. Expected output: rate, effectiveDate, convertedAmount. For example, 1,000 USD on 2024-12-31 = 156,954.47 JPY.

Rate history for one year

{
"pairs": ["EUR/USD", "EUR/CHF"],
"dateFrom": "2025-01-01",
"dateTo": "2025-12-31"
}

Expected behavior: 2 × 365 = 730 rows. Business days have a rate; weekends and holidays have no_rate with a reason. Add "missingDatePolicy": "skip" to get only the days with a rate.

Output

Each dataset row is one pair, one requested date and (optionally) one amount.

Output example

From the first input example above (one of 8 rows, full):

{
"pair": "USD/JPY",
"base": "USD",
"quote": "JPY",
"requestedDate": "2024-12-31",
"effectiveDate": "2024-12-31",
"status": "ok",
"note": null,
"noteText": null,
"rate": 156.9544711,
"inverseRate": 0.006371274377,
"rateType": "cross_via_EUR",
"eurToBase": 1.0389,
"eurToQuote": 163.06,
"amount": 1000,
"convertedAmount": 156954.47,
"provider": "ECB",
"providerName": "European Central Bank",
"providerLastPublished": "2026-10-09",
"source": "Frankfurter API v2 (frankfurter.dev)",
"sourceUrl": "https://api.frankfurter.dev/v2/rates?base=EUR&quotes=GBP%2CJPY%2CUSD&providers=ECB&from=2024-12-21&to=2024-12-31",
"fetchedAt": "2026-10-12T10:48:11.221Z"
}

The holiday row for the same pair has requestedDate: 2025-12-25, effectiveDate: 2025-12-24, rate: 155.9599559 and note: last_published_used. noteText explains the reason: “No publication on this business day (TARGET/ECB holiday)”.

Output fields

FieldMeaningCan be empty?Example / values
pair, base, quoteThe pair as BASE/QUOTE and its two codesnoUSD/JPY
requestedDateThe date you asked forno2025-12-25
effectiveDateThe ECB publication date of the ratewhen there is no rate2025-12-24
statusResult of the rownook, no_rate, unsupported_currency
noteWhy the row is specialyes (normal day)weekend, ecb_holiday, not_yet_published, currency_not_quoted, last_published_used, same_currency, unsupported_currency
noteTextThe note in plain Englishyes—
rateQUOTE units for 1 BASE unitwhen there is no rate156.9544711
inverseRate1 / ratewhen there is no rate0.006371274377
rateTypeHow the rate was madewhen there is no ratedirect (EUR/X), inverse (X/EUR), cross_via_EUR, identity
eurToBase, eurToQuoteThe ECB euro rates usedwhen there is no rate1.0389, 163.06
amountYour amount in BASEyes (no amounts)1000
convertedAmountamount × rate, roundedyes156954.47
provider, providerNameRate sourcenoECB, European Central Bank
providerLastPublishedLatest ECB publication date at run timeno2026-10-09
source, sourceUrlWhere the numbers came fromno—
fetchedAtWhen the Actor read the sourcenoISO time

Direct rates are exactly the ECB numbers. Cross and inverse rates keep 10 significant digits.

Output surfaces

SituationWhereWhat you see
Normal runDatasetRows with status: ok; run summary COMPLETE
Only weekends or holidaysDatasetno_rate rows with the reason; summary NO_RESULTS
Some codes unknownDatasetunsupported_currency rows; summary PARTIAL
Source problemKey-value store RUN_SUMMARYNo rows for the failed dates; sourceError lists them; summary PARTIAL or ERROR
Bad inputKey-value store RUN_SUMMARYINVALID_INPUT with the reason; the run fails, no rows

RUN_SUMMARY also has counts (ok, noRate, lastPublishedUsed, …), warnings and the ECB source links.

Moving from another FX Actor

If you used an Actor with snake_case fields, map them like this:

Old fieldThis Actor
base_currencybase
target_currencyquote
raterate
inverse_rateinverseRate
dateeffectiveDate (the rate's day) or requestedDate (your day)
extracted_atfetchedAt

Pricing and limits

Pricing unit

This Actor uses pay per event. You pay only for these events:

EventWhenPrice
Actor startOnce per run$0.002
Rate rowEach row with status: ok and a real rate$0.0009 (Free plan) · $0.00085 (Bronze) · $0.0008 (Silver) · $0.00075 (Gold and higher)
Note rowEach no_rate, unsupported_currency or same-currency row$0.00003
  • Platform usage (compute, storage) is included. You do not pay it.
  • Rows from missingDatePolicy: skip are not written and not charged.
  • If the source fails, no rows are written for the failed dates, so they are not charged.
  • An invalid input costs only the Actor start.
  • If you set a maximum cost for the run, the Actor stops writing rows before it goes over it. The summary says CAPPED.

The ECB rates are free at the source. You pay for the processing: cross rates, holiday checks, conversion and a clean table.

Cost examples (Free plan prices)

ScenarioRowsCost
10 rate rowse.g. 1 pair × 10 business days$0.011
Prefill run: 2 pairs × last 7 days8 rate rows + 6 note rows$0.0094
100 rate rowse.g. 1 pair × 100 dates$0.092
1,000 rate rowse.g. 4 pairs × 1 year with skip$0.902
1 year, 1 pair, every dayabout 255 rate rows + 110 note rowsabout $0.234

Limits

  • Up to 10 pairs, 100 dates or a 366-day range, 10 amounts and 10,000 rows per run.
  • The largest tested run (10,000 rows) took about 30 seconds.
  • Dates from 1999-01-04 to today (UTC).

API, export and integrations

  • API: start the Actor with the same JSON input and read the dataset by API.
  • Export: download the dataset as JSON, CSV, Excel or XML from the Output tab.
  • Scheduling: run it every day after 16:30 CET with lastDays: 1 to save the new rate.

Use with AI agents (MCP)

  • MCP server with only this Actor: https://mcp.apify.com?tools=cliqtomedia/historical-fx-rates-converter
  • Smallest useful input: {"pairs": ["EUR/USD"], "dates": ["2024-12-31"]}
  • Key output fields: rate (QUOTE per 1 BASE), effectiveDate (the ECB day used), status (ok / no_rate / unsupported_currency), note (reason for a missing rate), convertedAmount.
  • A small run finishes in a few seconds. Cost: $0.002 per run plus $0.00075–$0.0009 per rate row and $0.00003 per note row. Limits: 10 pairs, 100 dates or 366 days, 10 amounts, 10,000 rows.

Troubleshooting

Today's row is not_yet_published

The ECB publishes at about 16:00 CET. Run again later, or use missingDatePolicy: last_published to get the previous rate.

A currency has currency_not_quoted

The ECB published rates that day, but not for this currency. It was not quoted yet (for example CNY before 2005) or it was suspended or removed (for example RUB after 2022-03-01). The Actor does not carry such rates forward.

Rows are missing and the summary has sourceError

The rate source did not answer for some dates. sourceError.failedDates lists them, and they were not charged. Run again for those dates later.

The run failed with INVALID_INPUT

Read the message in RUN_SUMMARY. Common reasons: a date before 1999-01-04 or in the future, a wrong date format, or more than 10,000 rows.

FAQ

Do I need an API key?

No. The Actor reads public ECB data.

Why are there no rates on weekends?

The ECB does not publish on weekends and on TARGET holidays (for example 1 January, Good Friday, Easter Monday, 1 May, 25–26 December). These days get a no_rate row with the reason, or the previous rate if you choose last_published.

Are these the same as bank rates?

No. ECB reference rates are for information. Banks and card companies use their own buy and sell rates.

How fresh is the data?

The Actor reads the source on every run. providerLastPublished shows the latest ECB publication at that time.

Data source and licence

Source: ECB statistics (euro foreign exchange reference rates), read through the open Frankfurter API. Cross rates are computed by this Actor from the ECB euro rates. The ECB allows reuse of its statistics with the source named. See the ECB reference rates page. This Actor is not made by or linked to the ECB.

Support

For bugs, questions or feature requests, open an issue in the Issues tab. Include the run ID and the input. Do not send private data.