← tickerbot.io
View as markdown

Query events

GEThttps://api.tickerbot.io/v2/events

One timeline across every ticker: earnings, dividends, splits, insider filings, analyst actions, plus opt-in signal firings and news.

enum[]

Comma list of kinds. Omitted → the five corporate kinds; signal and news join only when named here.

earningsQuarterly earnings reports — reported vs estimated EPS and the surprise, timestamped at the report date.
dividendCash-dividend declarations, timestamped at the ex-dividend date.
splitStock splits and reverse splits, timestamped at the execution date.
insiderOfficer and director trades — one event per reported Form 4 transaction.
analystUpgrades, downgrades, initiations, and price-target changes. Lands within the hour, 24/7.
newsHeadlines, one event per article × tagged ticker. Opt-in — joins the stream only when named.
signalBoolean flips as enter/exit point-events. Opt-in — joins the stream only when named.
string

Single-ticker filter. When both ticker and tickers are passed, ticker wins.

string[]

Comma list of tickers (max 50). Mutually exclusive with universe.

string

Universe slug (top_10, top_100, or one of yours) to scope the stream. Mutually exclusive with tickers.

string

Analyst-only structured filter — requires kind=analyst alone (400 otherwise). Exact firm-name match on the ratings feed.

enum

Analyst-only structured filter — requires kind=analyst alone. Same action vocabulary as Analyst actions.

upgradesRating raised.
downgradesRating lowered.
initiates_coverage_onFirst rating from this firm.
maintainsRating unchanged, restated.
reiteratesRating unchanged, emphasized.
assumesCoverage transferred to a new analyst at the firm.
reinstatesCoverage resumed after a pause.
suspendsRating suspended — coverage paused without being dropped.
terminates_coverage_onCoverage dropped entirely.
string

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

enum

Signal-only filter — requires kind=signal alone. enter (false→true) or exit (true→false).

string

Events 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.)

string

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

string

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

enum

Set to state to allow ticker-state signals in q/select/group_by/having, evaluated as of each event's timestamp (daily resolution).

stateWiden the grammar to ticker-state columns, evaluated as of each event’s timestamp. Resolves at daily grain.
enumdefault auto

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

1mOne minute. The finest stored tier; carries the intraday column subset and ~1,100 of ~13,700 tickers.
1hOne hour. Stored tier, intraday column subset, full universe.
1dOne day. The full-history, full-column tier — the only grain that carries daily-only columns (SMAs, RSI, fundamentals).
autodefaultLet 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.

string

Post-aggregation filter. Requires group_by.

string

Aggregate-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 desc

Aggregate-mode sort direction.

ascAscending — smallest or earliest first.
descdefaultDescending — largest or most recent first.
integerdefault 50

Page size (row modes) / max rollup rows (aggregate mode). Max 1000.

string

Opaque cursor from the previous response — carries the original filters (and q when short), so pass it alone. Not valid with group_by.

as_ofstring

Server time this response was assembled (ISO 8601).

queryobject

Your 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`.

countnumber

Rows in this page.

next_cursorstring

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

truncatedboolean

Aggregate mode only (`group_by`): `true` when the rollup stopped at its row cap. Aggregate responses are unpaged, so `next_cursor` is absent there.

resultsarray

One row per event (`ticker`, `ts`, `kind`, `payload`), or rollup rows plus `truncated: true` when an aggregate exceeds `limit`.

_metaobject

With `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`).

200
Success — the response shape is documented under Returns above.
400
Bad kind, >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.
404
Referenced universe does not exist.
503
kind_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.
  • Rows are { ticker, ts, kind, payload }, newest first. Payload fields per kind match the legacy /v2/tickers/{ticker}/events and /v2/analyst/events spellings, 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 in kind: signal — boolean firings as enter/exit point-events — and news, mirroring /v2/news.
  • The endpoint speaks the SQL grammar over a normalized projection of exactly four columns — ticker, ts, kind, payload (jsonb). q filters rows (kind='analyst' AND payload->>'action'='downgrades'); group_by with optional select/having switches 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; the query echo carries join: "state" with the deprecated state_resolution: "1d", and _meta.state_interval is the canonical grain field. Works in row and aggregate mode.
  • Requires at least one bound — a ticker scope or a time window; q alone does not count, and a bare kind over millions of rows is refused.
  • kind=signal needs the signal named before it will take the grammar: q and join=state over the ~175M-row firing log require signal=<name>, which is what lets the query ride an index instead of scanning (transition=enter|exit is optional). group_by over that kind is unavailable — an aggregate has no limit to stop at, so it reads every firing in the window. Filter mode needs none of this.
  • Aggregates do not paginate: past limit you get truncated: 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. A q too long to ride inside the cursor token sets q_truncated: true — resend q alongside cursor on later pages.
  • Both GET and POST are supported; POST takes the same parameters in the body for long queries.
Event volume by kind (aggregate mode)
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"
Response
{
  "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 }
  ]
}
Downgrade volume by firm (rolling up a payload field)
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"
Response
{
  "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 }
  ]
}
Same rollup, un-named key — the name is derived for you
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"
Response
{
  "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 }
  ]
}
join=state: downgrades on stocks above their 200-day (point-in-time)
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"
Response
{
  "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 } },
    "(...)"
  ]
}