A common bug in invoicing and bookkeeping code goes like this:
rate = get_live_rate("USD", "EUR") # mid-market, right now invoice.eur_total = invoice.usd_total * rate It works and the number looks plausible, but it is often wrong in the sense that matters: an auditor or tax office will not accept it.
What the rules actually ask for
VAT, customs and statutory accounts rarely say "use the current market rate". They name a source and a date:
- EU VAT usually points to the European Central Bank rate on the tax point date.
- UK HMRC issues its own monthly rates for customs and VAT.
- India uses the CBIC notified rates for customs and RBI/FBIL reference rates for many filings.
- The US IRS gives yearly average rates for some reporting, and the US Treasury sets quarterly rates for federal agencies.
All of these are official rates: one figure per day (or month, or quarter), set by a named institution and never revised. A live feed that changes every minute cannot reproduce the figure on a past date, and it is not the named source anyway.
Fetching the rate the rule names
AllRatesToday serves official rates from 123 central banks and 6 tax authorities through one endpoint shape:
import requests KEY = "your_api_key" H = {"Authorization": f"Bearer {KEY}"} # ECB rate in force on a given invoice date (dated lookups are on paid plans) r = requests.get( "https://allratestoday.com/api/v1/central-bank/ecb/2026-06-30", params={"source": "USD", "target": "EUR"}, headers=H, ).json() print(r["rate"], r["rate_date"]) If the institution did not set a rate that day (a weekend or holiday), the response returns the most recent earlier rate_date. That is the rate compliance rules expect you to apply, and the response says which date it is.
Swap ecb for hmrc, cbic, irs, ustreasury, bazg (Swiss customs) or sunat (Peru) and the code stays the same.
Keep the mid-market rate too
None of this makes the live rate useless. Use it to show prices and estimates. Use the official rate to book them. Many teams store both on each transaction: the live rate shows what the customer saw, and the official rate is what the ledger records.
txn = { "amount_usd": 1200, "display_rate": live_rate, # /api/v1/rates "book_rate": r["rate"], # official "book_rate_source": "ecb", "book_rate_date": r["rate_date"], } Both layers come from the same API key, which is the point: one integration, two kinds of rate, no mismatched providers.
Docs: allratestoday.com/docs · Free key: allratestoday.com/register
