tickerbot-io News API
Article-level news with sentiment, queryable through the same SQL grammar as /scan.
Article-level news with sentiment, queryable through the same SQL grammar as /scan.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/tickerbot-io:tickerbot-io-news-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Tickerbot News API
version: 2.69.6
summary: The stock market API designed for agents.
description: 'Tickerbot is the stock market API designed for agents. We serve real-time and historical market data as one queryable table. You and your agents can ask it anything, without building anything custom. Every ticker is a row. Every data point is a column. 23,000+ tickers, covering US stocks and ETFs, FX, crypto, rates and metals. 452+ columns, covering raw data, calculations and conditions: price, volume, price action, technicals, fundamentals, events, profile, analyst ratings and news. Refreshed every minute, historized back to 2003. The table is the API: /v2/tickers/ returns one ticker''s whole row, /v2/signals/ returns every ticker that matches one column, /v2/scan returns every ticker that matches a SQL WHERE clause. Every read runs live or as of any date. The power is in the columns: every ticker gets the same columns, query any of them, in any combination. Ask any question: anything you want to know about the market is a query over the table. Build any trading tool: anything you want to build is a few queries in sequence. Get a free API key, or paste our setup prompt and your agent connects over MCP. Free plan: 10,000 calls a month, no card required.
All responses carry a top-level `as_of` ISO timestamp indicating data vintage. List and history responses use opaque cursor pagination — treat `next_cursor` as a black-box token and pass it back via `?cursor=` on the next request.'
contact:
name: Tickerbot Support
url: https://tickerbot.io/support
email: support@tickerbot.io
license:
name: Proprietary
url: https://tickerbot.io/terms
servers:
- url: https://api.tickerbot.io
description: Production
security:
- apiBearer: []
tags:
- name: News
description: Article-level news with sentiment, queryable through the same SQL grammar as /scan.
paths:
/v2/news:
get:
tags:
- News
summary: SQL query over the news_article archive. Returns articles or aggregate rollups
operationId: search_news
description: 'SQL query over the news archive. Article rows, or rollups when you group them.
Notes:
- Without `group_by`, each result is one article. By default it has 7 columns (`id`, `time_published`, `title`, `source`, `tickers`, `overall_sentiment_score`, `overall_sentiment_label`); name any of the 16 columns listed under `q` in `select` to get them. With `group_by` set, results become aggregate rollups and the default SELECT becomes `, COUNT(*) AS volume`. Use the `tk` alias — an auto-UNNEST of `tickers` — to roll up per ticker without writing the join.
- Same scoping sugar as `/v2/events`: `ticker`/`tickers`/`universe` and `from`/`to` AND into the WHERE clause, and `q` is optional once any of them is present — `?ticker=NVDA&from=2026-07-01` needs no SQL at all.
- There is no `asof` parameter here: "as of" is a WHERE filter on `time_published`, because every article already carries the moment it was published.
- Both `GET` and `POST` are supported; the `POST` body takes the same parameter names, for queries too long to fit in a URL.
- `/v2/news` is the canonical path — the log-shaped endpoints read uniformly (`/v2/scan`, `/v2/news`, `/v2/events`) — and the original spelling `/v2/news/scan` answers identically. The anonymous sandbox mirrors both spellings, GET and POST, with the same limits.'
parameters:
- name: search
in: query
required: false
description: 'Full-text search over `title` + `summary` — websearch grammar: `apple earnings` (all words), `"price target"` (phrase), `chips OR semiconductors`, `-crypto` (negation). Max 200 chars. ANDs with `q` and the scoping params. Language-stemmed English.'
schema:
type: string
example: apple earnings
- name: q
in: query
required: false
description: 'WHERE clause over the news_article table. Max 4000 chars. Required UNLESS `search` or a scoping param (`ticker`/`tickers`/`universe`/`from`/`to`) is present — the simplest call needs no SQL. Queryable columns: `time_published`, `title`, `summary`, `source`, `source_domain`, `category`, `authors`, `topics`, `overall_sentiment_score`, `overall_sentiment_label`, `tickers`, `ticker_data`, `banner_image`, `url`, `id`, `created_at` — plus `tk`, the per-ticker UNNEST alias. Signal/state columns are not joinable here.'
schema:
type: string
example: overall_sentiment_score < -0.2
- name: ticker
in: query
required: false
description: Articles mentioning this symbol (ANDed with `q`).
schema:
type: string
example: NVDA
- name: tickers
in: query
required: false
description: Comma list, up to 50 — articles mentioning ANY of them. Not combinable with `ticker` or `universe`.
schema:
type: array
example: NVDA,AMD
items:
type: string
- name: universe
in: query
required: false
description: Universe slug — articles mentioning any member. Not combinable with `ticker`/`tickers`.
schema:
type: string
example: top_100
- name: min_relevance
in: query
required: false
description: 'With `ticker`/`tickers`/`universe`: keeps an article only when it is about one of those tickers, meaning that ticker''s relevance score (`ticker_data.<TICKER>.rel`, 0 to 1) is at least this value. `0.8` drops passing mentions. Omitted: every article that mentions them. `400` without a ticker scope; inside `q`, filter `(ticker_data->''AAPL''->>''rel'')::numeric >= 0.8` directly.'
schema:
type: number
example: 0.8
- name: from
in: query
required: false
description: 'Earliest `time_published` (inclusive) — strict ISO: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM[:SS]Z`.'
schema:
type: string
example: '2026-09-21'
- name: to
in: query
required: false
description: Articles strictly before this instant — same strict ISO subset, matching `/v2/events`.
schema:
type: string
example: '2026-10-19'
- name: group_by
in: query
required: false
description: 'AGGREGATE MODE: comma-separated group keys, 1-6 (max 1000 chars). Switches the response to rollup rows. Use `tk` to roll up per ticker without writing the UNNEST. Name a key with `AS` to choose its JSON key; an un-named expression is named for you rather than returned as `?column?`.'
schema:
type: array
example: tk
items:
type: string
- name: select
in: query
required: false
description: Columns/expressions to return (max 2000 chars). Defaults to article columns (no `group_by`) or `<group_by cols>, COUNT(*) AS volume` (with `group_by`).
schema:
type: array
example: tk, COUNT(*) AS volume
items:
type: string
- name: having
in: query
required: false
description: HAVING clause on the aggregate (max 1000 chars). Requires `group_by`.
schema:
type: string
example: COUNT(*) >= 10
- name: order
in: query
required: false
description: Sort — a bare column name or SELECT alias only (put expressions in `select` and order by their alias). Defaults to `time_published` (article rows) or `volume` (aggregate rows; with your own `select` that has no `volume` column, its last column's alias).
schema:
type: string
example: time_published
- name: dir
in: query
required: false
description: Sort direction.
schema:
type: string
enum:
- asc
- desc
default: desc
- name: limit
in: query
required: false
description: Page size. Max 1000.
schema:
type: integer
default: 50
example: 5
- name: cursor
in: query
required: false
description: Opaque pagination cursor from a prior response's `next_cursor`.
schema:
type: string
responses:
'200':
description: Success.
content:
application/json:
schema:
type: object
properties:
as_of:
type:
- string
- 'null'
description: Server time this response was assembled (ISO 8601).
query:
type:
- object
- 'null'
description: Your filters, echoed.
count:
type:
- number
- 'null'
description: Rows in this page.
next_cursor:
type:
- string
- 'null'
description: Opaque token for the next page; `null` on the last page, and when a custom `select` leaves out the `order` column (keep it in `select` to page). Pass it back as `cursor`.
truncated:
description: 'Aggregate mode only (`group_by`): `true` when the rollup stopped at its row cap.'
results:
type:
- array
- 'null'
description: 'Article rows, or rollup rows when you passed `group_by`. Aggregate responses add `truncated: true` when `limit` cut the group list.'
'400':
description: '`bad_request` or `invalid_query` — missing/malformed `q`, unknown column, or invalid parameter.'
'401':
description: Missing or invalid API key.
'402':
description: No active plan on the account (rare — new signups land on the Free plan automatically).
'429':
description: Rate limit exceeded for your plan.
components:
securitySchemes:
apiBearer:
type: http
scheme: bearer
bearerFormat: API key (tb_live_… or tb_test_…)
description: 'API keys are minted in the dashboard. Send as `Authorization: Bearer <key>`.'