IRS Yearly Average Exchange Rates: How to Compute and Automate Them
Every year US taxpayers with foreign income search for "IRS exchange rate 2025" and land on one page: the IRS's Yearly average currency exchange rates table. It is a useful convenience, and a frequently misunderstood one. This guide explains what the table is and is not, shows how to compute an equivalent yearly average yourself from an official daily or weekly series with Python and pandas, explains why your figure may differ slightly from the IRS's, and sets out when the Treasury, IRS and Federal Reserve series each apply.
What the IRS table is
The IRS page is explicit about its own status. In its words: "The Internal Revenue Service has no official exchange rate. Generally, it accepts any posted exchange rate that is used consistently." The general rule it states for translating foreign income and expenses is to use the exchange rate prevailing, that is the spot rate, when you receive, pay or accrue the item. The yearly average table is offered for convenience, and the page tells you how to use it: "To convert from foreign currency to U.S. dollars, divide the foreign currency amount by the applicable yearly average exchange rate in the table below."
So the figures are quoted as foreign currency units per one US dollar. From the table as published on irs.gov when this post was written, the 2025 column reads: Euro Zone, Euro, 0.886; United Kingdom, Pound, 0.759; Canada, Dollar, 1.398; Japan, Yen, 149.632; Switzerland, Franc, 0.831. The page does not state which source series the averages are computed from or how (simple or weighted, daily or less frequent observations), which matters when you try to reproduce them.
Which official series to average
For a dollar-based average you want a published US-dollar series with dated observations covering the whole year. Two are natural candidates, and both are on AllRatesToday with the current table keyless:
- Federal Reserve H.10. The H.10 release is the official US statistical series: the noon buying rates in New York for about 22 currencies against the dollar, certified by the Federal Reserve Bank of New York. The API carries it as a weekly-dated table, so a year gives you roughly 52 observations per currency.
- ECB reference rates. The ECB daily table quotes about 30 currencies against the euro on every TARGET business day, so EUR/USD gives about 255 daily observations a year. For other currencies against the dollar the API cross-computes through the euro and flags the result
derived: true, method: "cross".
Either is defensible if you name it in your working papers and use it consistently from year to year, which is the IRS's one stated condition. Here is the Fed series for USD→EUR, keyless, so you can see the shape before paying for history:
curl "https://allratestoday.com/api/open/central-bank/fed?source=USD&target=EUR" {
"bank": "fed",
"rate_date": "2026-10-16",
"source": "USD",
"target": "EUR",
"rate": 0.8903926632,
"rate_type": "reference",
"derived": true,
"method": "inverse",
"cadence": "weekly"
} Notice derived: true and method: "inverse": the Fed publishes EUR/USD as dollars per euro (1.1231 on this table), and the API inverts it to give the IRS-style euro-per-dollar quote you asked for. Ask for source=EUR&target=USD and you get the published figure with derived: false.
Compute the yearly average with pandas
A full year of history for one pair comes from the history endpoint, which is on paid plans. Pair mode returns one resolved rate per published date, ascending, as rates: [{date, rate, rate_type, derived, method}] with count and a truncated flag (the cap is 5,000 rows, far above a year of any series). The average is one line of pandas; the rest of the function records what was averaged:
import os, requests, pandas as pd
BASE = "https://allratestoday.com/api/v1/central-bank"
H = {"Authorization": f"Bearer {os.environ['ALLRATESTODAY_API_KEY']}"} # paid plan
def yearly_average(bank: str, currency: str, year: int) -> dict:
"""Simple average of the published observations for one calendar year.
Quoted IRS-style: foreign currency units per 1 USD."""
r = requests.get(
f"{BASE}/{bank}/history",
params={"source": "USD", "target": currency,
"from": f"{year}-01-01", "to": f"{year}-12-31"},
headers=H, timeout=30,
)
r.raise_for_status()
body = r.json()
df = pd.DataFrame(body["rates"]) # columns: date, rate, rate_type, derived, method
df["date"] = pd.to_datetime(df["date"])
return {
"bank": body["bank"],
"currency": currency,
"year": year,
"observations": int(body["count"]),
"first": df["date"].min().date().isoformat(),
"last": df["date"].max().date().isoformat(),
"average": round(df["rate"].mean(), 3),
"any_derived": bool(df["derived"].any()),
"truncated": body.get("truncated", False),
}
for ccy in ["EUR", "GBP", "CAD", "JPY"]:
print(yearly_average("fed", ccy, 2025)) Keep the returned dictionary with your return workpapers. It says which publisher, how many observations, the first and last dates covered, and whether any observation was a cross-computation. That is the difference between "an average I found" and "an average I can recompute in three years".
To use daily observations instead of weekly, swap the bank:
# Same function, ECB daily table. EUR/USD is published directly; other pairs
# against USD are cross-computed through the euro and flagged derived=True.
print(yearly_average("ecb", "EUR", 2025)) If you prefer a spreadsheet, add &format=csv or &format=xlsx to the same history URL and the file has one row per published date with the columns rate_date, source, target, rate, rate_type, derived, method:
=AVERAGE(C2:C260) ' column C = rate, one row per published date, downloaded with format=csv Comparing with the IRS figure
Run the function for 2025 and set the result next to the IRS's 0.886 for the euro. Expect the two to be close but not necessarily identical, for three reasons that have nothing to do with either being wrong:
- Observation frequency. A simple average of about 52 weekly H.10 observations weights each week equally; an average of about 255 daily ECB observations weights each business day equally. Over a year of movement the two differ in the third decimal place.
- Publication time. The H.10 noon New York rate and the ECB's early-afternoon Frankfurt rate are hours apart. On volatile days that shows up in the average.
- Method. The IRS page does not state its method, so there is no specification to match against. Treat agreement to two decimals as confirmation that both numbers describe the same year, not as a test one of them failed.
If the difference would change a tax figure materially, use the rate the instructions for that form name, or the spot rate on the transaction date, rather than any average. Averages are an approximation the IRS tolerates; they are not a right.
Treasury, IRS or Fed: which series when
| Purpose | Series commonly used | Why |
|---|---|---|
| FBAR (FinCEN 114) and Form 8938 year-end values | Treasury Reporting Rates of Exchange, December 31 table | The form instructions name the Bureau of the Fiscal Service rate for the last day of the year. See FBAR and Form 8938 exchange rates. |
| Foreign salary, pension, rental income on Form 1040 | Spot rate when received, or a yearly average used consistently (IRS table, or your own from an official series) | The IRS accepts any posted rate used consistently; the average is a convenience for items spread evenly across the year. |
| Reporting to or reconciling with a federal agency | Treasury Reporting Rates, the quarter's table | Agencies must use it; contractors and grantees match it so figures reconcile. |
| US GAAP financial statements (ASC 830) | Federal Reserve H.10 or a documented mid-market policy | Spot at transaction date, current rate at balance-sheet date, average for the income statement. See ASC 830 exchange rates. |
| A single transaction on a known date | The published table for that date | The dated endpoint returns the table in force on the date and says whether it was published that day. |
The common thread is that each purpose names a publisher and a date convention, and consistency matters more than which defensible series you pick. The thing to avoid is mixing them: a yearly average for income, a converter screenshot for the FBAR, and a bank statement rate for a property sale, in the same return.
Automating it each January
The yearly job is small: once the final observation of the year is published (the last H.10 release of December, or the ECB table for the last TARGET business day), run the function for each currency you hold, write the dictionaries to a file dated that day, and file it with the year's tax papers. A scheduled script on the first working day of January does it without anyone remembering. For the FBAR year-end value, the companion post shows the Treasury table call that belongs in the same script.
FAQ
Does the IRS require me to use its yearly average table?
No. The IRS says it has no official exchange rate and generally accepts any posted exchange rate that is used consistently. The yearly average table is a convenience it publishes. Whether an average is appropriate for a given item at all, rather than the rate on the date received or paid, is a question for your tax adviser.
Does AllRatesToday republish the IRS yearly average table?
No. We serve the underlying official series the averages are built from, such as the Federal Reserve H.10 release and the Treasury Reporting Rates of Exchange, with their publication dates, and show you how to compute an average you can reproduce. For the IRS's own figures, use the IRS page and cite it.
Will my computed average match the IRS figure exactly?
Usually close, not always identical. The IRS page does not state which source series or averaging method it uses, and a simple average of weekly H.10 observations will differ slightly from an average of daily observations or a trade-weighted figure. Whatever you compute, keep the series and the method with the result so it can be recomputed.
Is the history endpoint free?
The current table from each publisher is keyless. Pulling a year of history for a pair is on paid plans, billed as one call per currency per month of data. A free account carries 300 free requests to the market-rate endpoints, which does not include central-bank history.
A year of official history per pair
Fed H.10, ECB and Treasury series by date on paid plans, in JSON, CSV or XLSX, with the publication date on every row.
See plansRelated Articles
- FBAR and Form 8938 Exchange Rates: The Treasury Reporting Rate, via API
- Exchange Rates for ASC 830 Remeasurement and Translation
- Federal Reserve H.10 exchange rate API — today’s table and endpoints
- Fed H.10 Exchange Rate API: Official USD Rates as JSON
- US Treasury reporting rates API
- Exchange rates for finance teams