# ECB Exchange Rates History (`grit-77/ecb-fx-rates`) Actor

ECB exchange rate history scraper for dated currency-pair tables. Export daily reference observations and derived same-day cross rates as JSON with calculation inputs and source series.

- **URL**: https://apify.com/grit-77/ecb-fx-rates.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** Business, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 rate blocks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 History

ECB exchange rates scraper returns published daily reference rates and derived same-day cross rates. ECB exchange rates API rows include observation dates, currency pairs, calculation inputs, and source series.

The actor uses the [official ECB Data Portal SDMX API](https://data.ecb.europa.eu/help/api/data-examples). It returns one flat row per published date and requested currency pair. A rate means **quote-currency units per one base-currency unit**. For example, a EUR/USD rate of 1.0956 means one euro corresponds to 1.0956 US dollars in that reference observation.

For a non-EUR base, the actor computes `quote/EUR ÷ base/EUR` from observations on the **same date**. It does not substitute previous-day rates, interpolate weekends or manufacture missing observations. Same-currency pairs return 1 on observed publication days; EUR/EUR uses USD's ECB series solely as a publication-day anchor.

### What it returns

| Field | Type | Example value |
|---|---|---|
| `date` | string | `"2024-01-02"` |
| `baseCurrency` | string | `"EUR"` |
| `quoteCurrency` | string | `"USD"` |
| `rate` | number | `1.0956` |
| `kind` | string | `"reference"` |
| `status` | string | `"ok"` |
| `source` | string | `"ECB"` |
| `baseEurRate` | integer | `1` |
| `quoteEurRate` | number | `1.0956` |
| `sourceSeries` | array | `["EXR.D.USD.EUR.SP00.A"]` |
| `sourceUrls` | nullable / see code | `null` |
| `observationStatus` | nullable / see code | `null` |
| `fetchedAt` | nullable / see code | `null` |
| `startDate` | nullable / see code | `null` |
| `endDate` | nullable / see code | `null` |
| `errors` | array | `[]` |

The default dataset has a Daily rates view and a source-provenance view. A `SUMMARY` key-value record reports output counts, diagnostics, duration and whether an output/budget limit stopped the run.

### Input example

```json
{
  "startDate": "2024-01-02",
  "endDate": "2024-01-02",
  "baseCurrency": "EUR",
  "currencies": [
    "USD"
  ]
}
```

Use the input schema for the remaining filters and defaults.

### Source example

The saved [real ECB fixture](tests/fixtures/ecb_daily.csv), retrieved with the URL in [SOURCE.json](tests/fixtures/SOURCE.json), includes:

```csv
KEY,TIME_PERIOD,OBS_VALUE
EXR.D.USD.EUR.SP00.A,2024-01-02,1.0956
EXR.D.GBP.EUR.SP00.A,2024-01-02,0.86645
EXR.D.JPY.EUR.SP00.A,2024-01-02,155.68
```

These are selected columns from the actual source response. For USD/GBP, the resulting calculation is `0.86645 / 1.0956`, or approximately `0.7908451989777292`. The complete actor JSON sample is generated by `tools/acceptance.py` only after an actual local actor run. Its generation is currently blocked by this build session's process-launch failure; no actor output has been fabricated.

Output contract example for that observed EUR/USD value (selected fields, not a captured actor run):

```json
{
  "date": "2024-01-02",
  "baseCurrency": "EUR",
  "quoteCurrency": "USD",
  "rate": 1.0956,
  "kind": "reference",
  "status": "ok",
  "source": "ECB",
  "baseEurRate": 1,
  "quoteEurRate": 1.0956,
  "sourceSeries": ["EXR.D.USD.EUR.SP00.A"],
  "errors": []
}
```

### Pricing

Proposed event **`rate`: $0.05 per block of up to 100 successful rows**. The final partial block rounds up to one event; 1–100 successful rows cost $0.05, and 1,000 cost $0.50. Diagnostics add no rate events. Prices are proposals awaiting owner publication and configuration; see [PRICING.md](PRICING.md) for the live Store comparison and rounding caveat.

### Limits and source behavior

- These are reference observations, not executable trading quotes. ECB [discourages their use for transaction purposes](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html).
- Only currencies and dates covered by ECB are available. Coverage changes over time; historical discontinued currencies can have shorter ranges.
- Weekends, holidays, missing base/quote observations and delayed current-day publication cannot produce a successful pair rate.
- Dates are requested in 90-day windows. Rows are ordered by window, observation date and target input order; range-level errors follow dated rows in that window.
- The cap includes diagnostic rows. A small cap can leave later currencies/dates unreturned.
- Requests use bounded concurrency and three attempts with backoff for throttling, server errors, transport failures or invalid source bodies. An exhausted source failure becomes a diagnostic for affected pairs.
- Each run is independent; restarting a run does not promise cross-run deduplication or billing recovery.

### FAQ

**What limits the output?** The maxItems input caps output rows; its schema maximum is 100,000. Source availability and errors may yield fewer rows.

**Does it need a proxy, login, or API key?** The actor calls public ECB endpoints without an API key, login, or proxy input.

**How is it priced?** PRICING.md proposes $0.05 per block of up to 100 successful rate rows, including a final partial block. This is not an applied Store price.

**What happens on rate limits?** The ECB client retries throttling and server failures with backoff and Retry-After. Missing observations are not filled from another date.

**How fresh is the data?** The date is the ECB observation date; fetchedAt is retrieval time. Missing dates are not interpolated.

**Are cross rates carried forward?** No. A cross rate uses observations published for the same date.

### Use with AI agents / MCP

An AI agent can call a published actor through the Apify MCP server or Apify API, pass the JSON input, then read the run’s default dataset. Check the actor’s published identifier and permissions in your Apify account.

Expose this actor through Apify's [MCP server](https://docs.apify.com/platform/integrations/mcp) after the owner publishes it. An agent can pass an explicit historical date range, base code and quote codes, then read the default dataset. Ask the agent to retain `date`, `sourceUrls`, `status` and `errors` when explaining results; `fetchedAt` is retrieval time, not the date of the rate. This package does not claim a tested hosted MCP integration.

### Local development and verification

The supplied input file lives at `storage/key_value_stores/default/INPUT.json`. On this Windows workspace, run from the actor directory:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
& C:/GritWork/earn/.venv/Scripts/python.exe -m src
& C:/GritWork/earn/.venv/Scripts/python.exe -m pytest -q -m "not live"
& C:/GritWork/earn/.venv/Scripts/python.exe -m pytest -q -m live
apify validate-schema
```

For the complete acceptance pass, use `& C:/GritWork/earn/.venv/Scripts/python.exe tools/acceptance.py`. It runs tests, validates schemas, executes real local actor probes and writes an actual <=20-row `sample_output.json` with evidence. It does not publish or install packages. For development elsewhere, create a separate environment from `requirements.txt` and use Python 3.12; pytest is a development-only prerequisite.

**Current verification status:** source fixture and Store research fetched successfully; Python tests, actor runs, CLI schema validation and independent review are blocked by process-launch access denial in this session. See [REPORT.md](REPORT.md).

# Actor input Schema

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

YYYY-MM-DD, from 1999-01-04. If omitted, use 29 days before endDate. ECB publication days only; no weekend carry-forward.

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

YYYY-MM-DD, no later than UTC today. If omitted, use UTC today. The current day's observation may not yet have been published.

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

Three-letter code such as EUR, USD or GBP. Each output rate is quote-currency units per one base unit. Non-EUR rates require both series on the same day.

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

One to 50 three-letter codes. Case and whitespace are normalized; duplicates are removed. Unsupported or unavailable pairs return free diagnostic rows.

## `maxItems` (type: `integer`):

Hard limit on total output, including diagnostic rows. Rows are ordered by source window, then date and the quote-currency input order.

## `concurrency` (type: `integer`):

Maximum simultaneous HTTP requests to the official ECB API. A shared request limit also applies to retries.

## Actor input object example

```json
{
  "startDate": "2024-01-02",
  "endDate": "2024-01-05",
  "baseCurrency": "EUR",
  "currencies": [
    "USD",
    "GBP",
    "JPY"
  ],
  "maxItems": 1000,
  "concurrency": 3
}
```

# Actor output Schema

## `dataset` (type: `string`):

Open the default dataset items. A rate is quote-currency units per one base-currency unit.

# 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 = {
    "startDate": "2024-01-02",
    "endDate": "2024-01-05"
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/ecb-fx-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 = {
    "startDate": "2024-01-02",
    "endDate": "2024-01-05",
}

# Run the Actor and wait for it to finish
run = client.actor("grit-77/ecb-fx-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 '{
  "startDate": "2024-01-02",
  "endDate": "2024-01-05"
}' |
apify call grit-77/ecb-fx-rates --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/ecb-fx-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/oB3ljBeSldG2JWI40/builds/Bd6FBmwRmAFiiywhx/openapi.json
