Finance APIs Series — Part 1 of 5

Price Data APIs — OHLCV, Real-Time & Historical

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.

8 APIs Compared Python & JS Free Tiers WebSocket
Finance APIs — The Ultimate Guide1/5
OverviewYahoo FinanceAlpha VantagePolygon.ioMore APIsComparisonCode ExamplesKey Takeaways
The Price Data Landscape

Why Price Data Is the Foundation of Everything

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.

What Is OHLCV?

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.

Three Tiers of Price Data

1

End-of-Day (EOD)

Daily OHLCV bars, updated after market close. Best for swing trading, backtesting, and portfolio tracking. Often free.

2

Intraday

1-min to 1-hour bars during market hours. Needed for day trading strategies and intraday analytics. Usually paid.

3

Real-Time Tick

Sub-second WebSocket feeds with every trade and quote. Required for market making, HFT, and live dashboards. Premium pricing.

What to Look For in a Price API

The APIs — Deep Dive

Yahoo Finance (yfinance)

Yahoo Finance / yfinance

Free No API Key Legal Grey Area

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.

Pros

  • Completely free, no API key needed
  • 20+ years of historical daily data
  • Global coverage (60+ exchanges)
  • Adjusted for splits and dividends
  • Options chains included
  • Massive community, battle-tested

Cons

  • Not an official API — can break anytime
  • Rate limiting is aggressive and opaque
  • 15-minute delay on quotes
  • No WebSocket / streaming
  • Terms of Service forbid scraping
  • No SLA, no support

Code Example — Python (yfinance)

Python
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())

The Legal Grey Area

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.

Rate Limiting Strategy

Yahoo's rate limiting is undocumented and changes frequently. Based on community testing:

Alpha Vantage

Alpha Vantage

Freemium $50-250/mo

One 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.

Pros

  • Legitimate API with proper licensing
  • Free tier: 25 req/day (stocks, forex, crypto)
  • 20+ years of daily data
  • Technical indicators built in (SMA, EMA, RSI, MACD...)
  • Economic indicators (GDP, CPI, unemployment)
  • Simple REST API, JSON/CSV output

Cons

  • Free tier is extremely limited (25 req/day)
  • Premium is expensive ($50/mo for 75 req/min)
  • No WebSocket streaming
  • Data quality issues reported (gaps, delays)
  • Slow response times on free tier
  • JSON format is non-standard (nested keys with dates)

Code Example — Python

Python
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()

Code Example — JavaScript (Node.js)

JavaScript
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)));

Polygon.io

Polygon.io

Free Tier $29-199/mo WebSocket

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.

Pros

  • Excellent data quality (SIP feed)
  • WebSocket streaming (real-time)
  • Full options data (chains, greeks, flow)
  • Free tier: 5 API calls/min, delayed data
  • Excellent documentation & SDKs
  • Flat files for bulk historical download

Cons

  • Free tier is very limited (5 calls/min)
  • Real-time requires $29+/mo
  • Options data only on higher tiers
  • US-only (no international exchanges)
  • Historical intraday limited on lower tiers

Code Example — Python

Python
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()

WebSocket Streaming

Python
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()

SIP vs. IEX Feed

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.

More Price Data APIs

Tiingo

Freemium 500 Symbols Free WebSocket (IEX)

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.

Pros

  • Generous free tier (500 symbols/month)
  • Free WebSocket (IEX feed)
  • Excellent EOD data quality
  • Crypto and forex included

Cons

  • IEX-only real-time (not SIP)
  • Limited intraday history
  • No options data

Code Example — Tiingo

Python
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']}")

IEX Cloud

$9-499/mo WebSocket (SSE)

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.

Pros

  • Best-in-class documentation
  • SSE streaming (simpler than WebSocket)
  • Sandbox mode for testing
  • Fundamentals + news included

Cons

  • No free tier anymore
  • Message-based pricing is confusing
  • IEX-only data (not SIP)
  • Gets expensive at scale

Twelve Data

Freemium 800 req/day Free WebSocket

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.

Pros

  • 800 req/day free (generous!)
  • Free WebSocket (8 connections)
  • 100+ built-in technical indicators
  • Global coverage (50+ exchanges)

Cons

  • Free WebSocket is delayed (not real-time)
  • Historical depth limited on free tier
  • Data accuracy varies by exchange

Code Example — Twelve Data WebSocket

JavaScript
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

Freemium 60 req/min Free WebSocket

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.

Pros

  • 60 req/min free (best rate limit)
  • Free real-time WebSocket (US stocks)
  • Alternative data (sentiment, insiders)
  • Good crypto coverage
  • Company news and press releases

Cons

  • Limited historical candle data
  • No adjusted prices on free tier
  • Documentation could be better

Code Example — Finnhub

Python
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']}")

EODHD (EOD Historical Data)

$20-80/mo WebSocket

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.

Pros

  • 60+ global exchanges at $20/mo
  • 30+ years of EOD history
  • Fundamentals, dividends, splits included
  • Bulk download via CSV/JSON

Cons

  • No free tier
  • Intraday only on higher plans
  • API design feels dated
  • Support can be slow

Code Example — EODHD

Python
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()
Head-to-Head Comparison

The Ultimate Comparison Table

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

API Comparison Radar

Decision Matrix by Use Case

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
Unified Code Examples

Building a Universal Price Fetcher

Instead of coupling your code to a single API, build an abstraction layer. Here's a universal price fetcher that works with any provider:

Python — Abstract Price Provider

Python
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")

Yahoo Finance Implementation

Python
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"),
        }

Polygon.io Implementation

Python
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"],
        }

Using the Abstraction

Python
# 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)}")

JavaScript / Node.js — Fetch Wrapper

JavaScript
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));
Cost Optimization

Stacking Free Tiers Like a Pro

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:

Yahoo Finance

Historical EOD data, backtesting, bulk downloads. Your primary data warehouse.

Finnhub

Real-time WebSocket for live dashboards. 60 req/min for REST endpoints.

Twelve Data

Technical indicators API. 800 req/day for RSI, MACD, Bollinger Bands.

Tiingo

IEX quotes for US stocks. WebSocket for streaming quotes. 500 symbols/mo.

Monthly Cost Comparison

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.

Monthly Cost Chart

Key Takeaways

Key Takeaways

What You Should Remember

What about Bloomberg / Reuters / FactSet?

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.

Is yfinance going to be shut down?

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.

Which API has the best documentation?

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.

Next in Series — Part 2
Fundamentals APIs — Financials, Filings & Company Data
Finance APIs — The Ultimate Guide1/5