Historical Exchange Rates & Currency Converter
Pricing
from $0.75 / 1,000 exchange rate rows
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
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
2 days ago
Last modified
Categories
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.

Table of contents
- What it does
- Quick start
- Input
- Output
- Pricing and limits
- API, export and integrations
- Troubleshooting
- FAQ
- Data source and licence
- Support
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_rateand a reason innote:weekend,ecb_holiday,not_yet_publishedorcurrency_not_quoted. - Optional fill. With
missingDatePolicy: last_published, a missing day gets the last published rate before it.effectiveDatealways 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
- Open the Input tab.
- In Currency pairs (
pairs), keep the prefillEUR/USDandEUR/GBPor add your own, for exampleUSD/JPY. - Keep Last days (
lastDays) at 7, or add Specific dates (dates), for example2024-12-31. - Click Start.
- Open the Output tab. Each row has
pair,requestedDate,status,rateandnote.
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
| Field | Plain name | Type | Required | Default / prefill | Allowed values | Effect |
|---|---|---|---|---|---|---|
pairs | Currency pairs | list of text | yes | prefill EUR/USD, EUR/GBP | 1–10 pairs as BASE/QUOTE; EURUSD and eur-usd also work | Rate = how many QUOTE units one BASE unit buys |
dates | Specific dates | list of text | no | — | 1–100 dates YYYY-MM-DD, 1999-01-04 to today (UTC) | One row per date, in your order |
dateFrom | Date from | date | no | — | YYYY-MM-DD | Start of a range; use with dateTo |
dateTo | Date to | date | no | — | YYYY-MM-DD, not after today | End of a range; at most 366 days |
lastDays | Last days | number | no | 7 (prefill 7) | 1–366 | Every day from N−1 days ago to today (UTC) |
provider | Rate source | select | no | ECB | ECB | Official source of the rates |
missingDatePolicy | Days without a rate | select | no | none | none, last_published, skip | What to do on weekends, holidays and days not published yet |
amounts | Amounts to convert | list of numbers | no | — | 0–10 amounts in the BASE currency | One row per amount with convertedAmount |
rounding | Decimal places | number | no | 2 | 0–8 | Rounding 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 havestatus: unsupported_currency.
Interacting settings
| When | Effective behavior |
|---|---|
dates is set | dates is used. dateFrom, dateTo and lastDays are ignored (with a warning). |
dateFrom and dateTo are set, dates is empty | The range is used. lastDays is ignored. Both range fields are needed. |
| Nothing is set | lastDays is used (7 if empty). |
missingDatePolicy: none | Days without a rate get a no_rate row with the reason. |
missingDatePolicy: last_published | Days 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: skip | Days 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"es=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
| Field | Meaning | Can be empty? | Example / values |
|---|---|---|---|
pair, base, quote | The pair as BASE/QUOTE and its two codes | no | USD/JPY |
requestedDate | The date you asked for | no | 2025-12-25 |
effectiveDate | The ECB publication date of the rate | when there is no rate | 2025-12-24 |
status | Result of the row | no | ok, no_rate, unsupported_currency |
note | Why the row is special | yes (normal day) | weekend, ecb_holiday, not_yet_published, currency_not_quoted, last_published_used, same_currency, unsupported_currency |
noteText | The note in plain English | yes | — |
rate | QUOTE units for 1 BASE unit | when there is no rate | 156.9544711 |
inverseRate | 1 / rate | when there is no rate | 0.006371274377 |
rateType | How the rate was made | when there is no rate | direct (EUR/X), inverse (X/EUR), cross_via_EUR, identity |
eurToBase, eurToQuote | The ECB euro rates used | when there is no rate | 1.0389, 163.06 |
amount | Your amount in BASE | yes (no amounts) | 1000 |
convertedAmount | amount × rate, rounded | yes | 156954.47 |
provider, providerName | Rate source | no | ECB, European Central Bank |
providerLastPublished | Latest ECB publication date at run time | no | 2026-10-09 |
source, sourceUrl | Where the numbers came from | no | — |
fetchedAt | When the Actor read the source | no | ISO time |
Direct rates are exactly the ECB numbers. Cross and inverse rates keep 10 significant digits.
Output surfaces
| Situation | Where | What you see |
|---|---|---|
| Normal run | Dataset | Rows with status: ok; run summary COMPLETE |
| Only weekends or holidays | Dataset | no_rate rows with the reason; summary NO_RESULTS |
| Some codes unknown | Dataset | unsupported_currency rows; summary PARTIAL |
| Source problem | Key-value store RUN_SUMMARY | No rows for the failed dates; sourceError lists them; summary PARTIAL or ERROR |
| Bad input | Key-value store RUN_SUMMARY | INVALID_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 field | This Actor |
|---|---|
base_currency | base |
target_currency | quote |
rate | rate |
inverse_rate | inverseRate |
date | effectiveDate (the rate's day) or requestedDate (your day) |
extracted_at | fetchedAt |
Pricing and limits
Pricing unit
This Actor uses pay per event. You pay only for these events:
| Event | When | Price |
|---|---|---|
| Actor start | Once per run | $0.002 |
| Rate row | Each row with status: ok and a real rate | $0.0009 (Free plan) · $0.00085 (Bronze) · $0.0008 (Silver) · $0.00075 (Gold and higher) |
| Note row | Each no_rate, unsupported_currency or same-currency row | $0.00003 |
- Platform usage (compute, storage) is included. You do not pay it.
- Rows from
missingDatePolicy: skipare 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)
| Scenario | Rows | Cost |
|---|---|---|
| 10 rate rows | e.g. 1 pair × 10 business days | $0.011 |
| Prefill run: 2 pairs × last 7 days | 8 rate rows + 6 note rows | $0.0094 |
| 100 rate rows | e.g. 1 pair × 100 dates | $0.092 |
| 1,000 rate rows | e.g. 4 pairs × 1 year with skip | $0.902 |
| 1 year, 1 pair, every day | about 255 rate rows + 110 note rows | about $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: 1to 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.