Historical data · Candles, series, ticks, spreads

Replay the data the API actually served.

GET /v1/ohlc returns up to 1,000 completed candles a call, and every one of the 42 published indicator series can be pulled as a time series beside them on the same timestamps. The values were computed once — so a replay reads the same numbers a live run would have seen.

1,000 candles per call 42 indicator series 7 timeframes
tickatlas.com / v1 / ohlc 200 OK
GET /v1/ohlc?symbol=EURUSD&timeframe=H1&limit=100
EUR/USD · H1 · candles
100
2× weight
orderoldest first
limit ceiling1,000
retention (H1)14 days
paginationwalk `to`
row shapet·o·h·l·c·v
scopehistorical
bid/ask on candleno
timestampsUTC
{
  "success": true,
  "data": {
    "symbol": "EURUSD",
    "timeframe": "H1",
    "candles": [                    // "candles" - there is no "bars" key
      { "time": "2026-09-18T06:00:00Z", "open": 1.08210,
        "high": 1.08245, "low": 1.08192,
        "close": 1.08231, "volume": 8432 },
      { "time": "2026-09-18T07:00:00Z", … },
      … oldest first, 100 of them
    ],
    "count": 100,
    "retention": "14 days"       // the LIVE figure, from the server
  }
}
1,000Candles per call
42Indicator series
7Timeframes
45dDeepest retained window
What you can pull

Five surfaces, three different ceilings.

The ceilings are the part worth reading before you write the fetch loop: a candle request can span the whole retention window, an indicator-history request can only span half of it, and a tick request can span one hour. Each limit is enforced with a named 400 that tells you the exact number.

Surface Max window Rows Weight Scope Plan Returns
GET /v1/ohlc Full retention limit 1 to 1000, default 100 2× historical Any paid plan Candles: time, open, high, low, close, volume — plus count and the live retention string.
GET /v1/indicator/history Half the retention limit 1–5,000, capped per timeframe 5× historical Starter, Pro, Enterprise, PAYG One series as a time series: series[] of {time, value}, plus from, to, count and max_window_hours.
GET /v1/multi (with from/to) Half the retention ≤10 symbols, 5,000 rows total 2× per call premium Starter, Pro, Enterprise, PAYG Several series across several symbols at one timeframe. An unknown series name is a 404, not an empty column.
GET /v1/ticks 1 hour per request Hard cap of 50,000 rows 3× historical Pro and Enterprise only Raw ticks: time, bid, ask, flags. from and to are both required; 24-hour retention.
GET /v1/spread 1h, 24h, 7d or 30d Aggregates, not rows 1× none Every plan Current, average, minimum, maximum and standard deviation of the spread, for cost modelling.

/v1/indicators is not a history endpoint. It returns the current cached snapshot for one symbol and timeframe and has no time-range parameters at all. For a series over time use /v1/indicator/history, or /v1/multi with from and to when you want several series across up to ten symbols in one request.

Depth by timeframe

Retention is per timeframe. Never per plan.

The resolver that answers "how far back does this go" takes a timeframe and nothing else — there is no plan argument anywhere in it, so a pay-as-you-go account and an Enterprise account read the identical window. The right-hand columns are the separate, tighter ceiling that applies to the historical indicator endpoints: half the retention, and the candle count that fits inside it.

Window and max-candle figures are computed here from the retention column using the endpoint's own arithmetic, so the two can never disagree. The live value is always in data.retention on an OHLC response and in max_window_hours on an indicator-history response.
Timeframe Retention ≈ candles retained Indicator-history window Max candles per history call
M1 — 1 Minute 1 d 1,440 12 h 720
M5 — 5 Minutes 2 d 576 1 d 288
M15 — 15 Minutes 4 d 384 2 d 192
M30 — 30 Minutes 7 d 336 3.5 d 168
H1 — 1 Hour 14 d 336 7 d 168
H4 — 4 Hours 30 d 180 15 d 90
D1 — Daily 45 d 45 22.5 d 22

Raw ticks sit on the M1 schedule: 24 hours. They are cleaned up with the same retention key as M1 data, so tick-level work is always a rolling window. Combined with the one-hour range cap and the 50,000-row ceiling, a full day of ticks for one symbol is 24 requests at 3× each.

Parameter reference

Five parameters on the candle endpoint.

One of them is required and one of them behaves in a way that quietly breaks naive pagination. Invalid values are rejected with a named error code and the acceptable set in the detail rather than being coerced into something that silently changes your result.

Parameter Type Required Default Behaviour
symbol string required — Canonical symbol; the response echoes the canonical name.
timeframe string optional H1 M1, M5, M15, M30, H1, H4, D1. Uppercased for you. Anything else is a 400 with INVALID_TIMEFRAME and the valid set in the detail.
from string optional retention boundary ISO 8601 start. Omit it and the window opens at the earliest retained candle. Earlier than that is a 400 with OUTSIDE_RETENTION, which names earliest_available so you can retry correctly.
to string optional now ISO 8601 end. This is the parameter you move when paging backwards, because there is no offset.
limit integer optional 100 Candles to return, 1 to 1000 inclusive, validated rather than clamped. The query sorts newest-first before applying it, so limit returns the NEWEST n inside the window — then hands them back oldest-first.

There is no offset. The query sorts newest-first, applies limit, then reverses — so limit selects the newest candles in the window and returns them oldest-first. Page by moving to back to the timestamp of the first candle you received. On /v1/indicator/history the ordering is the other way round: rows are read oldest-first, so limit there truncates the end of the window, not the beginning.

Request pattern

Pull once. Iterate locally.

Indicator history is 5× per call per symbol, so the expensive mistake is re-requesting the same series for every parameter you want to try. Fetch each series once, cache it on disk, and run the sweep against the cached copy.

  • Walk to backwards; there is no offset.
  • Join candles and series on time, not by index.
  • Treat OUTSIDE_RETENTION as "end of history".
  • All timestamps are UTC; boundaries align to the timeframe.
# newest 100 H1 candles
curl -H "X-API-Key: YOUR_API_KEY" \
  "https://tickatlas.com/v1/ohlc?symbol=EURUSD&timeframe=H1&limit=100"

# one indicator as a SERIES - note the window ceiling
curl -H "X-API-Key: YOUR_API_KEY" \
  "https://tickatlas.com/v1/indicator/history?symbol=EURUSD&indicator=RSI_14&timeframe=H1"
# page BACKWARDS. There is no offset on /v1/ohlc - the
# query takes the newest rows inside [from, to], so you walk "to".
URL = "https://tickatlas.com/v1/ohlc"
H   = { "X-API-Key": KEY }

def pull_all(symbol, timeframe="H1"):
    out, to = [], None
    while True:
        p = { "symbol": symbol, "timeframe": timeframe, "limit": 1000 }
        if to: p["to"] = to

        r = requests.get(URL, headers=H, params=p)
        if r.status_code == 400:
            # OUTSIDE_RETENTION names earliest_available - we are done
            break
        d = r.json()["data"]
        if not d["candles"]: break

        out = d["candles"] + out        # response is oldest-first
        to  = d["candles"][0]["time"]  # step the window back
        if d["count"] < 1000: break

    return out                       # d["retention"] is the real depth

# Descriptive crossover measurement - counts state changes between
# two series and records the realised difference between them.
# It is arithmetic over past data, not a rule to trade.
sma20 = series("SMA_20")     # /v1/indicator/history -> data.series
sma50 = series("SMA_50")     # align on "time" - do NOT assume equal lengths

pairs = { p["time"]: p["value"] for p in sma50 }
joined = [(p["time"], p["value"], pairs[p["time"]])
          for p in sma20 if p["time"] in pairs]

crossings = []
for (t0, a0, b0), (t1, a1, b1) in zip(joined, joined[1:]):
    if (a0 - b0) * (a1 - b1) < 0:
        crossings.append({ "time": t1,
                           "state": "fast_above" if a1 > b1 else "fast_below" })

print(len(crossings), "state changes over", len(joined), "aligned bars")
data.count
100 EUR/USD · H1 · oldest first · retention "14 days"
no offset field
Candle shape{ time, open, high, low, close, volume }
Series shape{ time, value }
Tick shape{ time, bid, ask, flags }
Depth, from the serverretention · max_window_hours
Three shapes, one timestamp

Everything joins on time.

Candles, indicator series and ticks all carry a UTC timestamp aligned to the timeframe that produced them, which makes assembling a feature matrix a dictionary join rather than an interpolation problem. Join on the key rather than by position: an indicator row is skipped when its value is null, so two series for the same window can differ in length.

Timeframes

The same 7 intervals, with different depth.

M1, M5, M15, M30, H1, H4, D1, defaulting to H1. Higher timeframes keep more calendar time and fewer rows; lower timeframes keep more rows over less time. Nothing above D1 is accepted by the historical endpoints — W1 exists only inside /v1/heatmap, and MN1 is accepted nowhere.

  • M1 — 1 Minute
  • M5 — 5 Minutes
  • M15 — 15 Minutes
  • M30 — 30 Minutes
  • H1 — 1 Hour (default)
  • H4 — 4 Hours
  • D1 — Daily
Developer-first behavior

Properties a backtest depends on.

A backtest is only worth running if the data it reads is the data the live system would have read. These are the four guarantees that make that true here — and the one place the platform deliberately does not promise.

PROVENANCE

One deterministic row per timestamp

DISTINCT ON the timestamp picks one row, consistently. A series is never an interleaved mix, and limit counts rows, not duplicates.

  • No silent range shrink
ALIGNMENT

UTC, on the boundary

Every timestamp is UTC and every candle starts on its timeframe boundary — H1 on the hour, H4 at 00:00, 04:00, 08:00 and so on. No local time, no daylight-saving drift inside the data.

  • Joinable across timeframes
PARITY

Same values as the live endpoint

Indicators are computed once and stored; the live and historical endpoints read the same rows. A backtest and a live run do not diverge because of a second implementation.

  • One pipeline, two readers
HONEST LIMITS

Errors name the ceiling

OUTSIDE_RETENTION returns earliest_available; RANGE_TOO_LARGE returns max_window_hours and max_candles. You never have to guess a limit from a page.

  • Machine-readable retry hints
Developer API pricing

Candles and history from pay-as-you-go up. Ticks from Pro.

/v1/spread works on every plan and /v1/ohlc on every paid plan, including pay-as-you-go, and historical indicator series work from pay-as-you-go up. Tick data is Pro and Enterprise only — that is the one gate worth planning a research budget around.

There is no free tier and no self-serve trial. Every account starts on pay-as-you-go with $2.50 of prepaid credit, no card and no overage — monthly plans lift the starting quota.
Pay as you go
$0 to start 200 requests/day · 30/min until you top up
  • $2.50 of credit included
  • Every REST endpoint except raw ticks
  • No card required, no overage
  • 10 API keys
Start free
Starter
$29/mo 10,000 requests · 120/min
  • Every REST endpoint except raw ticks
  • WebSocket streaming, 5 symbols
  • Released calendar actuals
  • Email support
  • 3 API keys
View plan
Enterprise
$349/mo 1,000,000 requests · 6000/min
  • Everything in Pro
  • Dedicated support
  • 100 API keys
  • Custom indicators
  • SLA guarantee
  • On-premise option
Contact sales
Common software patterns

Fetch is the slow part. Do it once.

Every research workflow built on this data ends up in the same shape: one bounded pull into local storage, then as many passes over the cache as the question needs.

Replay harnesses

Pull the candles once, pull each indicator series once, align on the time key and step the result. The indicator values were computed once, at the candle close, so a replay reads the same numbers a live run would have seen.

replaywalk-forward

Parameter sweeps

The 42 published series include several periods of the same family — SMA at five lengths, EMA at three, ATR at two, CCI at two — so comparing period choices is a second series request rather than a second implementation.

sweepscomparison

Feature extraction

A time-aligned join of candles and indicator series is a feature matrix. Because the values are pre-computed and identical across every consumer, a training set built today matches what the live API will serve tomorrow.

ML featuresdatasets
Frequently asked questions

Historical data, clarified.

How deep the window really is, why the indicator endpoints stop at half of it, how to page without an offset, and what the data is and is not checked against.

How far back does the data go?

Retention is set per timeframe and never per plan — settings.get_retention_hours(timeframe) takes no plan argument, so Enterprise and pay-as-you-go read exactly the same window. As deployed that is 45 days on D1, 30 on H4, 14 on H1, 7 on M30, 4 on M15, 2 on M5 and 24 hours on M1, with ticks on the same 24-hour schedule as M1. Treat those as the documented values and the API as the authority: every /v1/ohlc response repeats the live figure back in data.retention, and an out-of-range request returns 400 OUTSIDE_RETENTION carrying earliest_available.

Why did my indicator history request fail with RANGE_TOO_LARGE?

Because the query window on the historical indicator endpoints is capped at half the retention period for that timeframe — not the whole of it. /v1/ohlc will serve the full retention in one call; /v1/indicator/history and /v1/multi with from/to will not. The 400 carries max_window_hours and max_candles for the timeframe you asked about, so the retry is mechanical: split the range into windows of that size and walk them.

How do I paginate candles? There is no offset.

Correct, and this is the part that surprises people. The query sorts by candle time descending, applies limit, then reverses — so limit returns the NEWEST n candles inside [from, to] and hands them back oldest-first. To walk backwards, set `to` to the time of the first candle you received and repeat until the response is short or you hit OUTSIDE_RETENTION. Incrementing an imaginary offset will silently return the same page forever.

Can I get indicator values alongside candles in one call?

No. /v1/ohlc returns candles and nothing else; /v1/indicators returns the CURRENT cached snapshot for one symbol and timeframe with no time-range parameters at all. The historical series come from /v1/indicator/history, one series per call, or /v1/multi with from and to for several series across up to ten symbols. They share the same candle timestamps, so a join on time is exact — but align on the key rather than assuming the two arrays are the same length, because a series row is skipped when its column is null.

Which plans include tick data?

Pro and Enterprise only. The route carries an explicit allowlist and returns 403 PLAN_UPGRADE_REQUIRED for every other plan, including pay-as-you-go and Starter. Both from and to are required, the range is capped at one hour per request, the result is hard-limited to 50,000 rows inside the query, and tick retention is 24 hours — so tick-level work is a rolling-window exercise, not a deep-history one.

What is in a candle?

time, open, high, low, close and volume — volume as our data carries it for that candle. There is no bid, ask or spread on a candle. Spread statistics come from /v1/spread, which aggregates current, average, minimum, maximum and standard deviation over 1h, 24h, 7d or 30d, and that is the endpoint to use when you want realistic cost modelling in a backtest.

Can a series interleave two rows per timestamp?

No. Candle and tick queries are DISTINCT ON the timestamp, so one deterministic row wins per timestamp. A series is never an interleaved mix, and limit counts rows, not duplicates.

How good is the data?

Every incoming record is checked on ingest against that symbol’s own last cached mid and skipped if the move exceeds the threshold for its asset class — 5% for FX majors, 15% for gold, 25% for oil, 10% for indices, 50% for the large crypto pairs — plus a separate rejection for a quoted spread above 10% of mid. Separately, a background scan walks stored candles every two minutes, learns each symbol’s own trading hours from its history rather than assuming a schedule, and queues a backfill for anything missing. Both are outlier and gap handling on our data, and neither promises a completion time.

Is there survivorship bias in the symbol list?

The set of tracked symbols follows our data and changes when it does, so it is not a fixed universe and a backtest run today may cover instruments a backtest run last month did not. FX pairs are not delisted the way equities are, which removes the classic form of the problem, but the honest answer is that coverage is operational rather than curated. GET /v1/symbols returns the live list, filterable by category and paginated up to 500 rows a page.

What does a full history pull cost?

Candles are 2× per call and carry up to 1,000 rows, so a deep pull is cheap per row. Indicator history is 5× per call and per symbol, which is where a careless sweep gets expensive: fetch each series once, cache it locally, and iterate your parameters against the cached copy rather than re-requesting. Ticks are 3× for at most one hour of data. All timestamps are UTC and candle boundaries align to the timeframe, so a locally cached series stays joinable indefinitely.

Candles, 42 series, ticks and spreads — one key

Test it on the data you will trade on.

The same rows the live endpoints read, on the same timestamps, with every limit reported by the API instead of guessed from a page. Start on your $2.50 of credit, no card.