How do I fetch 1h OHLCV candles for BTCUSDT?

One authenticated GET. To fetch 1h OHLCV candles for BTCUSDT, call /api/v1/market-data/klines with the symbol, the interval and how many bars you want:

curl -H "X-API-Key: cdk_live_YOUR_KEY" \
  "https://cryptodataapi.com/api/v1/market-data/klines?symbol=BTCUSDT&interval=1h&limit=2"

The response is already parsed into named fields — no positional arrays to decode:

{
  "symbol": "BTCUSDT",
  "interval": "1h",
  "klines": [
    {
      "open_time": 1787742000000,
      "open": 78727.46,
      "high": 78782.0,
      "low": 78317.0,
      "close": 78487.26,
      "volume": 413.44093,
      "close_time": 1787745599999,
      "quote_volume": 32467808.9018965,
      "trades": 144198,
      "taker_buy_base_volume": 199.88935,
      "taker_buy_quote_volume": 15698362.5209286
    }
  ],
  "count": 2
}

That is the whole integration. symbol accepts any Binance spot pair, interval runs from 1m to 1M, and limit tops out at 1,000 bars per request. Candles are on the free tier — no card, no gate.

Every field in the candle, explained

Most OHLCV feeds hand you six numbers. This one returns eleven, and the extra five are the ones worth having — they let you separate real participation from wash-like volume without a second call.

FieldTypeWhat it is
open_timeint (ms)Unix epoch in milliseconds when the bar opened. This is the bar's canonical timestamp.
openfloatFirst trade price in the bar.
high / lowfloatExtremes touched during the hour.
closefloatLast trade price. On the newest bar this is the live price, not a settled one.
volumefloatBase-asset volume — in BTC for BTCUSDT.
close_timeint (ms)Last millisecond of the bar. Always open_time + 3599999 for 1h.
quote_volumefloatQuote-asset volume — USDT turnover. Use this, not volume, to compare across coins.
tradesintNumber of individual trades. Volume ÷ trades gives average trade size.
taker_buy_base_volumefloatVolume where the buyer was the aggressor.
taker_buy_quote_volumefloatSame, in USDT.

The last two are the cheapest order-flow proxy in crypto. In the bar above, taker buys were 199.9 of 413.4 BTC — a 48.3% buy ratio, so sellers were marginally the aggressors while price fell from 78,727 to 78,487. That read costs you zero extra requests.

Intervals, limits, and how far back one call reaches

limit caps at 1,000, so the interval decides your lookback. One call, one interval, one window:

interval1,000 bars coversTypical use
1m~16.7 hoursIntraday microstructure, execution studies
5m~3.5 daysScalping signals, short-horizon features
15m~10.4 daysIntraday trend, session structure
1h~41.7 daysThe default swing-trading resolution
4h~166 daysMulti-week structure, regime context
1d~2.7 yearsCycle work, long-horizon backtests

So a single interval=1h&limit=1000 call gives you roughly six weeks of hourly history — enough to seed a 200-period moving average, an RSI, or an ATR with room to spare.

If you need more than 1,000 hourly bars, do not loop the endpoint with offsets. Drop to 1d for the long arm of the series, or use the minute-resolution archive covered further down.

The partial-candle trap that corrupts your last data point

Here is the bug that shows up in almost every first integration. The newest bar in the response is still open. Its close is the current price, its volume is an hour-to-date running total, and both will change before the hour ends.

Take the second bar from the call above: open_time 1787745600000, close_time 1787749199999. At the moment of the request the clock read 1787747651499 — sitting inside that window, roughly 34 minutes into a 60-minute bar. Its reported volume of 675 BTC was 34 minutes of trading, not an hour of it.

Feed that row into a moving average and the average is wrong. Feed it into a volume-spike detector and it under-reads by up to 98% at the top of the hour, then snaps up. Feed it into a backtest and you have leaked the future into your training data.

The fix is two lines. Compare close_time against the wall clock and drop anything that has not settled:

import time

now_ms = int(time.time() * 1000)
settled = [k for k in data["klines"] if k["close_time"] < now_ms]

Ask for limit=201 when you need 200 settled bars. The extra row is the one you throw away.

Loading BTCUSDT candles into a pandas DataFrame

The named-field shape means the DataFrame construction is a one-liner. No column lists, no positional index guessing:

import time
import pandas as pd
import requests

API_KEY = "cdk_live_YOUR_KEY"
BASE = "https://cryptodataapi.com/api/v1"


def get_candles(symbol="BTCUSDT", interval="1h", limit=500, settled_only=True):
    r = requests.get(
        f"{BASE}/market-data/klines",
        params={"symbol": symbol, "interval": interval, "limit": limit},
        headers={"X-API-Key": API_KEY},
        timeout=15,
    )
    r.raise_for_status()
    rows = r.json()["klines"]

    if settled_only:
        now_ms = int(time.time() * 1000)
        rows = [k for k in rows if k["close_time"] < now_ms]

    df = pd.DataFrame(rows)
    df["time"] = pd.to_datetime(df["open_time"], unit="ms", utc=True)
    return df.set_index("time").sort_index()


df = get_candles(interval="1h", limit=501)
df["ret"] = df["close"].pct_change()
df["taker_buy_ratio"] = df["taker_buy_base_volume"] / df["volume"]
df["vwap"] = df["quote_volume"] / df["volume"]

print(df[["close", "vwap", "taker_buy_ratio"]].tail())

Three derived columns, zero extra API calls. vwap falls straight out of quote_volume / volume, and taker_buy_ratio above 0.5 means buyers were lifting offers during the bar.

Every response also carries your budget in the headers — x-ratelimit-remaining-minute and x-ratelimit-remaining-day — so a polling loop can back off on its own instead of discovering the ceiling with a 429.

BTCUSDT vs BTC: spot candles vs perp candles

BTCUSDT is a Binance spot pair. If what you actually want is the perpetual futures candle — the instrument most crypto strategies trade — the symbol is just BTC and the endpoint is different.

Spot candlesPerp candles
Endpoint/market-data/klines/hyperliquid/candles
Symbol paramsymbol=BTCUSDTcoin=BTC
VenueBinance spotHyperliquid perps
Fields per bar116 (timestamp, OHLC, volume)
Bar timestamp keyopen_timetimestamp
Has funding / OINo — spot has neitherYes, via the derivatives endpoints

Passing BTCUSDT to the Hyperliquid endpoint returns nothing useful, and passing BTC to the spot endpoint is not a listed pair. This naming mismatch is the single most common integration error against any multi-venue crypto API. Pick your venue first, then the symbol format follows.

Practical rule: use spot candles for price history and indicators, use perp candles when your fills happen on the perp — basis between the two is real and occasionally large.

When 1,000 bars isn't enough: the minute archive

The /klines endpoint is a trailing window. For a range query — "every minute of BTCUSDT between two dates" — there is a separate archive endpoint backed by our own stored history, not a passthrough:

curl -H "X-API-Key: cdk_live_YOUR_KEY" \
  "https://cryptodataapi.com/api/v1/backtesting/klines\
?symbol=BTCUSDT&exchange=binance\
&start=2026-08-01T00:00:00Z&end=2026-08-08T00:00:00Z\
&limit=10000&format=csv"

Three differences that matter:

This endpoint is Pro Plus, with a limit ceiling of 10,000 rows per call. The trailing-window /market-data/klines stays free.

Which call to make, when

Four situations, four answers:

A sane polling cadence for 1h bars is once per hour, a minute or two after the top of the hour. Anything faster re-fetches a bar that has not changed. On the free tier's 1,000 requests/day that leaves you 976 requests for everything else.