CRIUS
OHLCVCandlesHistorical dataTutorial

How to Fetch Historical OHLCV Candles on Robinhood Chain

Fetch historical OHLCV candles for any Robinhood Chain token: intervals from 1s to 1d, paging with to_ts, pool selection, gap filling and clean wicks.

Crius 8 min read

Short answer: call GET /v1/robinhood/token/{address}/candles?interval=1h on the Crius API to get open, high, low, close and volume for any Robinhood Chain token, from 1-second to 1-day buckets, back to the token’s first trade. For longer histories, page backwards with to_ts; for charts, add fill=true so empty buckets don’t leave gaps.

This guide covers the request, the response format, paging through months of history, choosing which pools a candle is built from, and the data-quality rules that keep dust trades and off-market prints out of your highs and lows. Every code sample was run against the live API while writing this (October 2026).

The request

Terminal window
curl "https://api.crius.sh/v1/robinhood/token/0x322f0929c4625ed5bad873c95208d54e1c003b2d/candles?interval=1h&limit=3" \
-H "x-api-key: $CRIUS_API_KEY"

That token is TSLA, Tesla’s tokenized stock on Robinhood Chain. The parameters:

Parameter Values Default Notes
interval 1s, 30s, 1m, 5m, 15m, 1h, 4h, 6h, 1d 1m Bucket size. Buckets are aligned to UTC.
limit 1–1500 500 Number of buckets.
from_ts, to_ts Unix milliseconds the last limit buckets Window to return.
currency usd, quote usd quote prices in the main pool’s quote token (ETH, WETH, USDG …).
pool markets, main, all, or a pool key markets (usd) / main (quote) Which pools the candles are built from.
continuous true, false true Open each candle at the previous close.
fill true, false false Emit flat candles for buckets with no trades.

Without from_ts and to_ts, you get the most recent limit buckets, ending with the current (still open) one.

The response: columnar arrays

Candles come back as parallel arrays rather than an array of objects. It is smaller over the wire and maps directly onto charting libraries:

{
"data": {
"interval": "1h",
"currency": "usd",
"t": [1792828800000, 1792832400000, 1792836000000],
"o": [371.193, 370.843, 371.239],
"h": [373.614, 373.488, 372.938],
"l": [369.41, 369.085, 369.888],
"c": [370.843, 371.239, 370.268],
"v": [15932.4, 23648.5, 13459.2],
"bv": [1014.77, 4598.04, 10643.7],
"n": [77, 107, 81],
"mc": [4344418, 4349056, 4337675]
},
"meta": { "block": 96713540, "lag_ms": 21, "from_ts": 1792828800000, "pools": ["0xb2a6…", "0xa953…", "…"] }
}
  • t: bucket start, Unix milliseconds (UTC).
  • o, h, l, c: prices in USD (or the quote token with currency=quote).
  • v: volume in USD; bv: the buy side of it, so sell volume is v - bv.
  • n: number of trades in the bucket.
  • mc: market cap at the close (USD candles only).
  • meta.pools: exactly which pools went into the candles, so you can audit any bar.

Turning the columns into rows is one line in most languages:

const bars = d.t.map((t, i) => ({ time: t / 1000, open: d.o[i], high: d.h[i], low: d.l[i], close: d.c[i], volume: d.v[i] }));

Paging through months of history

One call returns at most 1,500 buckets: about 25 hours of 1-minute candles, 62 days of hourly candles or four years of daily ones. For more, walk backwards: ask for the newest page, then set to_ts to just before the oldest bucket you received, and repeat.

// Fetch N days of hourly candles, paging backwards with to_ts, and write a CSV.
const API = "https://api.crius.sh/v1/robinhood";
const KEY = process.env.CRIUS_API_KEY;
const TOKEN = "0x322f0929c4625ed5bad873c95208d54e1c003b2d"; // TSLA
const STEP_MS = 3_600_000, DAYS = 60;
async function page(toTs) {
const url = `${API}/token/${TOKEN}/candles?interval=1h&limit=1000&to_ts=${toTs}`;
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url, { headers: { "x-api-key": KEY } });
if (res.status === 429 || res.status === 503) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") ?? 2) * 1000));
continue;
}
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return (await res.json()).data;
}
throw new Error("gave up after retries");
}
const since = Date.now() - DAYS * 86_400_000;
const rows = new Map(); // keyed by bucket start: dedupes any overlap between pages
let to = Date.now();
while (to > since) {
const d = await page(to);
if (d.t.length === 0) break; // reached the first trade
d.t.forEach((t, i) => rows.set(t, [t, d.o[i], d.h[i], d.l[i], d.c[i], d.v[i], d.n[i]]));
to = d.t[0] - STEP_MS; // next page ends just before the oldest bucket
}
const out = [...rows.values()].filter((r) => r[0] >= since).sort((a, b) => a[0] - b[0]);
console.log(`${out.length} hourly candles`);

Run against TSLA it returned 1,439 hourly candles covering 60 days in two requests. Three details matter:

  1. Key by t. Pages can overlap by one bucket at the boundary; a map keyed by bucket start removes duplicates.
  2. Stop on an empty page. Before a token’s first trade there is nothing to return.
  3. Respect Retry-After. On a 429 (rate limit) or 503 (busy), wait the number of seconds in the header and retry the same page.

The same loop in Python:

import os, time, requests
API = "https://api.crius.sh/v1/robinhood"
HEADERS = {"x-api-key": os.environ["CRIUS_API_KEY"]}
TOKEN = "0x322f0929c4625ed5bad873c95208d54e1c003b2d"
def candles(interval="1h", limit=1000, to_ts=None, **extra):
params = {"interval": interval, "limit": limit, **extra}
if to_ts:
params["to_ts"] = to_ts
for _ in range(5):
r = requests.get(f"{API}/token/{TOKEN}/candles", params=params, headers=HEADERS, timeout=30)
if r.status_code in (429, 503):
time.sleep(float(r.headers.get("retry-after", 2)))
continue
r.raise_for_status()
d = r.json()["data"]
return [dict(t=t, o=o, h=h, l=l, c=c, v=v)
for t, o, h, l, c, v in zip(d["t"], d["o"], d["h"], d["l"], d["c"], d["v"])]
raise RuntimeError("rate limited")
bars = candles("1d", limit=30)
print(len(bars), "daily bars; last:", bars[-1])

Each page costs 2 credits regardless of how many buckets it holds, so fetching a 60-day hourly history costs 4 credits. Store candles you have already fetched: closed buckets never change, except after a chain reorganisation within the last few minutes.

Which pools a candle is built from

Most active tokens trade in several pools at once: Uniswap v2, v3 and v4, against ETH, WETH or USDG, sometimes against another token. How you combine them changes the chart.

  • pool=markets (default for USD): the token’s real markets merged. A pool qualifies when it holds at least $1,000 or 2% of the token’s liquidity and its own price agrees with the token’s price within 25%. Thin side pools and pools priced through a junk or manipulated quote token are left out. Merging gives denser candles and a more accurate USD price than any single pool.
  • pool=main: only the token’s pricing pool, the pair most DEX screeners chart. This is the default when you ask for currency=quote, because quote prices only make sense within one pair.
  • pool=all: every pool, including thin ones. Useful for research, noisy for charts.
  • pool=<key>: one specific pool: a v2/v3 pool address or a 32-byte v4 pool id.

The merged view matters most for tokens whose deepest pool is quoted in something other than ETH or USDG. TSLA’s pricing pool, for example, quotes it against another tokenized stock; charting that pool alone in USD would carry the other stock’s moves into the TSLA chart, and it trades far less often than the merged markets.

Clean highs and lows

Raw on-chain swaps produce some prices that are not market prices. Crius filters two kinds before they reach a candle’s high or low; both still count toward volume and trade counts:

  1. Dust swaps. A trade where either side is under 10,000 raw units, for example 1 wei for 1 wei, has a meaningless price: a 1:1 raw swap between two 18-decimal tokens prices one at a whole ETH. These never set a price.
  2. Isolated spikes. A trade under $1,000 whose price is more than 1.3× away from both the five trades before it and the five after it, while those agree with each other, is an off-market fill: an arbitrage leg, a sandwich, a thin-pool print. The price snapped straight back, so it is left out of the high and low.

Large trades always count, because real price impact is real. A $380,000 dump that recovers within the same five minutes still draws its wick.

Continuous candles and gap filling

By default each candle opens at the previous candle’s close (continuous=true), the way exchange and DEX screener charts draw them. Without it, a token that trades once every few minutes produces bodies that float apart; set continuous=false if you want the first trade of each bucket as the open.

Buckets with no trades are omitted unless you pass fill=true, which inserts flat candles (open = high = low = close = the last close, zero volume) between the first and last returned bucket. Charting libraries such as TradingView Lightweight Charts compress missing time otherwise, so a sparse token’s x-axis jumps unevenly.

Fill works between trades you have: a window with no trades at all returns no candles even with fill=true. If you need a value for an empty window, use the last close from the previous page or the token’s current price from /price.

Prices in the quote token

currency=quote returns candles in the main pool’s quote asset instead of USD. For an ETH-quoted memecoin you get its price in ETH, which removes ETH’s own moves from the chart. Since quote prices only make sense within one pair, this mode uses the main pool unless you name another.

Terminal window
curl "https://api.crius.sh/v1/robinhood/token/0x322f0929c4625ed5bad873c95208d54e1c003b2d/candles?interval=15m&currency=quote&limit=4" \
-H "x-api-key: $CRIUS_API_KEY"

Choosing an interval

Use Interval One page covers
Live chart, last hour 1s / 30s 25 min / 12.5 h
Intraday chart 1m / 5m 25 h / 5 days
Swing chart, backtests 15m / 1h 15 days / 62 days
Long-term history 4h / 6h / 1d 250 days / 1 year / 4 years

For a chart that stays current, fetch history once over REST and then subscribe to candles:<token>:1m on the WebSocket stream, replacing the last bar whenever an update for the same t arrives. The trading terminal guide walks through exactly that.

Frequently asked questions

How far back do candles go? To each token’s first trade. Crius indexed Robinhood Chain from block zero, so the full history is available for every token, not only for recent ones.

Why does a candle’s open differ from its first trade? With the default continuous=true the open is the previous close. Pass continuous=false to get the first trade’s price instead.

Why do my USD candles differ from one DEX’s chart? DEX screeners usually chart a single pair. Crius merges the token’s real markets by default. Use pool=main to match a single-pair chart.

What does a candle request cost? 2 credits per call, whatever the interval or number of buckets. Anonymous requests work too, at 2 requests per second per IP.

Are candles available in real time? Yes. The current bucket is included and updates with every block; for push updates, subscribe to candles:<token>:<interval> on the WebSocket stream, available on every plan with a key.

Next steps

Get a free key at dashboard.crius.sh; questions go to support@crius.sh.

Build with Crius

Robinhood Chain data in one API

Free plan with 1M credits a month. Your key in under a minute.

Related APIs

More from the blog