# Binance historical data API: REST klines vs zip archives

URL: https://fincodelabs.com/en/blog/binance-historical-data-api · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Exchange APIs · Published: 2026-11-06 · Updated: 2026-11-06

Binance historical data API, tested 5 November 2026: klines paging, intervals, USD-M mark price candles, data.binance.vision zips, checksums, microseconds.

**Short answer:** The **Binance historical data API** has two routes: kline endpoints (up to 1,000 candles a call on Spot, 1,500 on USD-M futures) and daily or monthly zip files at data.binance.vision. Tested on 5 November 2026, one day of 1-minute BTCUSDT candles from `GET /api/v3/klines` was identical to that day's zip file once the file's timestamps, in microseconds since 1 January 2025, were converted to milliseconds. The files are licensed for non-commercial use only.

The two routes differ in limits, timestamp units and file layout. Everything below ran on 5 November 2026 against public endpoints; the Spot documentation, its changelog (last updated 30 October 2026), the USD-M market data page and the binance-public-data README and terms were read that day.

This technical guide recommends neither Binance nor any strategy, and Binance does not endorse it. Availability depends on your jurisdiction and the 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. Gap checks are in the [crypto price API guide](https://fincodelabs.com/en/blog/crypto-price-api) and trade archives in [crypto tick data](https://fincodelabs.com/en/blog/crypto-tick-data).

## Binance klines API: endpoints and the limit per call

Spot candles come from `GET /api/v3/klines` at up to 1,000 per call and USD-M futures candles from `GET /fapi/v1/klines` at up to 1,500, with 500 when `limit` is left out.

**Kline endpoints: documentation and public calls, 5 November 2026**

| Endpoint | Takes | Maximum per call | Tested |
| --- | --- | ---: | --- |
| `GET /api/v3/klines` (Spot) | `symbol` | 1,000 | `limit=1001` returned 1,000 rows and no error |
| `GET /api/v3/uiKlines` (Spot) | `symbol` | 1,000 | Closed candles identical to `klines` in every sample |
| `GET /fapi/v1/klines` (USD-M) | `symbol` | 1,500 | `limit=1501` rejected with error -1130 |
| `markPriceKlines`, `premiumIndexKlines`; `indexPriceKlines` (USD-M) | `symbol`; `pair` | 1,500 | Volume 0 |

Each candle is a 12-value array identified by its open time. The tests used the Spot market-data-only host, `data-api.binance.vision`. Weights are in the [Binance API rate limit guide](https://fincodelabs.com/en/blog/binance-api-rate-limit).

**uiKlines** returns "modified kline data, optimized for presentation of candlestick charts", per the Spot documentation, which does not say what changes. On 5 November 2026 its closed BTCUSDT candles matched `klines` exactly at every interval sampled, 1s to 1M. Store `klines`, which the README names as the archives' source.

**Mark, index and premium index candles** keep the shape, but the documentation marks volume and the last five fields as Ignore; an hourly mark price candle still carried 3,600 in the trade-count position, so never read it as trades.

## Binance API timeframes: interval values and time zones

Spot accepts 16 interval values from `1s` to `1M`, USD-M the same list without `1s`, and the archive files write the month as `1mo`.

**Interval values, one public call each on 5 November 2026**

| Unit | REST value | Spot | USD-M | Archive folder |
| --- | --- | --- | --- | --- |
| Seconds | `1s` | Yes | No: -1120 Invalid interval | `1s`, Spot only |
| Minutes | `1m 3m 5m 15m 30m` | Yes | Yes | Same |
| Hours | `1h 2h 4h 6h 8h 12h` | Yes | Yes | Same |
| Days and weeks | `1d 3d 1w` | Yes | Yes | Same |
| Months | `1M` | Yes | Yes | `1mo` |

Values are case-sensitive: `1H` and `1mo` returned -1120. The README says files use `1mo` so case-insensitive file systems can tell months from minutes. USD-M `continuousKlines` did accept `1s`.

Candles align to UTC unless you pass `timeZone`, a Spot parameter added on 4 December 2023 per the changelog. With `timeZone=8`, daily candles opened at 16:00 UTC; `15` was rejected with -1130, outside the documented -12:00 to +14:00. `startTime` and `endTime` stay in UTC and files are UTC only, so do not mix shifted REST candles with file candles. USD-M accepted `timeZone=8` without an error but kept daily candles at 00:00 UTC.

## Paging Binance kline history over REST

Request a window with `startTime`, `endTime` and the maximum `limit`, then start the next call 1 millisecond after the last candle's close time, until a page comes back empty.

- **Misaligned starts skip a candle.** A `startTime` 30 seconds into a minute returned the next minute first (Spot, 5 November 2026).
- **Empty is not an error.** A start after the end, or in the future, returned HTTP 200 and an empty list. An unknown symbol returned HTTP 400 with `{"code":-1121,"msg":"Invalid symbol."}`, so check the status first.
- **Units on input.** Spot accepted a 16-digit microsecond `startTime`, as the changelog entry of 17 December 2024 allows. USD-M answered the same value with an empty page, which a paging loop takes for the end of the data.

> **Hypothetical example.** A year of 1-second candles is 365 x 86,400 = 31,536,000 rows: 31,536 calls at weight 2, or 63,072 weight, about 10.5 minutes of the whole 6,000-per-minute Spot budget on 5 November 2026. As files it is 365 downloads; the 1-second file for 4 November 2026 was 2,636,462 bytes, roughly 0.96 GB a year if days are similar. The [API rate limit calculator](https://fincodelabs.com/en/tools/api-rate-limit-calculator) budgets other cases.

## Binance historical data download: data.binance.vision files

Binance publishes kline zip archives per symbol and interval at `data.binance.vision`, daily and monthly, each with a `.CHECKSUM` file holding its SHA-256.

A Spot path looks like `data/spot/daily/klines/BTCUSDT/1m/BTCUSDT-1m-2026-11-04.zip`; USD-M sits under `data/futures/um/`, with extra `markPriceKlines`, `indexPriceKlines` and `premiumIndexKlines` folders. The README says daily files appear the next day and monthly files on the first Monday of the month. `Last-Modified` times mostly agreed:

**BTCUSDT 1-minute files, checked 5 November 2026 (times UTC, all 2026)**

| File | Last-Modified |
| --- | --- |
| Spot daily, 4 November | 5 November, 01:35 |
| USD-M daily, 4 November | 5 November, 08:03 |
| Spot daily, 5 November | Not there at 19:55 (HTTP 404) |
| Spot and USD-M monthly, October | Monday 2 November, 10:54 and 09:34 |
| Spot and USD-M monthly, September | Monday 5 October, 11:13; Friday 2 October, 09:36 |

What the README leaves out:

- **Layout differs.** Spot files have no header row; USD-M kline and mark price files start with one. Each daily 1-minute file held 1,440 candles.
- **`1mo` files lag.** The Spot BTCUSDT `1mo` folder ended at June 2026 and the USD-M one at December 2023, while `1d` ran to October 2026. Build monthly candles from daily ones or REST `1M`.
- **Quiet seconds are kept.** The 1-second file for 4 November had all 86,400 rows, zero-volume seconds included, so a missing row is a real gap.
- **Files can be replaced.** The README lists replacements, the latest a kline fix dated 8 August 2022. Store each checksum and compare it before a rebuild.

## The microsecond trap in Binance Spot files

Spot archive timestamps changed to microseconds on 1 January 2025, while USD-M files and default REST responses stay in milliseconds, so decide the unit for each value.

The README states it and the files confirm it: Spot open times have 13 digits on 31 December 2024 and 16 on 1 January 2025, and the USD-M file for 4 November 2026 has 13. On REST, the header `X-MBX-TIME-UNIT: MICROSECOND` (changelog, 17 December 2024) switches responses to microseconds, which a test confirmed.

> **What goes wrong.** 1793750400000000 is 4 November 2026, 00:00 UTC, in microseconds. Read as milliseconds, pandas 2.2.3 raised `OutOfBoundsDatetime` for the year 58811. The reverse mistake is silent: 1793750400000 read as microseconds becomes 21 January 1970. A backfill crossing 1 January 2025 meets both units, so one global setting fails.

Accept 13 digits as milliseconds and 16 as microseconds, stop on anything else, and convert to one unit before storing.

## Tested Python: verify a daily file and match it to REST

The script verifies one daily Spot file against its `.CHECKSUM`, converts timestamps, pages the same day from REST and compares the two.

It ran on 5 November 2026 (Python 3.11, `requests` 2.31). Every call is public, so no key was needed; BTCUSDT is an example only.

```python
import csv, hashlib, io, math, zipfile
from datetime import datetime, timezone
import requests

VISION = "https://data.binance.vision/data/spot/daily/klines"
REST = "https://data-api.binance.vision/api/v3/klines"
SYMBOL, INTERVAL, DAY = "BTCUSDT", "1m", "2026-11-04"

def to_ms(stamp):                         # 16 digits: microseconds, 13: milliseconds
    if len(stamp) not in (13, 16) or not stamp.isdigit():
        raise ValueError("unexpected timestamp " + repr(stamp))
    return int(stamp) // 1000 if len(stamp) == 16 else int(stamp)

def checked(row):
    o, h, l, c, v = (float(x) for x in row[1:6])   # "1e400" becomes inf
    if not len(row) == 12 or not all(map(math.isfinite, (o, h, l, c, v))) \
            or l <= 0 or v < 0 or l > min(o, c) or h < max(o, c):
        raise ValueError(f"bad candle at {row[0]}")
    return [to_ms(row[0]), *row[1:6], to_ms(row[6]), row[7], int(row[8]), *row[9:12]]

def get(s, url, **params):
    r = s.get(url, params=params, timeout=60)
    if not r.ok:                          # status first, then the body
        raise RuntimeError(f"HTTP {r.status_code}: {r.text[:120]}")
    return r

def file_day(s):
    name = f"{SYMBOL}-{INTERVAL}-{DAY}.zip"
    url = f"{VISION}/{SYMBOL}/{INTERVAL}/{name}"
    blob, sums = get(s, url).content, get(s, url + ".CHECKSUM").text
    found = [p[0] for p in map(str.split, sums.splitlines()) if p[1:] in ([name], ["*" + name])]
    if len(found) > 1 or not found:       # never pick one of several lines
        raise ValueError(f"{len(found)} checksum lines for {name}")
    if not hashlib.sha256(blob).hexdigest() == found[0]:
        raise ValueError("SHA-256 mismatch: damaged or replaced file")
    with zipfile.ZipFile(io.BytesIO(blob)) as z:
        if not z.namelist() == [name[:-4] + ".csv"]:   # one member, no duplicate
            raise ValueError(f"unexpected zip members {z.namelist()}")
        rows = [r for r in csv.reader(io.StringIO(z.read(z.namelist()[0]).decode())) if r]
    if rows and rows[0][0] == "open_time":  # USD-M files start with a header
        rows = rows[1:]
    return len(blob), [checked(r) for r in rows]

def rest_day(s):
    start = int(datetime.fromisoformat(DAY).replace(tzinfo=timezone.utc).timestamp()) * 1000
    end, rows, calls = start + 86_400_000 - 1, [], 0
    while start <= end:
        page = get(s, REST, symbol=SYMBOL, interval=INTERVAL,
                   startTime=start, endTime=end, limit=1000).json()
        calls += 1
        if not isinstance(page, list) or not all(isinstance(k, list) for k in page):
            raise RuntimeError(f"unexpected body {str(page)[:120]}")
        if not page:
            break
        if page[0][0] < start or page[-1][6] < start or page[-1][0] > end:   # window ignored
            raise RuntimeError("page outside the requested window")
        rows += [checked([str(x) for x in k]) for k in page]
        start = page[-1][6] + 1            # close time + 1 ms: next candle
    return calls, rows

if __name__ == "__main__":
    with requests.Session() as s:
        s.headers["User-Agent"] = "Mozilla/5.0"
        size, from_file = file_day(s)
        calls, from_api = rest_day(s)
    print(f"file: {size:,} bytes, SHA-256 ok, {len(from_file):,} rows")
    print(f"REST: {calls} calls, {len(from_api):,} rows")
    print("identical after unit fix:", from_file == from_api)
```

Output on 5 November 2026:

```
file: 71,229 bytes, SHA-256 ok, 1,440 rows
REST: 2 calls, 1,440 rows
identical after unit fix: True
```

A local mock then fed it two checksum lines for one file, a changed byte, a duplicate zip member, prices of `1e400`, `1e-400` and `nan`, a high below the close, rows of 11 or 13 fields, a 15-digit timestamp, error bodies with HTTP 200, 4xx or 5xx, and servers that ignored the window or repeated pages; each raised a clear error instead of passing or looping, as did live requests for the unpublished 5 November file and an unknown symbol. Libraries that consume such data are compared in [Python backtesting libraries](https://fincodelabs.com/en/blog/backtesting-python-libraries).

## REST or bulk files: how to choose, and the license

Use files for backfills and REST for days the files do not cover yet, and compare the two where they overlap.

REST covers today, yesterday until its file appears, non-UTC days and monthly candles. Live candles belong on streams; see the [Binance WebSocket guide](https://fincodelabs.com/en/blog/binance-websocket-api).

**License.** The Binance Vision Dataset Terms, last updated 26 August 2026 (reopened 5 November 2026), allow non-commercial use only (CC BY-NC-SA 4.0), and section 4.1 limits it to listed uses such as academic research and personal non-production backtesting; commercial use needs a separate written agreement with Binance. Section 7 also bars scripts that get around rate limits. Historical candles predict nothing; see [backtesting pitfalls](https://fincodelabs.com/en/blog/backtesting-trading-strategy-pitfalls). For integration work in general, see [market data pipelines](https://fincodelabs.com/en/services/market-data-pipelines); naming Binance here does not mean Fincode integrates it or has built on it.

## Frequently asked questions

### How far back does Binance historical data go?

It depends on the symbol. On 5 November 2026 the oldest daily BTCUSDT candle was 17 August 2017 on Spot, 8 September 2019 on USD-M futures and 23 December 2019 for the USD-M mark price.

### What is the Binance kline API limit per request?

Spot returns at most 1,000 candles per call and USD-M futures 1,500, with 500 by default. On 5 November 2026, Spot silently capped a larger limit and USD-M rejected it.

### Why do Binance Vision timestamps have 16 digits?

Spot files use microseconds from 1 January 2025, per the binance-public-data README. Earlier Spot files and USD-M files use 13-digit milliseconds.

### Can I use data.binance.vision files in a commercial product?

Not under the Dataset Terms updated 26 August 2026, which allow non-commercial use only; commercial use needs a separate written agreement with Binance.

## Sources

- [Binance Spot API documentation: REST API, Kline/Candlestick data and UIKlines](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
- [Binance Developer Docs: Spot market data endpoints](https://developers.binance.com/en/docs/catalog/core-trading-spot-trading/api/rest-api/market)
- [Binance Spot API: CHANGELOG (last updated 30 October 2026; timeZone 4 December 2023, microseconds 17 December 2024)](https://github.com/binance/binance-spot-api-docs/blob/master/CHANGELOG.md)
- [Binance Spot API documentation: Market Data Only URLs (data-api.binance.vision)](https://github.com/binance/binance-spot-api-docs/blob/master/faqs/market_data_only.md)
- [Binance Developer Docs: USD-M Futures market data REST API (klines, continuous, index, mark and premium index klines)](https://developers.binance.com/en/docs/catalog/core-trading-derivatives-trading-usd-s-m-futures/api/rest-api/market-data)
- [Binance Public Data: README (file layout, intervals, CHECKSUM, updates, microseconds)](https://github.com/binance/binance-public-data/blob/master/README.md)
- [Binance Vision Dataset Terms, version 1.0, last updated 26 August 2026](https://github.com/binance/binance-public-data/blob/master/TERMS_AND_CONDITIONS.md)
- [Binance Data Collection (data.binance.vision)](https://data.binance.vision/)
- [Binance Spot exchangeInfo via the market data host, live rateLimits](https://data-api.binance.vision/api/v3/exchangeInfo?symbol=BTCUSDT)
- [Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> 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](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/binance-historical-data-api

Cite as: Fincode, "Binance historical data API: REST klines vs zip archives", updated November 6, 2026, https://fincodelabs.com/en/blog/binance-historical-data-api
