YFRateLimitError: Too Many Requests. Rate limited. Try after a while. means Yahoo answered your IP with HTTP 429. yfinance has no account or quota, so Yahoo throttles by network. Upgrade yfinance and let it manage its own session, fetch many tickers per call, cache what you already have, and retry slowly. If a job must not fail, use an API that sells a stated quota.
Why it happens
yfinance is not an official API. It calls the web endpoints the Yahoo Finance site uses, and Yahoo has had no public finance API since 2017. Yahoo publishes no limits for those endpoints and blocks traffic that looks automated. Users in the yfinance tracker report:
- blocks after about 100 quick requests that cleared after a 30-second pause (discussion #2431);
- the error continuing across releases 0.2.57 to 0.2.59 (issue #2480);
- the same code working on Google Colab and failing on a home connection, or the reverse, because Yahoo judges each IP separately (QuantInsti forum).
No setting turns the limit off. You can only hit it less often.
1. Upgrade, and drop your own requests session
Run pip install -U yfinance. Version 1.7.0 depends on curl_cffi, which sends requests with a browser fingerprint. Old code often passes session=requests.Session(), usually for requests_cache. Remove it. In our test on 2026-10-29, yfinance 1.7.0 with a plain requests session got YFRateLimitError on the first call. The same call without the session argument returned data from the same machine and IP.
2. Fetch many tickers in one call
import yfinance as yf
# One call for many tickers instead of a Ticker() loop
df = yf.download(["AAPL", "MSFT", "NVDA", "AMZN"], period="1mo", group_by="ticker", threads=False, progress=False)A loop over yf.Ticker(t).info costs at least one request per ticker. If you only need prices, download with a list costs far fewer.
3. Cache what you already have
Past daily bars don’t change. Save them once, then fetch only the days you’re missing.
from pathlib import Path
import pandas as pd, yfinance as yf
def history(symbol: str) -> pd.DataFrame:
path = Path(f"cache/{symbol}.parquet")
if path.exists():
old = pd.read_parquet(path)
new = yf.download(symbol, start=old.index[-1], progress=False, multi_level_index=False)
df = pd.concat([old, new]).loc[lambda d: ~d.index.duplicated(keep="last")]
else:
path.parent.mkdir(exist_ok=True)
df = yf.download(symbol, period="max", progress=False, multi_level_index=False)
df.to_parquet(path)
return df4. Retry slowly, and check for empty results
yf.download does not raise on a 429. It logs the error and returns an empty DataFrame, so a try/except YFRateLimitError around it never fires. Retry on df.empty instead, and wait longer each time.
import time, yfinance as yf
def fetch(symbols, chunk=20, pause=2):
frames = []
for i in range(0, len(symbols), chunk):
batch = symbols[i:i + chunk]
for wait in (0, 30, 120, 600):
time.sleep(wait)
# download() logs a 429 and returns an empty frame; it does not raise
df = yf.download(batch, period="5d", threads=False, progress=False)
if not df.empty:
frames.append(df)
break
time.sleep(pause)
return framesWhat makes it worse
- A plain
requestssession or hand-set user agents. They override the browser fingerprint that curl_cffi sends. - Free proxy lists. Many of those IPs are already blocked, and your queries pass through a stranger’s server.
- Retrying in a tight loop. Every retry counts toward the same limit.
When to stop fighting it
yfinance is fine for notebooks and research. Switch when a scheduled job breaks every time Yahoo blocks you for a day, when you run on cloud servers whose IP ranges many users share, or when you need a language other than Python.
There are two kinds of replacement. A market-data vendor such as Polygon, Twelve Data or Alpha Vantage uses its own feeds and its own field names. A hosted Yahoo Finance API returns Yahoo’s JSON fields under a stated quota. Yahoo Finance Turbo is one; it is a third-party service, not affiliated with Yahoo. Its free plan has 500 requests a month at 5 a second, and paid plans start at $9.95 for 10,000. The yfinance migration table maps common yfinance calls to Turbo endpoints.
import os, requests
r = requests.get(
"https://yahoo-finance-turbo.p.rapidapi.com/stock/v3/get-chart",
params={"symbol": "AAPL", "interval": "1d", "range": "1mo", "region": "US"},
headers={
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": "yahoo-finance-turbo.p.rapidapi.com",
},
timeout=30,
)
r.raise_for_status()curl --request GET \
--url 'https://yahoo-finance-turbo.p.rapidapi.com/stock/v3/get-chart?symbol=AAPL&interval=1d&range=1mo®ion=US' \
--header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \
--header 'X-RapidAPI-Host: yahoo-finance-turbo.p.rapidapi.com'Yahoo Finance Turbo
37 endpoints, a stated quota, free plan of 500 requests a month.