The two free-tier rate APIs every currency-converter tutorial was built on now refuse keyless requests outright (verified Aug 2026):
$ curl https://data.fixer.io/api/latest
{ "success": false,
"error": { "code": 101, "type": "missing_access_key", โฆ } }
$ curl https://api.exchangeratesapi.io/v1/latest
{ "error": { "code": "missing_access_key", โฆ } }
So the old tutorial code needs a signup, a key, and a plan for the monthly cap โ for an app whose real job is usually "multiply an amount by a number".
๐ก We check exchangerate.host (now key-walled โ it answers HTTP 200 with an error envelope, so we probe the body) and keyless Frankfurter, along with 35 other public mock/testing services, every 30 minutes โ live status page.
And if the API call lives in your tests, there's a second problem that no key fixes: live rates are non-deterministic. The assertion expect(convert(100, "USD", "EUR")).toBe(85.89) is true today and false tomorrow. A currency-conversion test suite wants rates that hold still.
Below: one curl that hosts a 30-currency dataset (ECB reference rates, a fixed dated snapshot) as your own API. No key, no monthly cap, CORS on, filters/sort/search/pagination, GraphQL, and full CRUD โ so you can pin JPY = 150.0 and your expected values stay exact forever.
We host a ready-made currencies dataset โ the euro plus the 29 currencies of the ECB daily reference list: ISO code, name, symbol, minor-unit decimals, rate per 1 EUR, rate per 1 USD (derived), and the snapshot date. Pipe it into the importer:
curl -s https://mockbird.mockbird.workers.dev/data/currencies.db.json \
| curl -s -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?name=currencies' \
--data-binary @-
The response has your project id and an adminKey (save it). Your API is live immediately, callable from browser JavaScript:
https://mockbird.mockbird.workers.dev/m/YOUR_ID/currencies
# one currency by ISO code
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/currencies?code=JPY'
# โ [{"code":"JPY","name":"Japanese Yen","symbol":"ยฅ","decimals":0,
# "rate_eur":185.92,"rate_usd":159.68โฆ,"asOf":"2026-08-28","id":16}]
# free-text search: the six dollars
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/currencies?q=dollar&select=code,name'
# โ AUD, CAD, HKD, NZD, SGD, USD
# zero-decimal currencies โ the classic money-formatting edge case
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/currencies?decimals=0&select=code,name'
# โ ISK, JPY, KRW
# range + sort: currencies weaker than 100-per-euro, weakest first
curl 'https://mockbird.mockbird.workers.dev/m/YOUR_ID/currencies?rate_eur_gte=100&sortBy=rate_eur&order=desc&select=code,rate_eur'
Conversion is one line of your own code against the snapshot โ no /convert endpoint, no per-call metering:
const rates = Object.fromEntries(list.map(c => [c.code, c.rate_eur]));
const convert = (amount, from, to) => amount * rates[to] / rates[from];
The dataset is a dated snapshot (every record carries asOf), and that's the feature, not the bug, for anything that isn't live commerce:
PATCH /currencies/16 {"rate_eur":180.0} โ writes persist, and now every expected value in your conversion tests is a round, stable number you chose.beforeEach, or pin a scenario per test with ?mock_snapshot= โ parallel workers can't race.?mock_status=401 replays the missing-key case, ?mock_status=429 the cap, ?mock_seq=429,429,200 drills the retry, ?mock_delay=2000 the slow morning. All simulation params work on this project.{ currencies(where:{decimals:0}) { code name rate_eur } } at /m/YOUR_ID/graphql, with GraphiQL in the browser.GET /m/YOUR_ID/db.json returns the whole dataset in json-server format โ no lock-in.| fixer.io / exchangeratesapi.io | Frankfurter | Your Mockbird project | |
|---|---|---|---|
| API key | Required โ keyless requests are rejected | None | None |
| Rates | Live (frequency depends on plan) | Live daily (ECB reference) | Fixed dated snapshot (ECB reference) |
| Deterministic for tests | โ changes constantly | โ changes daily | โ frozen until you change it |
| Editable / pin a rate | โ | โ | โ full CRUD |
| Failure simulation | โ | โ | โ status/delay/chaos/sequence params |
| Requests | metered monthly plans | free | 10,000/day per project |
asOf date โ don't present a snapshot as live market data.)The dataset contains the European Central Bank's euro foreign exchange reference rates for the date in each record's asOf field (the ECB publishes these freely; they are indicative reference rates, not tradable quotes). rate_usd is derived by cross-rate from the EUR table. Names, symbols, and minor-unit decimals follow ISO 4217 usage. It's your copy โ PATCH anything, add currencies the ECB list omits, or re-import a fresh snapshot whenever you like.
The one-liner in section 1 is the whole setup โ or open the dashboard and paste the dataset into the import panel. Pair it with the countries (each country record carries its currency) or timezones datasets, mock the payment API that consumes these rates, build the rest of a finance dashboard with a mock stock API, or host any CSV as an API.