How to Retrieve OHLCV Candlestick Data for Charting
Candlestick charts are the standard way traders read price action. Each candle packs four prices (open, high, low, close) plus volume into a single period, showing not just where the price ended but how it got there. The OHLCV historical endpoint returns these candles for any asset.
This guide covers fetching daily and hourly candles, the interval options, and one detail that causes more bugs than anything else on this endpoint: time_start is exclusive. Get that wrong and every chart is off by one period.
Key takeaways
time_startis exclusive,time_endis inclusive. To get the candle for 2025-01-01, passtime_start=2024-12-31. This is the single largest source of off-by-one charts.- Use
count=N+1for the last N candles. The currently active period is counted but excluded from results. - Two candle granularities:
time_period=dailyorhourly. Hourly volume only exists from 2020-09-22; earlier hourly candles return volume as zero. intervalsub-samples the series without fetching every period, across 20 supported values from1hto365d.- 1 credit per 100 OHLCV values returned, rounded up, so 90 daily candles for one asset costs 1 credit.
- Use
/v2/cryptocurrency/ohlcv/historical; the v1 endpoint is deprecated.
The endpoint
Making the request
cURL: Bitcoin daily candles for Q1 2025
curl -G 'https://pro-api.coinmarketcap.com/v2/cryptocurrency/ohlcv/historical' \
--data-urlencode 'id=1' \
--data-urlencode 'time_start=2024-12-31' \
--data-urlencode 'time_end=2025-03-31' \
--data-urlencode 'time_period=daily' \
--data-urlencode 'convert=USD' \
-H 'Accept: application/json' \
-H 'X-CMC_PRO_API_KEY: YOUR_API_KEY'
Note that time_start is exclusive. The first candle returned covers the period after the start timestamp, so to get the candle for 2025-01-01, pass time_start=2024-12-31.
Python
import os
import requests
HEADERS = {
"Accept": "application/json",
"X-CMC_PRO_API_KEY": os.getenv("CMC_API_KEY"),
}
response = requests.get(
"https://pro-api.coinmarketcap.com/v2/cryptocurrency/ohlcv/historical",
headers=HEADERS,
params={
"id": "1",
"time_start": "2024-12-31", # exclusive, first candle is Jan 1
"time_end": "2025-03-31", # inclusive
"time_period": "daily",
"convert": "USD",
},
)
response.raise_for_status()
data = response.json()
time_start is exclusive
time_start marks the beginning of the search window. The first candle returned is for the period after time_start. time_end is inclusive, so the candle ending on time_end is included.
| Parameter | Boundary | Result |
|---|---|---|
| time_start=2024-12-31 | Exclusive | First candle: 2025-01-01 |
| time_end=2025-03-31 | Inclusive | Last candle: 2025-03-31 |
To get a candle for one specific date, set time_start one period earlier:
# Want the candle for 2025-06-15
params = {
"id": "1",
"time_start": "2025-06-14", # one day before
"time_end": "2025-06-15",
"time_period": "daily",
"convert": "USD",
}
Time period options
| time_period | Description |
|---|---|
| daily | One candle per UTC calendar day |
| hourly | One candle per UTC hour |
Hourly volume data is only available from 2020-09-22 onwards.
Interval parameter
Use interval to sub-sample a time series without fetching every period:
# Get daily OHLCV sampled every 14 days
params = {
"id": "1",
"time_start": "2024-01-01",
"time_end": "2025-01-01",
"time_period": "daily",
"interval": "14d",
"convert": "USD",
}
Supported interval values:
Getting the last N candles
Use count instead of a date range to retrieve the most recent candles. Set count to N+1, because the currently active period is incomplete and counted but excluded from results:
# Get the last 30 complete daily candles
params = {
"id": "1027",
"count": "31", # N+1 to account for the active incomplete period
"time_period": "daily",
"convert": "USD",
}
The response structure
data is a dict keyed by string ID. Each asset has a quotes array of candles.
{
"data": {
"1": {
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"quotes": [
{
"time_open": "2025-01-01T00:00:00.000Z",
"time_close": "2025-01-01T23:59:59.999Z",
"time_high": "2025-01-01T14:23:00.000Z",
"time_low": "2025-01-01T03:11:00.000Z",
"quote": {
"USD": {
"open": 94832.14,
"high": 97841.23,
"low": 92104.88,
"close": 96312.55,
"volume": 48293847561.00,
"market_cap": 1905231456789.00,
"timestamp": "2025-01-01T23:59:59.999Z"
}
}
}
]
}
}
}
quote is a dict keyed by currency symbol.
Extracting candle data
asset = data["data"]["1"]
candles = asset["quotes"]
ohlcv = []
for candle in candles:
usd = candle["quote"]["USD"]
ohlcv.append({
"date": candle["time_open"][:10],
"open": usd["open"],
"high": usd["high"],
"low": usd["low"],
"close": usd["close"],
"volume": usd["volume"],
})
for c in ohlcv:
print(f"{c['date']} O:{c['open']:>10,.2f} H:{c['high']:>10,.2f} L:{c['low']:>10,.2f} C:{c['close']:>10,.2f}")
Credit cost
1 credit per 100 OHLCV values returned, rounded up. 90 daily candles for one asset costs 1 credit.
Common mistakes
Forgetting that time_start is exclusive
If you want the candle for January 1, pass time_start=December 31. This is the most common source of off-by-one errors with this endpoint.
Using count=N expecting N results
The count includes the active incomplete period. Pass count=N+1 to get N complete candles.
Expecting hourly volume before 2020-09-22
Hourly volume data starts from September 22, 2020. Earlier hourly candles return volume as zero.
Using the deprecated v1 endpoint
Use /v2/cryptocurrency/ohlcv/historical.
Start pulling candles
Check the endpoint reference for current parameter support and credit rules, then pick the plan that matches the historical depth your charts need.


