# Exchange Rate Scraper — 30+ Fiat FX, Historical Deltas, CSV (`knotless_cadence/exchange-rate-scraper`) Actor

34 runs · 100% ok past 30d (30/30). Daily FX rates CSV/JSON — 30+ currencies via ECB/Frankfurter. Historical delta + % change. No API key. Treasury / cross-border pricing / close. Trustpilot 968r + 32-actor portfolio (2190 lifetime). dev.to/0012303 · blog.spinov.online · t.me/scraping\_ai

- **URL**: https://apify.com/knotless\_cadence/exchange-rate-scraper.md
- **Developed by:** [Alex](https://apify.com/knotless_cadence) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Exchange Rate Scraper — Real-Time & Historical Currency Conversion

Get real-time and historical exchange rates for ~32 world currencies. Powered by European Central Bank (ECB) data via the [Frankfurter API](https://www.frankfurter.app). Compare rates over time, calculate percentage changes, and export individual currency pairs. Free, no API key needed.

### Features

- **Real-Time Rates** — current exchange rates from the European Central Bank
- **~32 Currencies** — USD, EUR, GBP, JPY, CHF, CAD, AUD, and the rest of Frankfurter's supported set
- **Historical Comparison** — fetch rates from any past date (back to 1999) and calculate absolute + percentage changes
- **Currency Pairs** — outputs one flat `PAIR` record per `(base, target)` for easy filtering
- **Multiple Base Currencies** — query rates for several base currencies in one run
- **ECB Data Source** — institutional-grade rates, updated once per business day

### Output Example

Summary record (1 per base currency, optionally with historical fields):

```json
{
  "base": "USD",
  "date": "2026-03-18",
  "rates": {
    "EUR": 0.9234,
    "GBP": 0.7891,
    "JPY": 149.52,
    "CHF": 0.8812,
    "CAD": 1.3621
  },
  "ratesCount": 30,
  "historicalDate": "2026-01-01",
  "historicalRates": { "EUR": 0.9102, "GBP": 0.7800, "JPY": 148.10 },
  "changes": {
    "EUR": {
      "current": 0.9234,
      "historical": 0.9102,
      "change": 0.013200,
      "changePercent": 1.4505
    }
  },
  "scrapedAt": "2026-03-18T10:00:00.000Z"
}
```

PAIR record (one per target currency, **current rates only — historical rates do NOT produce PAIR records**):

```json
{
  "_type": "PAIR",
  "pair": "USD/EUR",
  "base": "USD",
  "target": "EUR",
  "rate": 0.9234,
  "date": "2026-03-18",
  "scrapedAt": "2026-03-18T10:00:00.000Z"
}
```

So a single base currency yields **1 summary + ~30 PAIR records per run** (PAIR counts reflect current rates only, regardless of `includeHistorical`).

### Use Cases

- **E-Commerce Pricing** — automatically update product prices based on current exchange rates
- **Financial Analysis** — track currency movements and calculate historical changes
- **Travel Budgeting** — compare conversion rates across multiple currencies at once
- **Accounting & Invoicing** — fetch daily rates for multi-currency bookkeeping
- **Forex Research** — monitor ECB rate trends and currency pair volatility
- **Data Pipelines** — feed structured rate data into dashboards and reporting tools

### Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `baseCurrencies` | array | `["USD"]` | Base currencies (e.g., "USD", "EUR", "GBP"). Auto-uppercased. |
| `targetCurrencies` | array | `[]` | Target currencies to filter (empty = all ~30 supported). Auto-uppercased. |
| `includeHistorical` | boolean | `false` | Fetch historical rates for comparison |
| `historicalDate` | string | `""` | Date for historical rates (YYYY-MM-DD) — required when `includeHistorical=true` |

### How It Works

The actor queries `https://api.frankfurter.app/latest?from={base}` for current rates per base currency, then optionally `https://api.frankfurter.app/{historicalDate}?from={base}` for historical rates. When historical comparison is enabled, it computes per-currency `change` (6-decimal absolute) and `changePercent` (4-decimal relative) for every currency present in BOTH responses.

### Honest disclosure on inputs and behavior

- **⚠️ Outer try/catch wraps the entire run** (`src/main.js` lines 23-89). A single failed fetch on any base currency (e.g. invalid currency code passed to Frankfurter, transient HTTP 5xx, network timeout) halts the entire run — remaining base currencies in the same batch are skipped silently. Historical fetches have their own inner try/catch, so historical-data errors don't kill the run, but the primary `/latest` call is unprotected.
- **No retry, no proxy.** Single `fetch()` per URL. Frankfurter has no published rate limit (it's a free best-effort service), but is not guaranteed against intermittent 502/504s during ECB-publish bursts.
- **`changes` is intersection-only.** If a currency exists in current but not historical (or vice versa), it's silently dropped from the `changes` map — no error field, no null sentinel. Frankfurter occasionally adds/removes currencies; date-spread runs on this boundary will skip those. Recover the original maps from `rates` and `historicalRates` if you need symmetric coverage.
- **Historical mode does NOT emit PAIR records.** PAIR records are pushed exclusively from `currentData.rates` (line 73). If you need flat PAIR records for the historical date too, request a custom build.
- **ECB weekend / holiday handling.** ECB publishes business-day rates only (Mon-Fri, ~16:00 CET). Frankfurter falls back to the most recent business-day rate for queries on weekends, holidays, or pre-publish hours. The `date` field reflects what Frankfurter actually returned, not your input date — check it explicitly if exact-date matching matters.
- **No crypto, no precious metals.** Frankfurter is fiat-only. For BTC/ETH use the [Crypto Price Scraper](https://apify.com/knotless_cadence/crypto-price-scraper); gold/silver require a separate paid data feed.

### Free Data APIs (No Key Required)

Part of a collection of no-auth-needed data tools:

| Tool | Data |
|------|------|
| [IP Geolocation](https://apify.com/knotless_cadence/ip-geolocation-lookup) | Country, city, ISP |
| [Weather Data](https://apify.com/knotless_cadence/weather-data-scraper) | Temperature, forecast |
| [Exchange Rates](https://apify.com/knotless_cadence/exchange-rate-scraper) | Currency conversion (this) |
| [Country Info](https://apify.com/knotless_cadence/country-info-scraper) | Population, languages, currencies |
| [Crypto Price Scraper](https://apify.com/knotless_cadence/crypto-price-scraper) | Crypto prices via CoinGecko |

All free on [Apify Store](https://apify.com/store?search=knotless_cadence).

### Need a custom build?

**Apify-as-a-Service tiers:**

- **Pilot — $97**: 1 actor, basic config, 7-day support. Good for one-off "EUR/USD weekly snapshot" cron.
- **Standard — $297**: custom actor + Slack/email alerts on threshold (e.g. "ping me when USD/JPY crosses 150"), 30-day support.
- **Premium — $797**: custom actor + dashboard + 90-day support + 1 modification round. For recurring multi-base, multi-date FX dashboards with historical PAIR records and retry+circuit-breaker.

**Email:** spinov001@gmail.com
**Portfolio:** [apify.com/knotless\_cadence](https://apify.com/knotless_cadence) — 31 published actors (78 total). Trustpilot 949+ runs, Reddit 80+, Email Extractor 30+. Recently delivered a paid 3-article series for a client in the proxy industry ($150).
**Blog (case studies):** https://blog.spinov.online
**Tips & tutorials:** [t.me/scraping\_ai](https://t.me/scraping_ai)

***

### Honest disclosure

- Public Frankfurter API only (ECB-backed) — no key, no auth, single-attempt fetch with no retry/proxy.
- ECB publishes rates once per business day around 16:00 CET; weekends/holidays return Friday's rate via Frankfurter fallback. The `date` field reflects what was returned, not the input.
- \~32 fiat currencies supported (no crypto, no precious metals).
- Outer try/catch — single base-currency failure halts the entire batch. Historical fetches have inner-try protection.
- `changes` is intersection-only — currencies missing from either date are silently dropped.
- PAIR records are emitted only from current rates, never from historical.
- Independent project — not affiliated with the ECB or Frankfurter.

# Actor input Schema

## `baseCurrencies` (type: `array`):

Base currencies to get rates for (e.g., 'USD', 'EUR', 'GBP')

## `targetCurrencies` (type: `array`):

Specific target currencies (empty = all available). E.g., 'EUR', 'JPY', 'GBP'

## `includeHistorical` (type: `boolean`):

Also fetch historical rates for comparison

## `historicalDate` (type: `string`):

Date for historical rates (YYYY-MM-DD format, e.g., '2025-01-01')

## Actor input object example

```json
{
  "baseCurrencies": [
    "USD"
  ],
  "targetCurrencies": [],
  "includeHistorical": false,
  "historicalDate": ""
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("knotless_cadence/exchange-rate-scraper").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("knotless_cadence/exchange-rate-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call knotless_cadence/exchange-rate-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=knotless_cadence/exchange-rate-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/r2tUANCIioX1KSQhL/builds/54qloxbPyLFJFudln/openapi.json
