View as markdown
Endpoints · Events

Signal spans

GEThttps://api.tickerbot.io/v2/signals/{signal}/{ticker}/events

Each firing of one signal on one ticker as a span — when it turned on, when it turned off.

stringrequired

Built-in boolean flag name (e.g. above_sma_50, golden_cross). Numeric fields are not valid here; custom signals return 400.

stringrequired

Ticker symbol (case-insensitive). Equity-only: flag_events has no rows for non-equity instruments, which carry price and no flags.

string

Window 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.

string

Window 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 1000

Max rows returned, newest first. Clamped to 1000 — page past it with cursor.

string

Opaque 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 0

Interval-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.

as_ofstring

Server time this response was assembled (ISO 8601).

tickerstring

The symbol you asked for.

signalstring

The flag you asked for.

countnumber

Spans in this page.

next_cursorstring

Opaque token for the next page, walking older; `null` on the last page. Resend `merge_gap_seconds` unchanged alongside it.

eventsarray

One row per firing: `started_at`, `ended_at` (`null` while open), `start_price`, `end_price`, `definition_version`.

200
Success — the response shape is documented under Returns above.
400
bad_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).
503
unavailable — the span archive is temporarily unavailable; retry later.
  • The same facts /v2/events?kind=signal serves 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 the q grammar, so "every golden_cross enter across the market this week" is a question only that one can answer. merge_gap_seconds gap-merging exists only here.
  • For state-style signals (above_sma_50, in_uptrend, at_52w_high, …) each row is a true-window: started_at is when the condition flipped false→true, ended_at when it flipped back (or null while it is still true), and start_price/end_price carry the price at each edge.
  • For event-style signals (golden_cross, gap_up, sticky_breakout, …) each row is a point firing: started_at is when it fired, ended_at is the same value, and start_price is the price at that bar.
  • Boolean flags only. Spans come from the precomputed flag_events table, 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=86400 collapses 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) returns 400 not_a_flag_signal, and a custom signal returns 400 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_cursor is a token, null on the last page. limit caps 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 real ended_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).
Recent breakout firings on NVDA (event-style — point rows)
curl "https://api.tickerbot.io/v2/signals/breakout/NVDA/events?limit=2" \
  -H "Authorization: Bearer YOUR_KEY"
Response
{
  "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 }
  ]
}