# ECB Exchange Rates API: Daily, Historical, Conversion (`actose/ecb-exchange-rates`) Actor

Official ECB euro reference exchange rates for ~30 currencies: latest, history since 1999, cross rates and conversion of dated invoice amounts (rate, inverse, change %, converted amount). No API key, no login. MCP-ready for AI agents. Pay per result, errors are free. $0.50 per 1,000 rows.

- **URL**: https://apify.com/actose/ecb-exchange-rates.md
- **Developed by:** [ABM](https://apify.com/actose) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.43 / 1,000 exchange rate rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ECB Exchange Rates API: daily, historical euro FX rates and conversion

Get the **official European Central Bank (ECB) euro foreign exchange reference rates** as clean JSON or CSV: today's rates, the full history since 1999-01-04 for about 30 currencies, cross rates against any base (USD, GBP, CHF...), and **bulk conversion of your own dated amounts** (invoices, expenses, transactions) at the ECB rate of their date.

**Price: $0.50 per 1,000 rows** ($0.0005 per exchange rate or converted amount). No API key, no login, no monthly rental. Errors, unknown currencies and duplicates are free.

### What does it do?

- **Latest**: the last rates published by the ECB (around 16:00 CET on TARGET business days).
- **History**: every business day between two dates, with the day-over-day change in %.
- **Convert**: send a list of `{amount, from, to, date}` items and get the converted amount, the rate and the ECB date used. On weekends and holidays it uses the last rate published before that date (`isFallbackDate: true`), which is the usual accounting practice.
- **Cross rates**: set `baseCurrency` to `USD` (or any ECB currency) to get USD/GBP, USD/JPY... computed from the ECB euro rates.
- **Only new rates**: with `onlyNew: true`, a scheduled run delivers only the rates published since the previous run, so you pay only for new rows.
- **Outputs**: dataset (JSON, CSV, Excel, XML via the API) plus a `rates.csv` file in the key-value store. Every row also has a plain-English `text` field that LLMs and RAG pipelines can use directly.

Data comes straight from the ECB's public eurofxref files (daily XML, 90-day XML, full-history CSV), with the ECB Data Portal API as a fallback. Nothing is scraped from HTML.

### How much does it cost?

You pay **$0.0005 per delivered row**. Platform usage is included.

| Request | Rows | Price |
|---|---|---|
| Today's rates, all ~30 currencies | 30 | $0.015 |
| USD, GBP, CHF, JPY for all of 2024 | ~1,024 | ~$0.51 |
| 1,000 invoices converted | 1,000 | $0.50 |
| Daily `onlyNew` schedule, 5 currencies, for a month | ~110 | ~$0.055 |

Invalid items, unknown currencies, duplicates and empty periods are **not charged**. Use **Maximum rows** or the run's maximum cost to cap spending: the actor stops cleanly once your limit is reached.

### Input

| Field | Type | Description |
|---|---|---|
| `mode` | `latest` / `history` / `convert` | What to return (default `latest`) |
| `currencies` | array | ISO codes; empty = all ECB currencies (latest and history modes) |
| `baseCurrency` | string | `EUR` (official ECB figures) or any ECB currency for cross rates; default target in convert mode |
| `startDate`, `endDate` | `YYYY-MM-DD` | Period for `history` (default: the last 30 days) |
| `amounts` | array | Items for `convert`: `{"id", "amount", "from", "to", "date"}` |
| `onlyNew` | boolean | Deliver only rates newer than the previous run |
| `maxRows` | integer | Stop after N rows (0 = no limit) |
| `saveCsv` | boolean | Also save `rates.csv` (default true) |

Historical rates example:

```json
{
  "mode": "history",
  "currencies": ["USD", "GBP", "CHF", "JPY"],
  "baseCurrency": "EUR",
  "startDate": "2024-01-01",
  "endDate": "2024-12-31"
}
```

Conversion example:

```json
{
  "mode": "convert",
  "baseCurrency": "EUR",
  "amounts": [
    { "id": "INV-001", "amount": 1250.5, "from": "USD", "to": "EUR", "date": "2024-03-15" },
    { "id": "INV-002", "amount": "1 234,56", "from": "GBP", "date": "2024-12-25" }
  ]
}
```

Amounts can be numbers or strings in European or US format (`"1 234,56"`, `"1,234.56"`). Dates also accept `DD/MM/YYYY`.

### Output

Rate row (`history` or `latest`):

```json
{
  "type": "rate",
  "date": "2024-12-31",
  "baseCurrency": "EUR",
  "currency": "USD",
  "rate": 1.0389,
  "inverseRate": 0.96255655,
  "changePct": -0.2591,
  "isCrossRate": false,
  "text": "On 2024-12-31, 1 EUR = 1.0389 USD (ECB euro reference rate).",
  "source": "ECB euro foreign exchange reference rates",
  "sourceUrl": "https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html"
}
```

Conversion row:

```json
{
  "type": "conversion",
  "id": "INV-002",
  "itemIndex": 1,
  "requestedDate": "2024-12-25",
  "date": "2024-12-24",
  "amount": 1234.56,
  "fromCurrency": "GBP",
  "toCurrency": "EUR",
  "baseCurrency": "GBP",
  "currency": "EUR",
  "rate": 1.2058,
  "inverseRate": 0.8293,
  "isCrossRate": false,
  "convertedAmount": 1488.6,
  "isFallbackDate": true,
  "error": null,
  "text": "1234.56 GBP = 1488.6 EUR at the ECB reference rate of 2024-12-24 (1 GBP = 1.2058 EUR)."
}
```

(Figures above are illustrative.) An invalid item comes back with only its input fields and an `error` message, for example `"currency \"XYZ\" is not published by the ECB"`, and it is not charged.

### Use cases

#### 1. Month-end revaluation for an accountant

A chartered accountant converts every foreign-currency supplier invoice of the month into EUR at the ECB rate of the invoice date, as many EU tax rules allow.
Input: `{"mode": "convert", "baseCurrency": "EUR", "amounts": [{"id": "FA-2024-0312", "amount": 4820, "from": "USD", "date": "2024-03-12"}, {"id": "FA-2024-0316", "amount": 1500, "from": "CHF", "date": "2024-03-16"}]}`
Output: `[{"id": "FA-2024-0312", "rate": 0.9147..., "convertedAmount": 4409.0..., "date": "2024-03-12"}, {"id": "FA-2024-0316", "date": "2024-03-15", "isFallbackDate": true, ...}]`
Next step: 300 invoices cost $0.15. The CSV is imported into the accounting software, and `isFallbackDate` documents the weekend rule for the auditor.

#### 2. Daily rates in Google Sheets for a finance team

A treasury analyst wants the new ECB rates every morning in a shared sheet used by pricing and billing.
Input (scheduled every weekday at 17:00 CET): `{"mode": "latest", "currencies": ["USD", "GBP", "CHF", "SEK", "PLN"], "onlyNew": true}`
Output: `[{"date": "2026-10-16", "currency": "USD", "rate": 1.1253, "changePct": 0.12}, ...]`
Next step: Apify's Google Sheets integration appends the 5 rows. That is about 110 rows a month, roughly $0.06 a month, and nothing is charged on ECB holidays.

#### 3. Historical FX for a BI dashboard or a pricing model

A data analyst at an e-commerce company backfills USD, GBP and JPY against EUR since 2015 to restate revenue in a constant currency.
Input: `{"mode": "history", "currencies": ["USD", "GBP", "JPY"], "startDate": "2015-01-01", "endDate": "2025-12-31"}`
Output: ~8,500 rows like `{"date": "2015-01-02", "currency": "USD", "rate": 1.2043}`
Next step: about $4.25, loaded into BigQuery or Snowflake through the dataset CSV export, then joined on `date` + `currency`.

#### 4. USD-based cross rates for a US subsidiary

A controller reporting in USD sets `baseCurrency` to `USD`: `{"mode": "history", "baseCurrency": "USD", "currencies": ["EUR", "GBP", "CAD"], "startDate": "2024-01-01", "endDate": "2024-03-31"}` returns rows like `{"baseCurrency": "USD", "currency": "EUR", "rate": 0.91, "isCrossRate": true}`.

### API usage

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<username>/ecb-exchange-rates").call(run_input={
    "mode": "history",
    "currencies": ["USD", "GBP"],
    "startDate": "2024-01-01",
    "endDate": "2024-12-31",
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["date"], row["currency"], row["rate"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<username>/ecb-exchange-rates').call({
    mode: 'convert',
    amounts: [{ id: 'INV-001', amount: 1250.5, from: 'USD', to: 'EUR', date: '2024-03-15' }],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].convertedAmount);
```

Single HTTP call (returns the rows directly):

```
POST https://api.apify.com/v2/acts/<username>~ecb-exchange-rates/run-sync-get-dataset-items?token=<TOKEN>&format=csv
{"mode": "latest"}
```

### Integrations (Make, n8n, Zapier, Google Sheets)

- **n8n**: a *Schedule Trigger* node (weekdays at 17:00) feeds an *Apify* node with "Run actor and get dataset" and input `{"mode": "latest", "onlyNew": true}`, then a *Google Sheets → Append row* node. Your sheet gets only new rates, every day.
- **Make**: *Apify → Run an Actor* (synchronous), then *Apify → Get Dataset Items*, then *Iterator* and *Google Sheets / Airtable / Xero*.
- **Zapier**: trigger "New invoice in QuickBooks", then *Apify → Run Actor* in `convert` mode with the invoice amount, currency and date, then write `convertedAmount` back.
- **Google Sheets**: use Apify's built-in Google Sheets export or `=IMPORTDATA()` on the dataset CSV URL.

### Use with AI agents (MCP)

This actor works with the Apify MCP server (`https://mcp.apify.com`) from Claude, Cursor, ChatGPT and other MCP clients. An agent can call it with `{"mode": "convert", "amounts": [...]}` to answer "how much was this USD invoice worth in EUR on 15 March 2024?" using an official source. The `text` field gives a ready-to-quote sentence, and `sourceUrl` gives the citation.

### FAQ

**Is this the official ECB data?** Yes. The rates are the euro foreign exchange reference rates published by the European Central Bank, read from its public files. They are not modified, except that cross rates (base other than EUR) are computed from them.

**Why are there no rates on some days?** The ECB publishes only on TARGET business days, so there are no rates on weekends, 1 January, Good Friday, Easter Monday, 1 May, 25 and 26 December. In convert mode the previous business day is used.

**Which currencies are available?** About 30: USD, JPY, BGN, CZK, DKK, GBP, HUF, PLN, RON, SEK, CHF, ISK, NOK, TRY, AUD, BRL, CAD, CNY, HKD, IDR, ILS, INR, KRW, MXN, MYR, NZD, PHP, SGD, THB, ZAR, plus discontinued ones for the years they were published (e.g. RUB until March 2022, HRK until 2022).

**What time are new rates available?** Around 16:00 CET. Schedule `onlyNew` runs after 16:30 CET.

**How does `onlyNew` work?** The last delivered date per currency and base currency is stored in your named key-value store `ecb-exchange-rates-state`. Delete that record to start over.

**Do I need an API key or proxy?** No. The ECB data is public, and the actor downloads it directly.

### Limits

- ECB reference rates are for information. They are fixed once a day and are not live market or trading rates.
- No intraday rates, no rates before 1999-01-04, and no currencies outside the ECB list (for example, no crypto and no AED).
- Cross rates are derived from two euro rates and are rounded to 8 significant digits, so they can differ slightly from another provider's direct quotes.
- Rates are fixed after publication but are not available for the current day before about 16:00 CET; the convert mode then uses the previous business day.

# Actor input Schema

## `mode` (type: `string`):

<b>latest</b>: last published ECB reference rates (one day). <b>history</b>: every business day between Start date and End date. <b>convert</b>: convert your list of dated amounts at the ECB rate of their date.

## `currencies` (type: `array`):

ISO 4217 codes (USD, GBP, CHF, JPY, CNY, PLN...). Leave empty for all ~30 currencies published by the ECB. Used by the latest and history modes.

## `baseCurrency` (type: `string`):

Currency to express rates against. EUR returns the official ECB figures; any other code (e.g. USD) returns cross rates computed from the ECB euro rates. In convert mode it is the default target currency when an item has no "to".

## `startDate` (type: `string`):

First day of the period (YYYY-MM-DD), history mode. The ECB series starts on 1999-01-04. Default: 30 days before the end date.

## `endDate` (type: `string`):

Last day of the period (YYYY-MM-DD), history mode. Default: today.

## `amounts` (type: `array`):

Convert mode only. List of objects: {"id": "INV-001", "amount": 1250.5, "from": "USD", "to": "EUR", "date": "2024-03-15"}. "to" defaults to the base currency, "date" to today. Weekends and holidays use the last ECB rate before the date (isFallbackDate = true). Invalid items are returned with an "error" field and are not charged.

## `onlyNew` (type: `boolean`):

Deliver only rates published since the previous run with the same base currency (remembered in the named key-value store "ecb-exchange-rates-state"). Ideal for a daily schedule: you pay only for new rows.

## `maxRows` (type: `integer`):

Stop after this many rate rows (0 = no limit). Useful to cap the cost of large histories (all currencies since 1999 is about 200,000 rows).

## `saveCsv` (type: `boolean`):

Also save the delivered rows as rates.csv in the key-value store (link in the log and in the Output tab). The dataset itself can also be exported to CSV, Excel, JSON or XML.

## Actor input object example

```json
{
  "mode": "history",
  "currencies": [
    "USD",
    "GBP",
    "CHF",
    "JPY"
  ],
  "baseCurrency": "EUR",
  "startDate": "2024-01-01",
  "endDate": "2024-12-31",
  "amounts": [
    {
      "id": "INV-001",
      "amount": 1250.5,
      "from": "USD",
      "to": "EUR",
      "date": "2024-03-15"
    },
    {
      "id": "INV-002",
      "amount": 980,
      "from": "GBP",
      "to": "EUR",
      "date": "2024-12-25"
    },
    {
      "id": "INV-003",
      "amount": 50000,
      "from": "JPY",
      "to": "CHF",
      "date": "2024-06-28"
    }
  ],
  "onlyNew": false,
  "maxRows": 0,
  "saveCsv": true
}
```

# Actor output Schema

## `rates` (type: `string`):

No description

## `csvFile` (type: `string`):

No description

## `overview` (type: `string`):

Main results as a table (dataset view).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "history",
    "currencies": [
        "USD",
        "GBP",
        "CHF",
        "JPY"
    ],
    "baseCurrency": "EUR",
    "startDate": "2024-01-01",
    "endDate": "2024-12-31",
    "amounts": [
        {
            "id": "INV-001",
            "amount": 1250.5,
            "from": "USD",
            "to": "EUR",
            "date": "2024-03-15"
        },
        {
            "id": "INV-002",
            "amount": 980,
            "from": "GBP",
            "to": "EUR",
            "date": "2024-12-25"
        },
        {
            "id": "INV-003",
            "amount": 50000,
            "from": "JPY",
            "to": "CHF",
            "date": "2024-06-28"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("actose/ecb-exchange-rates").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "history",
    "currencies": [
        "USD",
        "GBP",
        "CHF",
        "JPY",
    ],
    "baseCurrency": "EUR",
    "startDate": "2024-01-01",
    "endDate": "2024-12-31",
    "amounts": [
        {
            "id": "INV-001",
            "amount": 1250.5,
            "from": "USD",
            "to": "EUR",
            "date": "2024-03-15",
        },
        {
            "id": "INV-002",
            "amount": 980,
            "from": "GBP",
            "to": "EUR",
            "date": "2024-12-25",
        },
        {
            "id": "INV-003",
            "amount": 50000,
            "from": "JPY",
            "to": "CHF",
            "date": "2024-06-28",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("actose/ecb-exchange-rates").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "history",
  "currencies": [
    "USD",
    "GBP",
    "CHF",
    "JPY"
  ],
  "baseCurrency": "EUR",
  "startDate": "2024-01-01",
  "endDate": "2024-12-31",
  "amounts": [
    {
      "id": "INV-001",
      "amount": 1250.5,
      "from": "USD",
      "to": "EUR",
      "date": "2024-03-15"
    },
    {
      "id": "INV-002",
      "amount": 980,
      "from": "GBP",
      "to": "EUR",
      "date": "2024-12-25"
    },
    {
      "id": "INV-003",
      "amount": 50000,
      "from": "JPY",
      "to": "CHF",
      "date": "2024-06-28"
    }
  ]
}' |
apify call actose/ecb-exchange-rates --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,actose/ecb-exchange-rates"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Q17ReiGIfBMAMMRXq/builds/OgwVZwbPxg3e5knhp/openapi.json
