Endpoints · News

Scan articles

GET/v2/news/scan

One endpoint, two shapes. With no `group_by` you get article rows; with `group_by` you get rollups (counts, averages) grouped by your expressions. Auto-joins `UNNEST(tickers) AS tk` whenever any clause references the `tk` alias — that's how you write per-ticker rollups without unnesting yourself. There is no separate "asof" parameter. Historical queries are just WHERE filters on `time_published`: ``` q=time_published >= '2026-01-15' AND time_published < '2026-01-16' q=time_published >= NOW() - INTERVAL '7 days' ``` The grammar is the same SQL whitelist used by `/v2/scan`: comparison and logical operators, casts (`::numeric`, `::date`, …), array operators (`= ANY(tickers)`, `tickers && ARRAY[…]`), JSONB operators (`->`, `->>`, `@>`), and the scalar/aggregate functions listed below. Subqueries and DDL keywords are rejected. **Columns available** on `news_article` (and as `select` / `group_by` items): `id`, `url`, `time_published`, `title`, `summary`, `source`, `source_domain`, `category`, `authors`, `topics`, `banner_image`, `overall_sentiment_score`, `overall_sentiment_label`, `tickers` (text[]), `ticker_data` (jsonb), `created_at`. Plus the synthetic `tk` alias — when referenced, the server auto-joins `UNNEST(tickers) AS tk`. **Aggregates**: `COUNT`, `AVG`, `SUM`, `MIN`, `MAX`, `STRING_AGG`. **Scalar funcs**: `COALESCE`, `NULLIF`, `ROUND`, `GREATEST`, `LEAST`, `TANH`, `ABS`, `DATE_TRUNC`, `EXTRACT`, `NOW`, `LOWER`, `UPPER`, `LENGTH`, `JSONB_ARRAY_LENGTH`. Pagination is opaque cursor — pass `next_cursor` back as `?cursor=…`. For aggregate queries, alias every `group_by` expression so the cursor can resolve the next page (e.g. `group_by=time_published::date AS day`).

Plan access

Pro and above. Hobby + Free return 403 news_tier_required for the archive — the live `news_volume` and `news_volume_weighted_sentiment` columns on the ticker object remain available on every paid plan.

Rate limit

Hobby 60/min · Pro 2,000/min · Scale 10,000/min.

Per-call cap

Pro: 200 rows. Scale/Enterprise: 1000. History back to 2015.

stringrequired

WHERE clause. Required. 4000-char max.

string

Comma-separated SELECT items. Default for article queries: `id, time_published, title, source, tickers, overall_sentiment_score, overall_sentiment_label`. Default for aggregate queries: the group_by keys + `COUNT(*) AS volume`.

string

Comma-separated grouping expressions. Switches the response shape to aggregate rows. Alias each expression for stable cursor pagination.

string

HAVING clause, applied post-aggregation. Requires `group_by`.

string

Column or alias to sort by. Defaults to `time_published` (article rows) or `volume` (aggregate rows).

string

`asc` or `desc`. Default `desc`.

integer

Rows per page. Per-plan max: Pro 200, Scale/Enterprise 1000. Default 50.

string

Opaque pagination cursor from a previous response.

200
OK — results array of article rows or aggregate rows depending on group_by. Always carries as_of, count, next_cursor, and an echo of the parsed query.
400
bad_request / invalid_query — missing q, malformed parameter, disallowed identifier, or SQL parse error from Postgres.
403
news_tier_required — archive access requires Pro or above.