Query events
https://api.tickerbot.io/ v2/ eventsOne timeline across every ticker: earnings, dividends, splits, insider filings, analyst actions, plus opt-in signal firings and news.
Query parameters
enum[]Comma list of kinds. Omitted → the five corporate kinds; signal and news join only when named here.
earnings | Quarterly earnings reports — reported vs estimated EPS and the surprise, timestamped at the report date. |
dividend | Cash-dividend declarations, timestamped at the ex-dividend date. |
split | Stock splits and reverse splits, timestamped at the execution date. |
insider | Officer and director trades — one event per reported Form 4 transaction. |
analyst | Upgrades, downgrades, initiations, and price-target changes. Lands within the hour, 24/7. |
news | Headlines, one event per article × tagged ticker. Opt-in — joins the stream only when named. |
signal | Boolean flips as enter/exit point-events. Opt-in — joins the stream only when named. |
stringSingle-ticker filter. When both ticker and tickers are passed, ticker wins.
string[]Comma list of tickers (max 50). Mutually exclusive with universe.
stringUniverse slug (top_10, top_100, or one of yours) to scope the stream. Mutually exclusive with tickers.
stringAnalyst-only structured filter — requires kind=analyst alone (400 otherwise). Exact firm-name match on the ratings feed.
enumAnalyst-only structured filter — requires kind=analyst alone. Same action vocabulary as Analyst actions.
upgrades | Rating raised. |
downgrades | Rating lowered. |
initiates_coverage_on | First rating from this firm. |
maintains | Rating unchanged, restated. |
reiterates | Rating unchanged, emphasized. |
assumes | Coverage transferred to a new analyst at the firm. |
reinstates | Coverage resumed after a pause. |
suspends | Rating suspended — coverage paused without being dropped. |
terminates_coverage_on | Coverage dropped entirely. |
stringSignal-only filter — requires kind=signal alone (400 otherwise). One built-in boolean signal; REQUIRED with q or join=state on that kind. See Signal firings.
enumSignal-only filter — requires kind=signal alone. enter (false→true) or exit (true→false).
stringEvents at/after this instant — strict ISO: YYYY-MM-DD or YYYY-MM-DDTHH:MM[:SS]Z. A bare YYYY-MM-DD means from the start of that day. (since accepted as an alias.)
stringWindow end — same strict ISO subset. A bare YYYY-MM-DD means through the end of that day, matching bars/series/spans; a timestamp is exclusive (events strictly before it). (until accepted as an alias.)
stringSQL WHERE over the projection — ticker, ts, kind, payload (plus ticker-state signals when join=state). When exactly ONE kind is named, that kind's payload fields are additionally first-class typed columns (amount > 1, firm = 'Goldman Sachs' — see each kind page for its list); multi-kind requests use payload->>'…'. Max 4000 chars. ANDs with the filter params. See the signals catalog for columns + flags you can compose.
enumSet to state to allow ticker-state signals in q/select/group_by/having, evaluated as of each event's timestamp (daily resolution).
state | Widen the grammar to ticker-state columns, evaluated as of each event’s timestamp. Resolves at daily grain. |
enumdefault autoGrain the per-event state is reconstructed at, when join=state: 1m, 1h, 1d, or auto (default). auto resolves to 1d — the event set's tickers are not known before the query runs, and 1d is the only tier covering the whole universe, so it is the only grain guaranteed to satisfy every event. An explicit 1m/1h trades coverage for precision: events on tickers absent from that tier join to null. A referenced column the grain does not store is a 400. Reported back as _meta.state_interval.
1m | One minute. The finest stored tier; carries the intraday column subset and ~1,100 of ~13,700 tickers. |
1h | One hour. Stored tier, intraday column subset, full universe. |
1d | One day. The full-history, full-column tier — the only grain that carries daily-only columns (SMAs, RSI, fundamentals). |
autodefault | Let the server pick the finest grain whose columns and ticker coverage both work out, resolving upward on a miss. Reported in _meta.interval. |
string[]Aggregate-mode output columns (requires group_by). Default: group keys + COUNT(*) AS events. Same naming rule as group_by — alias with AS, or take the name derived for you.
string[]Comma list of rollup keys — switches the response to aggregate rows. Columns (kind, ticker), payload fields (firm, or the explicit payload->>'firm'), and expressions over them all roll up. Name a key with AS to choose its JSON key: payload->>'firm' AS firm. Un-named keys are named for you — a payload read takes its key (payload->>'firm' → firm), a function keeps the function's name (lower(ticker) → lower), and anything else falls back to group_1, group_2.
stringPost-aggregation filter. Requires group_by.
stringAggregate-mode sort — a bare column name or an output name only (put expressions in select and sort by their alias). A group key's name works too, whether you aliased it or it was named for you: group_by=payload->>'firm' AS firm&order=firm. Default: events. (Row mode is always newest-first.)
enumdefault descAggregate-mode sort direction.
asc | Ascending — smallest or earliest first. |
descdefault | Descending — largest or most recent first. |
integerdefault 50Page size (row modes) / max rollup rows (aggregate mode). Max 1000.
stringOpaque cursor from the previous response — carries the original filters (and q when short), so pass it alone. Not valid with group_by.
Returns
as_ofstringServer time this response was assembled (ISO 8601).
queryobjectYour filters, echoed exactly as you sent them — `q`, `select`, `group_by` and `having` come back in your spelling, not the SQL they compile to — including `join` and its grain when you passed `join=state`.
countnumberRows in this page.
next_cursorstringOpaque token for the next page; `null` on the last page. Carries `q_truncated: true` alongside it when an oversized `q` could not ride the token — resend `q` on later pages.
truncatedbooleanAggregate mode only (`group_by`): `true` when the rollup stopped at its row cap. Aggregate responses are unpaged, so `next_cursor` is absent there.
resultsarrayOne row per event (`ticker`, `ts`, `kind`, `payload`), or rollup rows plus `truncated: true` when an aggregate exceeds `limit`.
_metaobjectWith `join=state` only: `state_interval`, the grain the joined state was read at, and `deprecated_columns` when a state column was named under its pre-2026-09-07 spelling and served under its current name (`requested`, `use`, `note`).
Status codes
200400kind, >50 tickers, tickers+universe together, no bound at all, kind=signal with q/join=state but without signal= (or with group_by at all), invalid timestamps/cursor, or invalid_query — an identifier outside the projection (or outside the widened set under join=state), a disallowed function, select/having/order without group_by, or cursor with group_by.404universe does not exist.503kind_unavailable — the signal event log is temporarily unavailable; retry later. The other kinds are unaffected. Validation runs first, so a 503 means the query itself is well-formed — a request that would 400 still 400s while the log is down.Notes
- Rows are
{ ticker, ts, kind, payload }, newest first. Payload fields per kind match the legacy/v2/tickers/{ticker}/eventsand/v2/analyst/eventsspellings, so switching here is a URL swap, not a reshape — and both of those are deprecated with a 2026-10-31 sunset, with this endpoint as their successor. - The default stream is the five corporate kinds, each backed by its own archive:
earnings,dividend,split,insider,analyst. Two more join as explicit opt-ins, named inkind:signal— boolean firings as enter/exit point-events — andnews, mirroring/v2/news. - The endpoint speaks the SQL grammar over a normalized projection of exactly four columns —
ticker,ts,kind,payload(jsonb).qfilters rows (kind='analyst' AND payload->>'action'='downgrades');group_bywith optionalselect/havingswitches to aggregate rollups. Payload fields are reached with jsonb operators and cast as needed:(payload->>'shares')::numeric > 1e6. No other identifiers are accepted — this is the event log's own grammar, not the ticker-state scan grammar. - `join=state` widens the grammar to ticker-STATE signals, evaluated against each event's ticker as of the event's timestamp. "Downgrades on stocks above their 200-day" is one call:
kind=analyst&join=state&q=payload->>'action'='downgrades' AND above_sma_200 = true. State resolves at daily resolution; thequeryecho carriesjoin: "state"with the deprecatedstate_resolution: "1d", and_meta.state_intervalis the canonical grain field. Works in row and aggregate mode. - Requires at least one bound — a ticker scope or a time window;
qalone does not count, and a barekindover millions of rows is refused. kind=signalneeds the signal named before it will take the grammar:qandjoin=stateover the ~175M-row firing log requiresignal=<name>, which is what lets the query ride an index instead of scanning (transition=enter|exitis optional).group_byover that kind is unavailable — an aggregate has nolimitto stop at, so it reads every firing in the window. Filter mode needs none of this.- Aggregates do not paginate: past
limityou gettruncated: true— narrow the window instead. - The cursor pages strictly *older* than the last row's timestamp, and the corporate archives are date-grained, so same-date remainders can fall on a page boundary. Raise
limit(max 1000) to avoid them. Aqtoo long to ride inside the cursor token setsq_truncated: true— resendqalongsidecursoron later pages. - Both
GETandPOSTare supported;POSTtakes the same parameters in the body for long queries.
More examples
curl -G "https://api.tickerbot.io/v2/events" \
-H "Authorization: Bearer YOUR_KEY" \
--data-urlencode "kind=analyst,split" \
--data-urlencode "from=2026-08-17" \
--data-urlencode "group_by=kind"{
"as_of": "2026-08-11T23:05:43.031Z",
"query": { "kind": "analyst,split", "tickers": null, "universe": null, "since": "2026-07-12T00:00:00.000Z", "until": null, "q": null, "select": null, "group_by": ["kind"], "having": null, "order": "events", "dir": "desc", "limit": 50 },
"count": 2,
"results": [
{ "kind": "analyst", "events": 9370 },
{ "kind": "split", "events": 145 }
]
}curl -G "https://api.tickerbot.io/v2/events" \
-H "Authorization: Bearer YOUR_KEY" \
--data-urlencode "kind=analyst" \
--data-urlencode "from=2026-08-17" \
--data-urlencode "q=action='downgrades'" \
--data-urlencode "group_by=payload->>'firm' AS firm" \
--data-urlencode "having=COUNT(*) > 5" \
--data-urlencode "limit=5"{
"as_of": "2026-08-12T16:54:39.884Z",
"query": { "kind": "analyst", "tickers": null, "universe": null, "since": "2026-07-03T00:00:00.000Z", "until": null, "q": "action='downgrades'", "select": null, "group_by": ["payload->>'firm' AS firm"], "having": "COUNT(*) > 5", "order": "events", "dir": "desc", "limit": 5 },
"count": 5,
"truncated": true,
"results": [
{ "firm": "Morgan Stanley", "events": 42 },
{ "firm": "JP Morgan", "events": 29 },
{ "firm": "Barclays", "events": 26 },
{ "firm": "Wells Fargo", "events": 21 },
{ "firm": "Jefferies", "events": 20 }
]
}curl -G "https://api.tickerbot.io/v2/events" \
-H "Authorization: Bearer YOUR_KEY" \
--data-urlencode "kind=analyst" \
--data-urlencode "from=2026-08-17" \
--data-urlencode "group_by=firm" \
--data-urlencode "limit=3"{
"as_of": "2026-08-12T16:54:42.769Z",
"query": { "kind": "analyst", "tickers": null, "universe": null, "since": "2026-07-03T00:00:00.000Z", "until": null, "q": null, "select": null, "group_by": ["firm"], "having": null, "order": "events", "dir": "desc", "limit": 3 },
"count": 3,
"truncated": true,
"results": [
{ "firm": "Barclays", "events": 777 },
{ "firm": "Wells Fargo", "events": 702 },
{ "firm": "UBS", "events": 669 }
]
}curl -G "https://api.tickerbot.io/v2/events" \
-H "Authorization: Bearer YOUR_KEY" \
--data-urlencode "kind=analyst" \
--data-urlencode "join=state" \
--data-urlencode "from=2026-08-17" \
--data-urlencode "q=payload->>'action'='downgrades' AND above_sma_200 = true"{
"as_of": "2026-08-11T23:02:40.660Z",
"query": { "kind": "analyst", "tickers": null, "universe": null, "since": "2026-07-12T00:00:00.000Z", "until": null, "q": "payload->>'action'='downgrades' AND above_sma_200 = true", "limit": 2, "join": "state", "state_resolution": "1d" },
"_meta": { "state_interval": "1d" },
"count": 50,
"next_cursor": "eyJmIjp7ImtpbmQiOiJhbmFseXN0In19",
"results": [
{ "ticker": "CZR", "ts": "2026-07-29T12:04:00.000Z", "kind": "analyst",
"payload": { "firm": "TD Cowen", "action": "downgrades", "rating": "Hold", "previous_rating": "Buy", "price_target": 32, "previous_price_target": 51 } },
"(...)"
]
}