Short answer: Crypto historical data comes from three places: exchange candle endpoints paged 300 to 1,000 rows at a time, bulk files some exchanges publish, and paid vendors under contract. Downloading is the easy part. As read on 6 November 2026, Binance's bulk files are licensed for non-commercial use only, and OKX's API agreement limits its market data, public endpoints included, to personal non-commercial use, so check the license before the data reaches a product.
Figures come from each exchange's documentation and keyless calls to public endpoints, read and run on 6 November 2026, Istanbul time (5 November in UTC); timestamps are UTC. Exchanges are named because their documentation was read, not as a recommendation, and nothing here is a view on any asset or strategy. Rules depend on your jurisdiction and each platform's terms. For readers in Turkey: Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.
Free crypto market data API limits, tested
Each exchange caps rows per call differently, and in tests on 6 November 2026 only Coinbase rejected an oversized request; the others quietly returned less than asked.
| Endpoint | Rows per call (docs) | Order | Asking for more (tested) | Calls for a year |
|---|---|---|---|---|
Binance Spot GET /api/v3/klines | 1,000 (default 500) | Oldest first | limit=1001 returned 1,000, HTTP 200 | 526 |
Bybit GET /v5/market/kline | 1,000 (default 200) | Newest first | limit=1001 returned 1,000, retCode 0 | 526 |
OKX GET /api/v5/market/history-candles | 300 (default 100) | Newest first | limit=301 returned 300, code 0 | 1,752 |
Coinbase GET /api/v3/brokerage/market/products/{id}/candles | 350 | Newest first | A 351-minute window: HTTP 400 INVALID_ARGUMENT | 1,502 |
Kraken GET /0/public/OHLC | 720 most recent, in total | Oldest first | since set in 2020 returned only the latest 721 rows | Not possible |
- Silent clamping skips data. Advance from the last timestamp received, not the one requested.
- The unfinished candle. All five returned the current minute: Binance and Kraken as the last row (Kraken's documentation says it is always present); Bybit, Coinbase and OKX (with
confirm0) as the first. Drop it before saving. - Depth is mostly undocumented. Binance's earliest BTCUSDT daily candle opened on 17 August 2017; OKX says its history covers recent years, with 1-second bars for 3 months; the Bybit and Coinbase pages give no start date.
- Budgets differ. Binance documents weight 2 per klines call and OKX 20 history requests per 2 seconds per IP; Coinbase's candles page states no rate limit. See the Binance API rate limit guide.
Download crypto historical data as CSV from bulk files
Binance, OKX and Kraken publish candle files you can fetch without paging, and each has a quirk that changes how you read them.
| Source | Contents | Publication | Quirk |
|---|---|---|---|
| Binance, data.binance.vision | Spot klines (1s to 1mo), futures klines, trades, aggregate trades | Daily files the next day, monthly on the first Monday | Spot timestamps in microseconds from 1 January 2025; a CHECKSUM per zip; archived files can be replaced, with an update list |
OKX, GET /api/v5/public/market-data-history | File links for trades, 1-minute candles, funding, order books, borrowing rates | Mostly T+2, order books T+3; backfill in progress | Days in UTC+8 except order books (UTC+0); 5 requests per 2 seconds |
| Kraken, OHLCVT downloads | OHLCVT at 1 to 1,440 minutes, no header row | Quarterly; complete through 30 September 2026 (page updated 2 November 2026) | Only intervals with trades are included |
| Bybit, public.bybit.com | Folders kline_for_metatrader4, premium_index, spot_index, trading, spot | Not stated | A bare listing, with no description or license statement |
The OKX day boundary is easy to miss: its BTC-USDT 1-minute file named 2026-10-01, downloaded on 6 November 2026, held 1,440 rows from 30 September 16:00 to 1 October 15:59 UTC, so one UTC day needs two files. The documentation also names one field two ways, dataTs in its table and dateTs in its example; the live response used dateTs. Binance's microsecond change is covered in the Binance historical data API guide, and trades and order books in crypto tick data.
Crypto data licenses: what each official page says
Free to download is not free to use: the two exchanges whose data terms could be read here both exclude commercial use without a separate written agreement.
| Source | Document | What it says |
|---|---|---|
| Binance Vision files | Vision Dataset Terms 1.0, updated 26 August 2026 | Covers data.binance.vision and undefined "associated endpoints". CC BY-NC-SA 4.0. Permitted uses are limited to academic research, non-monetized open education, personal non-production backtesting and non-commercial open-source evaluation; no live proprietary trading, commercial bot platforms or paid newsletters; commercial use needs a written enterprise license. |
| OKX endpoints and files | API Agreement section 9.4, updated 28 July 2026 | Personal, non-commercial trading and account management only; no redistribution, publication or display to third parties without written consent; no competing data product or training models to sell or share. It applies equally to public endpoints and bars scraping beyond personal need. |
| Kraken files | OHLCVT support article | No license or use terms on the page. |
| Coinbase candles | API reference page | No license terms on the page; Coinbase's developer terms page could not be opened, so it is not summarized. |
| Bybit files | Public listing | No license statement; Bybit's general terms were not reviewed. |
Binance's terms say an amendment applies only to datasets made available after it takes effect, so record the download date and terms version with each file; the script below writes the source and download time to a provenance file. The repository README ends with an MIT license line but points to the terms document for how the dataset may be used. A page with no license statement is not permission: the platform's general terms still apply. This is general information, not legal advice.
A missing candle is not always missing data
Exchanges disagree on what a minute with no trades looks like, so one quiet market appears as a gap on one venue and as zero-volume rows on another.
Tested on 6 November 2026, 1-minute candles for the UTC day of 4 November 2026. Coinbase returned all 1,440 candles for BTC-USD but only 360 for ADA-EUR, leaving 1,080 minutes without a row and no zero-volume candles. Binance returned 1,440 candles for ADAEUR, of which 1,156 had zero volume and zero trades.
- Kraken documents this convention for its files: only intervals with trades are included and gaps are not zero-filled. The Coinbase candles page says nothing either way.
- Count rows against the expected grid; on a venue that zero-fills, a real gap more likely means an outage or a missing file. Never forward-fill silently: fill in a logged step with a flag column, or a backtest may simulate fills at prices nobody traded.
- Live capture differs from backfill. Tardis.dev's data FAQ, read 6 November 2026, says its raw data comes mainly from real-time WebSocket feeds, that outage gaps are permanent, and that a REST endpoint "may return a cleaner history after the fact".
Tested Python: page candles, check gaps and duplicates, write CSV
The script pages Coinbase's public candle endpoint without a key, rejects impossible rows, refuses conflicting duplicates, counts minutes without a candle and writes a CSV plus a provenance file.
It ran on 6 November 2026 with Python 3.11 and requests 2.31, for the UTC day of 4 November 2026.
import csv, json, math, time
from decimal import Decimal, InvalidOperation
import requests
URL = "https://api.coinbase.com/api/v3/brokerage/market/products/{}/candles"
STEP = {"ONE_MINUTE": 60, "ONE_HOUR": 3600, "ONE_DAY": 86400}
CAP = 350 # buckets per call; both window ends count
FIELDS = ("open", "high", "low", "close", "volume")
class DataError(Exception):
pass
def fetch(product, gran, start, end, http):
r = http.get(URL.format(product), timeout=10,
params={"start": start, "end": end, "granularity": gran})
try:
body = r.json()
except ValueError:
body = None
rows = body.get("candles") if isinstance(body, dict) else None
if r.status_code == 200 and isinstance(rows, list):
return rows
msg = body.get("message") or body.get("error") if isinstance(body, dict) else None
raise DataError(f"HTTP {r.status_code}: {msg or r.text[:120]}")
def clean(raw):
t = str(raw.get("start", "") if isinstance(raw, dict) else "").strip()
if not t.isdecimal(): # an empty ID is an error, not a duplicate
raise DataError(f"no start time: {raw}")
try:
o, h, l, c, v = (Decimal(raw[k]) for k in FIELDS)
f = [float(x) for x in (o, h, l, c, v)]
except (KeyError, TypeError, ValueError, InvalidOperation) as e:
raise DataError(f"bad number at {t}: {repr(e)}")
if (not all(map(math.isfinite, f)) or min(f[:4]) <= 0 or f[4] < 0
or (v and not f[4]) or h < max(o, l, c) or l > min(o, h, c)):
raise DataError(f"impossible candle at {t}: {raw}")
return int(t), [raw[k] for k in FIELDS]
def download(product, gran, start, end, path, http=None, now=None):
http, step = http or requests.Session(), STEP[gran]
now = time.time() if now is None else now
start -= start % step
last = (end - 1) // step * step
rows, dupes, t = {}, 0, start
while t <= last:
stop = min(t + step * (CAP - 1), last)
for raw in fetch(product, gran, t, stop, http):
ts, vals = clean(raw)
if ts + step > now or not start <= ts <= last:
continue
if ts not in rows:
rows[ts] = vals
elif rows[ts] == vals:
dupes += 1
else:
raise DataError(f"conflicting candles at {ts}")
t = stop + step
time.sleep(0.2)
missing = [x for x in range(start, last + 1, step)
if x + step <= now and x not in rows]
with open(path, "w", newline="") as fh:
w = csv.writer(fh)
w.writerow(("start",) + FIELDS)
w.writerows([k] + rows[k] for k in sorted(rows))
meta = {"source": URL.format(product), "granularity": gran, "start": start,
"end": end, "rows": len(rows), "duplicates": dupes,
"missing": len(missing), "downloaded_at": int(time.time())}
with open(path + ".json", "w") as fh:
json.dump(meta, fh)
return meta
if __name__ == "__main__":
day = 1793750400 # 4 November 2026 00:00 UTC
for product in ("BTC-USD", "ADA-EUR"):
m = download(product, "ONE_MINUTE", day, day + 86400, product + ".csv")
print(product, m["rows"], "rows,", m["duplicates"], "duplicates dropped,",
m["missing"], "minutes without a candle")
Output:
BTC-USD 1440 rows, 0 duplicates dropped, 0 minutes without a candle
ADA-EUR 360 rows, 0 duplicates dropped, 1080 minutes without a candle
- Both window ends are inclusive. A 10-minute window returned 11 candles, and a window of exactly 350 intervals returned the newest 350, dropping the oldest without an error. A first version stepping 350 intervals reported 1,439 BTC-USD rows: midnight lost, 23:20 counted twice. Hence 349-interval windows, each starting one interval after the last.
- Failure paths, tested with synthetic responses: error bodies without the expected fields, HTML and empty bodies, a null candle list, empty and null start times, "1e400" and "1e-400" in any field, "NaN", "sNaN", impossible highs and lows, identical and conflicting duplicates, an unfinished candle and a connection error. Each was rejected or handled as intended; the connection error left no CSV.
- The float check.
float("1e400")isinfandfloat("1e-400")is0.0in any float64 reader, so the script rejects such values and keeps the exchange's original strings. - Adapting it. Rows keyed by start time make return order irrelevant. For OKX, page backward with
afterand dropconfirm0 rows. On HTTP 429, retry later with exponential backoff with jitter.
Paid crypto data vendors: questions before you buy
A vendor sells under its own contract, so ask about license scope and how the history was built.
- May you display, redistribute or train models on the data, and keep it after the contract ends?
- Was it captured live or backfilled, and how are outages marked?
- Are days in UTC, are delisted pairs included, and how are corrections announced?
Vendor prices read on 4 November 2026 are in the crypto price API guide, and storage in ClickHouse for market data. For integration work in general, see Fincode's market data pipelines page; naming Binance, Bybit, Coinbase, Kraken, OKX or Tardis.dev here does not mean Fincode integrates them or has built on them.
Frequently asked questions
Where can I download crypto historical data for free?
As read on 6 November 2026, Binance, OKX and Kraken publish candle files, and all five exchanges tested here serve candles through public endpoints without a key. Free to download is not free to use, so read each source's terms first.
Can I use free exchange data in a commercial product?
Not under the Binance Vision Dataset Terms or the OKX API Agreement as read on 6 November 2026; both require a separate written agreement. Check other sources' terms too.
Why does Kraken's OHLC endpoint ignore my since parameter?
Kraken's documentation says it returns up to 720 of the most recent entries and older data cannot be retrieved regardless of since. Use Kraken's quarterly OHLCVT files for older history.
Why does my downloaded CSV have missing minutes?
Kraken's files omit intervals without trades, and Coinbase returned no row for 1,080 ADA-EUR minutes on 4 November 2026, while Binance returned zero-volume candles. Check the venue's convention before calling it lost data.
Sources
- Binance Vision Dataset Terms, version 1.0, last updated 26 August 2026 (opens in a new tab)
- Binance Public Data: README (file layout, intervals, CHECKSUM, updates) (opens in a new tab)
- Binance Spot API documentation: REST API, Kline/Candlestick data (opens in a new tab)
- Bybit V5 API: Get Kline (opens in a new tab)
- Bybit public data listing (opens in a new tab)
- OKX API v5 documentation: candlesticks history and historical market data (opens in a new tab)
- OKX API Agreement, section 9.4, last updated 28 July 2026 (opens in a new tab)
- Coinbase Advanced Trade API: Get public product candles (opens in a new tab)
- Kraken API: Get OHLC data (opens in a new tab)
- Kraken Support: Downloadable historical OHLCVT data, last updated 2 November 2026 (opens in a new tab)
- Tardis.dev: Data FAQ (opens in a new tab)
- Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.