Tick API · Pro and Enterprise

Both sides of the book, second by second.

GET /v1/ticks returns the stored bid/ask samples for an hour-sized window, ascending, one row per second. Each row is stamped with the GMT second of the publish batch it arrived in — so it is second-resolution quote history, not a reconstruction of every individual market print. It is the finest resolution the platform stores.

1 hour max per request 1-second resolution 3× request weight
tickatlas.com / v1 / ticks 200 OK
GET /v1/ticks?symbol=EURUSD&from=2026-09-18T14:00:00Z&to=2026-09-18T15:00:00Z
EUR/USD · 14:00–15:00 UTC · count
874
3× weight
fields per rowtime · bid · ask · flags
ordertick_time ascending
resolutionwhole seconds · max 3,600/hour
planspro · enterprise
window cap1 hour
retention24h (M1 schedule)
key scopehistorical
spread fieldnone — derive ask − bid
{
  "success": true,
  "data": {
    "symbol": "EURUSD",
    "ticks": [
      { "time": "2026-09-18T14:32:00+00:00", "bid": 1.08432, "ask": 1.08445, "flags": 0 },
      { "time": "2026-09-18T14:32:04+00:00", "bid": 1.08435, "ask": 1.08447, "flags": 0 },
      { "time": "2026-09-18T14:32:08+00:00", "bid": 1.08421, "ask": 1.08436, "flags": 0 },
      { "time": "2026-09-18T14:32:12+00:00", "bid": 1.08425, "ask": 1.08438, "flags": 0 },
      { "time": "2026-09-18T14:32:16+00:00", "bid": 1.08456, "ask": 1.08469, "flags": 0 }
      … one object per sample, ascending, whole seconds, flags always 0
    ],
    "count": 874
  }
}
1 hourMax window
1sTimestamp resolution
24hRetention, all plans
3×Request weight
What a candle loses

Four numbers, or the sequence that produced them.

An M1 candle tells you the extremes of a minute. It cannot tell you the order they happened in, how long price spent near either end, or how the spread behaved while it got there — and those are precisely the questions a second-by-second series answers.

GET /v1/ohlc · M1

One candle

Open:  1.08432
High:  1.08456
Low:   1.08421
Close: 1.08443

You know the range. You do not know the path, the sequence, or what the spread was doing at the high.

GET /v1/ticks · same minute

The samples behind it

14:32:00   1.08432 / 1.08445   spread 0.00013
14:32:04   1.08435 / 1.08447   spread 0.00012
14:32:08   1.08421 / 1.08436   spread 0.00015
14:32:12   1.08425 / 1.08438   spread 0.00013
14:32:16   1.08456 / 1.08469   spread 0.00013
…
14:32:56   1.08443 / 1.08456   spread 0.00013

Both sides of the book on every sampled second — so the path, its order and the execution cost along it are recoverable. Spacing follows the update cycle of our data, so expect gaps rather than a row on every clock second.

Note what is not in the row. There is no spread field and no volume field in the response. Spread is ask − bid, computed by you; volume is not collected for samples, so anything that needs traded size — volume bars, VWAP — needs /v1/ohlc instead. flags is in the payload but is always 0.

Parameter reference

Three parameters. All three required.

There is no default window and no limit. That is deliberate: an open-ended tick query is an expensive accident, so the endpoint makes you name the hour you want.

from and to are query aliases — the route's own parameters are named differently internally, but the wire names are exactly from and to.
Parameter Type Required Behaviour
symbol string required Canonical symbol, e.g. EURUSD.
from string required ISO 8601 start of the window. A trailing Z is accepted; a naive value is treated as UTC. Unparseable is a 400 with INVALID_DATETIME.
to string required ISO 8601 end of the window. Must be at or after "from" — inverted is a 400 with INVALID_TIME_RANGE rather than an empty result set, because an empty 200 hides the caller's bug.
Limits and errors

Every ceiling, and the code you get for crossing it.

Three of these four are validation and easy to handle. The one to design around is the row ceiling, because it is the only limit that does not raise — it truncates.

Error code Status When
PLAN_UPGRADE_REQUIRED 403 The key’s plan is not one of pro or enterprise. The detail carries current_plan and an upgrade_url, so a client can route the user rather than guess.
INVALID_DATETIME 400 Either timestamp failed ISO 8601 parsing. The detail names the offending value.
INVALID_TIME_RANGE 400 "to" is before "from". Both values are echoed back.
RANGE_TOO_LARGE 400 The window exceeds 1 hour. The detail carries requested_range and max_range.

Where the real ceiling is. The query carries a LIMIT of 50,000 rows, but it is also DISTINCT ON a whole-second timestamp — so an hour cannot hold more than 3,600 rows and that LIMIT is never the binding constraint on an hour-sized window. The ceiling that matters is the resolution itself: one row per second, and only for the seconds a publish batch landed on.

Request pattern

Walk the clock, one hour at a time.

Because the window is capped, any longer period is a loop over hour-sized slices — and because the row cap is silent, that loop is also where you check count. Derive the spread yourself; the tick row does not carry one.

  • All three parameters required; window at most 1 hour.
  • A naive timestamp is treated as UTC; a trailing Z is accepted.
  • Check count against 50,000 on every response.
  • Key needs the historical permission scope.
# from and to are BOTH required, and the window is capped at 1 hour
curl -H "X-API-Key: YOUR_API_KEY" \
  "https://tickatlas.com/v1/ticks?symbol=EURUSD&from=2026-09-18T14:00:00Z&to=2026-09-18T15:00:00Z"
# walk a longer period one hour at a time
from datetime import datetime, timedelta, timezone

URL = "https://tickatlas.com/v1/ticks"
H = { "X-API-Key": KEY }

start = datetime(2026, 9, 18, 12, tzinfo=timezone.utc)
end   = start + timedelta(hours=4)

while start < end:
    stop = min(start + timedelta(hours=1), end)
    d = requests.get(URL, headers=H, params={
        "symbol": "EURUSD",
        "from": start.isoformat(), "to": stop.isoformat(),
    }).json()["data"]

    # one sample per second at most, so count <= 3600 for a full hour
    print(start, d["count"])

    # spread is DERIVED; there is no tick["spread"]
    for t in d["ticks"]:
        spread = t["ask"] - t["bid"]

    start = stop
// from and to are BOTH required and may not span more than one hour
const qs = new URLSearchParams({
  symbol: "EURUSD",
  from: "2026-09-18T14:00:00Z",
  to: "2026-09-18T15:00:00Z",
});

const res  = await fetch("https://tickatlas.com/v1/ticks?" + qs, {
  headers: { "X-API-Key": KEY },
});
const body = await res.json();

// a non-2xx uses the same envelope, with error instead of data
if (!res.ok) throw new Error(body.error.code);  // RANGE_TOO_LARGE, ...

const { data } = body;   // { symbol, ticks, count }

// spread is DERIVED; a row is { time, bid, ask, flags } and flags is always 0
const spreads = data.ticks.map((t) => t.ask - t.bid);
const mean = spreads.reduce((a, b) => a + b, 0) / spreads.length;
console.log(data.symbol, data.count, mean, Math.min(...spreads), Math.max(...spreads));
count
874 EUR/USD · one hour · max 3,600
ascending
Row shape{ time, bid, ask, flags }
DeduplicationDISTINCT ON (tick_time)
Empty windowticks [] · count 0 · still 200
Derived, not returnedspread = ask − bid
One row per second

A series you can trust to be a series.

Timestamps are whole seconds, so two rows could otherwise arrive stamped with the same second, in no defined order. DISTINCT ON the timestamp keeps one deterministic row per second, so the array you receive is a single coherent series.

Field reference

Everything in data.

The envelope is { "success": true, "data": { … } }. Three keys at the top, four inside each row.

Field Type What it carries
symbol string The canonical symbol.
ticks object[] The stored samples, ordered by time ascending. Empty array — not a 404 — when the window holds nothing.
ticks[].time string ISO 8601 at whole-second resolution. Every row in an update of our data is stamped with that update’s GMT second, so the fractional part is always zero and there is at most one row per second.
ticks[].bid number Bid at the moment the sample was taken. Not null — the column is required.
ticks[].ask number Ask at the moment the sample was taken. Spread is ask − bid; the endpoint does not pre-compute it.
ticks[].flags number Always 0, so do not branch on this value.
count number Length of the ticks array. One row per second means a full hour tops out at 3,600, well inside the 50,000-row query limit.
Retention

24 hours, identically on every plan.

Ticks are purged on the same cutoff as M1 candles — 24 hours by default, configurable per deployment. This is the one limit on the page that is deliberately not plan-gated: a Pro key and an Enterprise key see exactly the same depth, so history beyond the window is a collection problem, not an upgrade.

IN THE WINDOW

Query it directly

Anything inside the last 24 hours is one hour-sized request away. That covers same-day execution review, intraday microstructure work and rebuilding non-standard bars for the current session.

  • Same cutoff as M1, not a separate policy
  • Identical on Pro and Enterprise
BEYOND IT

Collect and keep

For a longer archive, run a scheduled job that pulls each hour once it has closed and writes it to your own store. One call per hour is 24 calls of quota per symbol per day for continuous coverage.

  • 24 hourly slices = 24 calls of quota
  • Check count on every slice
Developer-first behavior

The expensive endpoint is the strict one.

Tick queries are the heaviest thing this API does, so the guardrails are explicit and they fail early — before the database is touched, where that is possible.

GATE FIRST

Plan checked before parsing

The plan allowlist is the first thing the route evaluates, so an unentitled key gets a 403 without the symbol or the timestamps being touched.

  • 403 with upgrade_url
NO SILENT EMPTIES

An inverted range is an error

A to before from would return zero rows and look like "no data". It is a 400 with both values echoed instead, so a caller bug surfaces at the call site.

  • INVALID_TIME_RANGE
DEDUPLICATED

One row per second

DISTINCT ON (tick_time) keeps the series coherent: whole-second stamps would otherwise collide, so one deterministic row wins each second.

  • deterministic, ordered ascending
QUOTA

3× weight, historical scope

Its own step in the weighting table, above the 2× premium endpoints and below the 5× ones. The key needs the historical scope, as /v1/ohlc does.

  • X-API-Key: tk_…
Developer API pricing

Pro and Enterprise only.

It is the only REST endpoint that turns a pay-as-you-go or Starter key away. Those keys reach the rest of the REST surface — candles, quotes, indicators and their history, the screener, the heatmap, spreads — but a tick request returns 403.

Retention is identical across plans, so upgrading buys access to the endpoint, not more history. There is no free tier and no self-serve trial.
Pay as you go — no tick access
$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 — no tick access
$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 — tick access
$349/mo 1,000,000 requests · 6000/min
  • Raw ticks via /v1/ticks
  • Everything in Pro
  • Dedicated support
  • 100 API keys
  • Custom indicators
  • SLA guarantee
  • On-premise option
Contact sales
Common software patterns

Three reasons a candle will not do.

Every one of these needs the individual updates rather than a summary of them, which is the whole test for whether this endpoint is the right one.

Sub-minute bars

Range, renko and second-based bars need the samples inside the minute rather than a pre-aggregated one. An hour of samples is enough input to build any bar whose boundary is a price move or a span of seconds. Volume bars are not possible here — the response carries no volume.

range barsrenkoS5 / S15 bars

Spread behaviour

ask − bid on every sample is how a spread widening becomes a sequence rather than a single average — you see when it opened up and how long it stayed there. It is the layer beneath /v1/spread.

execution analysisliquidity

Model input

A second-resolution path between two candle closes is a far richer feature than the close alone. The 24-hour retention means the collection job is yours: pull, store, accumulate.

feature pipelinesresearch
Frequently asked questions

Ticks, clarified.

Who can call it, what a row actually contains, where the ceilings are — including the one that truncates silently — and how long the data lives.

Which plans can call this endpoint?

Pro and Enterprise only. The route checks the plan against an explicit allowlist of exactly {pro, enterprise} before it does anything else, and anything outside it is a 403 with PLAN_UPGRADE_REQUIRED, the current plan name and an upgrade URL in the detail. Pay-as-you-go and Starter reach the candle, quote, indicator, screener, heatmap and spread endpoints, but not raw ticks.

How much can one request return?

The window is capped at 1 hour — a wider one is a 400 with RANGE_TOO_LARGE, with your requested range echoed back. There is no limit parameter. Timestamps are whole seconds and the query is DISTINCT ON the timestamp, so a full hour can hold at most 3,600 rows; the query's own 50,000-row LIMIT sits far above that and is not something an hour-sized window reaches.

What is in a tick row?

Four fields: time, bid, ask and flags. time is ISO 8601 at whole-second resolution — every row in an update of our data is stamped with that update’s GMT second, so the fractional part is always zero. bid and ask are never null because the columns are required. flags is always 0. There is no spread field — derive it as ask − bid.

Is this every tick the market printed?

No, and the page does not claim it. What is stored is a bid/ask sample taken each time our data updates for that symbol, stamped with that update’s GMT second. So you get second-resolution history of where both sides of the book sat, not a reconstruction of every individual print. For studying spread behaviour, execution cost and the path price took between two candle closes that is the resolution the question actually needs; for order-by-order microstructure it is not, and we would rather say so than sell you an hour of data that cannot answer it.

What does flags mean?

Nothing yet. It is part of the response shape and the ingest path stores 0 for every row, so every tick you receive has flags 0. It is documented because it is in the payload, not because it carries information — do not branch on it.

Can two rows share a second?

No. The query is DISTINCT ON the whole-second timestamp, so the array holds one deterministic row per second: a single coherent series.

How long is tick data retained?

Ticks are purged on the same schedule as M1 candles, which is 24 hours by default and configurable per deployment. Retention is NOT plan-gated: a Pro key and an Enterprise key see exactly the same depth. Anything longer than that window is yours to collect and store — pull each hour as it becomes available and keep it.

What does a call cost?

One call against your daily quota, whether the response holds five ticks or fifty thousand. Its 3× weight is its own step in the weighting table, between the 2× endpoints and the 5× ones, and the key must carry the historical permission scope — the same scope /v1/ohlc and /v1/indicator/history require.

Is there a WebSocket tick stream?

Not for ticks. There is a live WebSocket at /ws/v1/quotes, available on Starter and above, but it pushes quote messages — bid, ask and symbol — rather than the stored tick rows this endpoint serves. For tick history the access pattern is REST over an hour-sized window.

How many rows will an hour actually give me?

At most 3,600 — one per second — and in practice fewer, because the sample cadence is the update cycle of our data for that symbol and a symbol only produces a row when our data updates it. TickAtlas does not publish a per-symbol figure it cannot hold for every instrument. The honest way to size it is to call one quiet hour and one active hour for the symbol you care about and compare count.

Bid and ask, second by second

Read the path, not the summary.

One hour-sized GET returns the second-by-second series a candle compresses away — enough to measure spread behaviour and execution cost rather than infer them. Available on Pro and Enterprise, weighted 3×, with 24 hours of depth on both.