Signal spans
https://api.tickerbot.io/ v2/ signals/ {signal}/ {ticker}/ eventsEach firing of one signal on one ticker as a span — when it turned on, when it turned off.
Query parameters
stringrequiredBuilt-in boolean flag name (e.g. above_sma_50, golden_cross). Numeric fields are not valid here; custom signals return 400.
stringrequiredTicker symbol (case-insensitive). Equity-only: flag_events has no rows for non-equity instruments, which carry price and no flags.
stringWindow start (inclusive) on each span's started_at: YYYY-MM-DD, ISO timestamp, or epoch-ms. "Golden crosses in June" is from=2026-06-01&to=2026-06-30.
stringWindow end (inclusive) on started_at; a bare YYYY-MM-DD means through the end of that day. With merge_gap_seconds, the bound applies to the merged run's start.
integerdefault 1000Max rows returned, newest first. Clamped to 1000 — page past it with cursor.
stringOpaque token from a previous response's next_cursor. Resend merge_gap_seconds unchanged alongside it — a cursor pins the mode it was minted in.
integerdefault 0Interval-union: contiguous windows whose gap is ≤ N seconds collapse into one. Default 0 = no merge — and unmerged spans are genuinely per-tick, often seconds long, because the writer records every evaluation. Pass 86400 to get one row per day-scale run, which is what most readers mean by an occurrence.
Returns
as_ofstringServer time this response was assembled (ISO 8601).
tickerstringThe symbol you asked for.
signalstringThe flag you asked for.
countnumberSpans in this page.
next_cursorstringOpaque token for the next page, walking older; `null` on the last page. Resend `merge_gap_seconds` unchanged alongside it.
eventsarrayOne row per firing: `started_at`, `ended_at` (`null` while open), `start_price`, `end_price`, `definition_version`.
Status codes
200400bad_request (invalid flag or ticker format), not_a_flag_signal (a numeric field like rsi_14 — use /v2/series or /v2/scan?asof=), or not_supported_for_custom_signal (custom signal name).503unavailable — the span archive is temporarily unavailable; retry later.Notes
- The same facts
/v2/events?kind=signalserves as enter/exit point-events, run-length-encoded into intervals instead. Both shapes are first-class; the choice is scope. This route answers about one ticker at a time — signal and ticker are both in the path — while the point-event shape is cross-ticker and speaks theqgrammar, so "everygolden_crossenter across the market this week" is a question only that one can answer.merge_gap_secondsgap-merging exists only here. - For state-style signals (
above_sma_50,in_uptrend,at_52w_high, …) each row is a true-window:started_atis when the condition flipped false→true,ended_atwhen it flipped back (ornullwhile it is still true), andstart_price/end_pricecarry the price at each edge. - For event-style signals (
golden_cross,gap_up,sticky_breakout, …) each row is a point firing:started_atis when it fired,ended_atis the same value, andstart_priceis the price at that bar. - Boolean flags only. Spans come from the precomputed
flag_eventstable, which exists only for built-in boolean flags. A numeric field (rsi_14,adx_14, …) has no on/off windows — read it over time with/v2/series, or find the days a threshold held with/v2/scan?asof=. Custom (expression) signals have no precomputed spans and are rejected with a 400. - UNMERGED spans are per-tick. The first example is three real rows spanning 14 minutes, 44 seconds and 60 seconds — all inside one morning — because the writer records each evaluation separately. That is the raw truth, not a fault, but it is rarely what "an occurrence of above_sma_50" means to a reader.
merge_gap_seconds=86400collapses the same data into day-scale runs (since 2026-08-04 those three rows merge into one run ending 2026-08-10). Pick the merge window deliberately; the Try-It demo on this page uses 86400. - Boolean flags only. A numeric field name (e.g.
rsi_14) returns400 not_a_flag_signal, and a custom signal returns400 not_supported_for_custom_signal— for those, reach for/v2/series(numeric series) or/v2/scan?asof=(days a threshold held) instead. - Cursor-paged like the rest of the read surface:
next_cursoris a token,nullon the last page.limitcaps a page at 1000. (Before 2026-08-05 this endpoint had no cursor, which is why its cap was 5000 — without paging, a lower cap made spans past it unreachable rather than merely truncating a page.) - A still-open window has
ended_at: null. Once it closes, the same window will reappear in subsequent calls with a realended_at. - Coverage matches
/v2/series: state signals are computed against daily bars (deep history) or minute bars (~1,000 most-liquid tickers, 2-year window).
More examples
curl "https://api.tickerbot.io/v2/signals/breakout/NVDA/events?limit=2" \
-H "Authorization: Bearer YOUR_KEY"{
"as_of": "2026-08-11T23:23:33.505Z",
"ticker": "NVDA",
"signal": "golden_cross",
"count": 2,
"next_cursor": "eyJiZWZvcmVfc3RhcnRlZF9hdCI6IjIwMjUtMDYtMjdUMDQ6MDA6MDAuMDAwWiI…",
"events": [
{ "started_at": "2025-06-27T20:00:00.000Z", "ended_at": "2025-06-28T20:00:00.000Z", "start_price": 157.75, "end_price": 157.75, "definition_version": 6 },
{ "started_at": "2025-06-27T04:00:00.000Z", "ended_at": "2025-06-27T04:00:00.000Z", "start_price": 157.75, "end_price": 157.75, "definition_version": 1 }
]
}