Every free and paid API for market prices compared head-to-head. From Yahoo Finance's legendary free tier to Polygon.io's institutional WebSocket feeds — find the right data source for your stack.
Every trading system, every dashboard, every financial model starts with the same primitive: price data. Whether you need end-of-day OHLCV bars for backtesting or sub-second WebSocket ticks for a live scalper, the API you choose determines your ceiling.
The market for price data APIs has exploded since 2020. Where once you had Bloomberg Terminal or nothing, today a solo developer can access institutional-grade data for under $30/month — or entirely free with some trade-offs.
OHLCV stands for Open, High, Low, Close, Volume — the five fundamental data points for each time period (candle). Open is the first trade price, High/Low are the extremes, Close is the last trade price, and Volume is the total number of shares or contracts traded. Every candlestick chart you've ever seen is built from OHLCV data.
Daily OHLCV bars, updated after market close. Best for swing trading, backtesting, and portfolio tracking. Often free.
1-min to 1-hour bars during market hours. Needed for day trading strategies and intraday analytics. Usually paid.
Sub-second WebSocket feeds with every trade and quote. Required for market making, HFT, and live dashboards. Premium pricing.
The undisputed king of free financial data. Yahoo Finance isn't technically an API — yfinance is a Python library that scrapes Yahoo's internal endpoints. It provides OHLCV data going back 20+ years for thousands of tickers, plus dividends, splits, financials, and options chains.
import yfinance as yf # Download 5 years of daily OHLCV for Apple ticker = yf.Ticker("AAPL") hist = ticker.history(period="5y") # Access OHLCV columns print(hist[["Open", "High", "Low", "Close", "Volume"]].tail()) # Get intraday data (1-minute bars, last 7 days max) intraday = ticker.history(period="5d", interval="1m") # Multiple tickers at once data = yf.download(["AAPL", "MSFT", "GOOGL"], period="1y") print(data["Close"].tail()) # Dividends and splits print(ticker.dividends) print(ticker.splits) # Options chains opts = ticker.options # list of expiration dates chain = ticker.option_chain(opts[0]) print(chain.calls.head())
Yahoo Finance's Terms of Service explicitly prohibit automated scraping. However, yfinance has been the most popular financial data library on PyPI for years with 300K+ weekly downloads. Yahoo has not taken legal action against individual users, but they do periodically change their internal endpoints, breaking the library temporarily. Never use yfinance for commercial products — use it for personal research and prototyping, then migrate to a licensed API for production.
Yahoo's rate limiting is undocumented and changes frequently. Based on community testing:
yf.download() are more efficient than individual Ticker.history() callsOne of the oldest dedicated financial data APIs. Alpha Vantage offers a generous free tier (25 requests/day) and covers stocks, forex, crypto, commodities, and economic indicators. The premium tiers unlock higher rate limits and intraday data with extended history.
import requests import pandas as pd API_KEY = "YOUR_API_KEY" # Free key from alphavantage.co # Daily OHLCV — full history url = f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=AAPL&outputsize=full&apikey={API_KEY}" data = requests.get(url).json() # Parse into DataFrame ts = data["Time Series (Daily)"] df = pd.DataFrame.from_dict(ts, orient="index") df.index = pd.to_datetime(df.index) df = df.astype(float).sort_index() print(df.tail()) # Intraday 5-minute bars (premium only for extended history) url_intra = f"https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=AAPL&interval=5min&apikey={API_KEY}" intra = requests.get(url_intra).json() # Technical indicator: RSI url_rsi = f"https://www.alphavantage.co/query?function=RSI&symbol=AAPL&interval=daily&time_period=14&series_type=close&apikey={API_KEY}" rsi = requests.get(url_rsi).json()
const API_KEY = 'YOUR_API_KEY'; // Fetch daily OHLCV async function getDailyData(symbol) { const url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=${symbol}&outputsize=compact&apikey=${API_KEY}`; const res = await fetch(url); const data = await res.json(); const timeSeries = data['Time Series (Daily)']; // Transform to array of {date, open, high, low, close, volume} return Object.entries(timeSeries).map(([date, vals]) => ({ date, open: parseFloat(vals['1. open']), high: parseFloat(vals['2. high']), low: parseFloat(vals['3. low']), close: parseFloat(vals['4. close']), volume: parseInt(vals['6. volume']) })); } getDailyData('AAPL').then(d => console.log(d.slice(0, 5)));
The gold standard for developers who need institutional-grade data. Polygon.io provides real-time and historical data for stocks, options, forex, and crypto with excellent WebSocket streaming. Their Stocks Starter plan ($29/mo) is arguably the best value in the market.
import requests from datetime import datetime, timedelta API_KEY = "YOUR_POLYGON_KEY" # Aggregates (bars) — daily OHLCV for AAPL end = datetime.now().strftime("%Y-%m-%d") start = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d") url = f"https://api.polygon.io/v2/aggs/ticker/AAPL/range/1/day/{start}/{end}?apiKey={API_KEY}" data = requests.get(url).json() for bar in data["results"][:3]: print(f"Date: {bar['t']}, O: {bar['o']}, H: {bar['h']}, L: {bar['l']}, C: {bar['c']}, V: {bar['v']}") # Intraday 1-minute bars url_1m = f"https://api.polygon.io/v2/aggs/ticker/AAPL/range/1/minute/2026-03-05/2026-03-05?apiKey={API_KEY}" intra = requests.get(url_1m).json() # Real-time snapshot snap = requests.get(f"https://api.polygon.io/v2/snapshot/locale/us/markets/stocks/tickers/AAPL?apiKey={API_KEY}").json()
import websocket import json def on_message(ws, message): data = json.loads(message) for event in data: if event["ev"] == "T": # Trade event print(f"{event['sym']} — Price: ${event['p']}, Size: {event['s']}") def on_open(ws): # Authenticate ws.send(json.dumps({"action": "auth", "params": API_KEY})) # Subscribe to AAPL trades ws.send(json.dumps({"action": "subscribe", "params": "T.AAPL"})) ws = websocket.WebSocketApp( "wss://socket.polygon.io/stocks", on_message=on_message, on_open=on_open ) ws.run_forever()
Polygon.io uses the SIP (Securities Information Processor) feed — the consolidated tape that includes all trades from all US exchanges. This is the same feed that Bloomberg and Reuters use. Some cheaper providers use the IEX-only feed, which only shows trades executed on the IEX exchange (about 3-5% of total volume). SIP data is more accurate for VWAP, volume analysis, and fair price discovery.
A hidden gem in the API landscape. Tiingo offers a generous free tier with 500 unique symbols per month, real-time IEX data, and 20+ years of EOD history. The free WebSocket feed streams IEX trades and quotes — perfect for hobby projects.
import requests headers = {"Content-Type": "application/json", "Authorization": f"Token {TIINGO_KEY}"} # EOD prices url = "https://api.tiingo.com/tiingo/daily/AAPL/prices?startDate=2025-01-01" data = requests.get(url, headers=headers).json() for d in data[:3]: print(f"{d['date'][:10]} — Close: ${d['adjClose']:.2f}, Vol: {d['volume']:,}") # IEX real-time iex_url = "https://api.tiingo.com/iex/AAPL" realtime = requests.get(iex_url, headers=headers).json() print(f"Last: ${realtime[0]['last']}, Bid: ${realtime[0]['bidPrice']}, Ask: ${realtime[0]['askPrice']}")
Built on top of the IEX exchange data, IEX Cloud provides a clean, well-documented API. The Launch plan ($9/mo) gives you 50K messages/month. Excellent for dashboards and mid-frequency trading systems. Their SSE (Server-Sent Events) streaming is simple to implement.
Twelve Data offers one of the most generous free tiers: 800 API requests per day with 8 concurrent WebSocket connections. They cover stocks, forex, crypto, ETFs, and indices across 50+ exchanges globally. Their technical indicator library has 100+ indicators built into the API.
const WebSocket = require('ws'); const ws = new WebSocket('wss://ws.twelvedata.com/v1/quotes/price?apikey=YOUR_KEY'); ws.on('open', () => { ws.send(JSON.stringify({ action: 'subscribe', params: { symbols: 'AAPL,MSFT,GOOGL,BTC/USD' } })); }); ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.event === 'price') { console.log(`${msg.symbol}: $${msg.price} (${msg.timestamp})`); } });
Finnhub is the Swiss army knife of financial APIs. The free tier includes 60 API calls/minute, free WebSocket for US stocks, and an impressive range of alternative data (social sentiment, insider transactions, congressional trades). Particularly strong for crypto coverage.
import finnhub import time client = finnhub.Client(api_key="YOUR_FINNHUB_KEY") # Real-time quote quote = client.quote("AAPL") print(f"Current: ${quote['c']}, Change: {quote['dp']:.2f}%") # Candles (OHLCV) now = int(time.time()) one_year_ago = now - 365 * 86400 candles = client.stock_candles("AAPL", "D", one_year_ago, now) print(f"Got {len(candles['c'])} daily bars") # Company news news = client.company_news("AAPL", _from="2026-03-01", to="2026-03-06") for n in news[:3]: print(f" {n['headline']}")
The budget champion for global coverage. EODHD covers 60+ exchanges worldwide (including obscure ones like Johannesburg, Karachi, and Bangkok) at just $20/month. If you need international EOD data for backtesting a global portfolio, EODHD is hard to beat on price-per-exchange.
import requests API_KEY = "YOUR_EODHD_KEY" # EOD data for a European stock (Airbus on Paris exchange) url = f"https://eodhd.com/api/eod/AIR.PA?api_token={API_KEY}&fmt=json&from=2025-01-01" data = requests.get(url).json() for d in data[:3]: print(f"{d['date']} — Close: {d['adjusted_close']}, Vol: {d['volume']:,}") # Bulk EOD for entire exchange (e.g., NYSE) bulk_url = f"https://eodhd.com/api/eod-bulk-last-day/US?api_token={API_KEY}&fmt=json" bulk = requests.get(bulk_url).json() print(f"Got {len(bulk)} tickers from NYSE") # Crypto (BTC on multiple exchanges) btc_url = f"https://eodhd.com/api/eod/BTC-USD.CC?api_token={API_KEY}&fmt=json&from=2025-01-01" btc = requests.get(btc_url).json()
| API | Free Tier | Paid From | Rate Limit (Free) | WebSocket | History Depth | Coverage | Data Feed |
|---|---|---|---|---|---|---|---|
| Yahoo (yfinance) | Yes | N/A | ~2K req/hr | No | 20+ years | Global (60+ exch.) | Delayed 15min |
| Alpha Vantage | Yes | $50/mo | 25 req/day | No | 20+ years | US + Forex + Crypto | Delayed 15min |
| Polygon.io | Yes | $29/mo | 5 req/min | Yes (paid) | 15+ years | US only | SIP (real-time) |
| Tiingo | Yes | $10/mo | 500 sym/mo | Yes (IEX) | 20+ years | US + Crypto + Forex | IEX real-time |
| IEX Cloud | No | $9/mo | N/A | Yes (SSE) | 15+ years | US + limited intl | IEX real-time |
| Twelve Data | Yes | $29/mo | 800 req/day | Yes (delayed) | 30+ years | Global (50+ exch.) | Delayed / RT paid |
| Finnhub | Yes | $49/mo | 60 req/min | Yes (free!) | Limited | US + Crypto + Forex | Real-time WS |
| EODHD | No | $20/mo | N/A | Yes (paid) | 30+ years | Global (60+ exch.) | Delayed / RT paid |
| Use Case | Best Choice | Runner-Up | Why |
|---|---|---|---|
| Backtesting (EOD) | Yahoo (yfinance) | Twelve Data | Free, 20+ years, adjusted data, multi-ticker download |
| Live Dashboard | Finnhub | Twelve Data | Free WebSocket, 60 req/min for REST fallback |
| Production App | Polygon.io ($29) | IEX Cloud ($9) | SIP feed, proper SLA, excellent docs, flat files |
| Global Portfolio | EODHD ($20) | Twelve Data | 60+ exchanges, cheap, bulk download |
| Crypto Focus | Finnhub | Tiingo | Free real-time crypto WS, wide exchange coverage |
| Options Data | Polygon.io ($199) | Yahoo (yfinance) | Full chain + Greeks + flow (see Part 4) |
| Technical Indicators | Alpha Vantage | Twelve Data | 100+ indicators built into the API |
| Zero Budget | Yahoo + Finnhub | Twelve Data + Tiingo | Stack free tiers: Yahoo for history, Finnhub for real-time |
Instead of coupling your code to a single API, build an abstraction layer. Here's a universal price fetcher that works with any provider:
from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime from typing import List, Optional import pandas as pd @dataclass class Bar: timestamp: datetime open: float high: float low: float close: float volume: int class PriceProvider(ABC): """Abstract base for all price data providers.""" @abstractmethod def get_bars(self, symbol: str, start: str, end: str, interval: str = "1d") -> List[Bar]: pass @abstractmethod def get_quote(self, symbol: str) -> dict: pass def to_dataframe(self, bars: List[Bar]) -> pd.DataFrame: return pd.DataFrame([vars(b) for b in bars]).set_index("timestamp")
import yfinance as yf class YahooProvider(PriceProvider): def get_bars(self, symbol, start, end, interval="1d"): ticker = yf.Ticker(symbol) df = ticker.history(start=start, end=end, interval=interval) return [ Bar(timestamp=idx, open=row["Open"], high=row["High"], low=row["Low"], close=row["Close"], volume=int(row["Volume"])) for idx, row in df.iterrows() ] def get_quote(self, symbol): info = yf.Ticker(symbol).info return { "price": info.get("regularMarketPrice"), "change": info.get("regularMarketChangePercent"), "volume": info.get("regularMarketVolume"), }
import requests from datetime import datetime class PolygonProvider(PriceProvider): def __init__(self, api_key: str): self.api_key = api_key self.base = "https://api.polygon.io" def get_bars(self, symbol, start, end, interval="1d"): # Map interval: "1d" -> (1, "day"), "1h" -> (1, "hour") multiplier = int(interval[:-1]) if interval[:-1].isdigit() else 1 timespan = {"d": "day", "h": "hour", "m": "minute"}[interval[-1]] url = f"{self.base}/v2/aggs/ticker/{symbol}/range/{multiplier}/{timespan}/{start}/{end}" resp = requests.get(url, params={"apiKey": self.api_key, "limit": 50000}).json() return [ Bar( timestamp=datetime.fromtimestamp(r["t"] / 1000), open=r["o"], high=r["h"], low=r["l"], close=r["c"], volume=r["v"] ) for r in resp.get("results", []) ] def get_quote(self, symbol): url = f"{self.base}/v2/snapshot/locale/us/markets/stocks/tickers/{symbol}" data = requests.get(url, params={"apiKey": self.api_key}).json() tick = data["ticker"] return { "price": tick["lastTrade"]["p"], "change": tick["todaysChangePerc"], "volume": tick["day"]["v"], }
# Switch providers with one line provider = YahooProvider() # Free, for prototyping # provider = PolygonProvider("key") # Paid, for production # Same interface regardless of provider bars = provider.get_bars("AAPL", "2025-01-01", "2026-03-01") df = provider.to_dataframe(bars) # Calculate SMA df["SMA_20"] = df["close"].rolling(20).mean() df["SMA_50"] = df["close"].rolling(50).mean() # Golden cross detection df["signal"] = (df["SMA_20"] > df["SMA_50"]).astype(int) crosses = df[df["signal"].diff() == 1] print(f"Golden crosses found: {len(crosses)}")
class PriceFetcher { constructor(provider = 'finnhub', apiKey = '') { this.provider = provider; this.apiKey = apiKey; } async getBars(symbol, from, to) { switch (this.provider) { case 'polygon': return this._polygonBars(symbol, from, to); case 'finnhub': return this._finnhubBars(symbol, from, to); case 'twelvedata': return this._twelvedataBars(symbol, from, to); } } async _polygonBars(symbol, from, to) { const url = `https://api.polygon.io/v2/aggs/ticker/${symbol}/range/1/day/${from}/${to}?apiKey=${this.apiKey}`; const data = await fetch(url).then(r => r.json()); return data.results.map(r => ({ date: new Date(r.t), open: r.o, high: r.h, low: r.l, close: r.c, volume: r.v })); } async _finnhubBars(symbol, from, to) { const f = Math.floor(new Date(from).getTime() / 1000); const t = Math.floor(new Date(to).getTime() / 1000); const url = `https://finnhub.io/api/v1/stock/candle?symbol=${symbol}&resolution=D&from=${f}&to=${t}&token=${this.apiKey}`; const data = await fetch(url).then(r => r.json()); return data.t.map((ts, i) => ({ date: new Date(ts * 1000), open: data.o[i], high: data.h[i], low: data.l[i], close: data.c[i], volume: data.v[i] })); } } // Usage const fetcher = new PriceFetcher('finnhub', 'YOUR_KEY'); const bars = await fetcher.getBars('AAPL', '2025-01-01', '2026-03-01'); console.log(bars.slice(-5));
You don't have to pick just one API. The smartest approach is to stack multiple free tiers and use each one for what it does best:
Historical EOD data, backtesting, bulk downloads. Your primary data warehouse.
Real-time WebSocket for live dashboards. 60 req/min for REST endpoints.
Technical indicators API. 800 req/day for RSI, MACD, Bollinger Bands.
IEX quotes for US stocks. WebSocket for streaming quotes. 500 symbols/mo.
Free tier stack: Yahoo + Finnhub + Twelve Data + Tiingo = $0/month — covers EOD history, real-time streaming, technical indicators, and IEX quotes.
Budget production: Polygon.io Starter ($29) + EODHD ($20) = $49/month — SIP feed, US real-time, 60+ global exchanges.
Full institutional: Polygon.io Business ($199) + IEX Cloud Growth ($79) = $278/month — full options, unlimited real-time, SLA.
These are the "enterprise" tier — Bloomberg Terminal costs $24,000/year, Refinitiv Eikon starts at $22,000/year, FactSet at $12,000/year. They offer everything: real-time data, news, analytics, chat, order management. If you're managing $100M+, they're table stakes. For everyone else, the APIs in this article provide 95% of the functionality at 1% of the cost.
It's been "about to be shut down" since 2017. Yahoo periodically changes their internal API structure, which breaks yfinance temporarily. The maintainers (Ran Aroussi and the open-source community) typically fix it within days. The risk is real for production systems, but for personal research it remains the best free option.
Polygon.io wins by a wide margin. Their docs include interactive examples, Python/JS/Go SDKs, WebSocket playground, and excellent error messages. IEX Cloud is a close second. Alpha Vantage documentation is functional but outdated. Finnhub is adequate. EODHD is the weakest.