Overview
The Firehose is the deck without the deck. AO.news captures push notifications from hundreds of news apps worldwide, plus headlines from front-page robots. All of it is translated to English, clustered by story, and served as JSON from a single endpoint. You get the same filters a deck column has (sources, countries, keywords, minimum score, cross-source consensus) plus a polling cursor, so a few lines of code can keep a CMS, a wall screen, a bot or a model in step with the world's news alerts.
Three things to know before you start:
- Authentication is a tracker. Your API key is a
trackerstring passed as a query parameter. It is the same identifier that owns your decks; the Firehose is enabled on newsroom (enterprise) trackers. - It is a live window, not an archive. The Firehose serves the last 24 hours. The archive goes back to 2021, but getting at it is a separate arrangement. Ask us.
- Only translated items are returned. A notification appears in the Firehose once its English translation exists, which is normally seconds after capture. English-language sources are passed straight through.
Quick start
# Latest translated notifications from news apps (not robots), score ≥ 8
curl 'https://ao.news/firehose/?tracker=YOUR_TRACKER&sources=apps&score=8&limit=2&keys=timestamp_utc,app_name,country_code,identifier,title,text_translation,cleaned_url'
{
"next": "https://ao.news/firehose/?tracker=YOUR_TRACKER&score=8&sources=apps&cleaned=true&since_utc=2026-08-18T20%3A54%3A17.412922&…",
"data": [
{
"timestamp_utc": "2026-08-18T20:54:13",
"app_name": "Breaking News",
"country_code": "US",
"identifier": "796c99d368d8eae8174adddc3fe9cf5",
"title": "",
"text_translation": "Melania Trump has kept her distance from husband Donald because of aide Natalie Harp's presence: report",
"cleaned_url": "https://ao.news/push/796c99d368d8eae8174adddc3fe9cf5/url"
},
{
"timestamp_utc": "2026-08-18T20:54:12",
"app_name": "NZ Herald",
"country_code": "NZ",
"identifier": "7e66bd9edb4ea0af962e46dbd6475c",
"title": "'It's scandalous'",
"text_translation": "'It's scandalous': Documents reveal minister was aware of Health NZ hiring crackdown",
"cleaned_url": "https://ao.news/push/7e66bd9edb4ea0af962e46dbd6475c/url"
}
]
}
A real response, trimmed with keys. Without keys you get the full field set listed below.
Polling with next
Every response includes a next URL. It repeats your filters and adds since_utc set to the server time when the response was generated. Call next, get everything translated since then, call its next, and so on. That is the whole protocol. No websockets, no subscriptions to manage. It is idempotent too, so a crashed client just resumes from its last next.
# Python: poll every 5 seconds forever
import requests, time
url = "https://ao.news/firehose/?tracker=YOUR_TRACKER&consensus=3&sources=apps"
while True:
r = requests.get(url, timeout=10).json()
for item in reversed(r["data"]): # oldest first
handle(item)
url = r["next"]
time.sleep(5)
Two details: the initial call (no since_utc) returns the standard feed limit of 12 items unless you pass limit; polling calls with since_utc are unlimited unless you pass limit. And since_utc filters on translation time, which is what you want for "what's new since I last asked". It cannot be older than 24 hours.
Query parameters
| Parameter | Default | Description |
|---|---|---|
tracker | none | Your API key / tracker identifier. Required. |
since_utc | none | Return items translated after this UTC timestamp (ISO 8601). Must be within the last 24 hours. Normally you take it from next. |
from_minutes_ago | 1440 | Only items whose push time is newer than N minutes ago. Capped at 1440. |
to_minutes_ago | none | With from_minutes_ago: only items older than or equal to N minutes ago. Must be < 1440. |
q | none | Base64 of a comma-separated package_id allow-list. The deck's source picker uses the same encoding. Easiest way to get one: build the column in the deck and read it off the deck links endpoint. |
countries | none | Comma-separated ISO 3166-1 alpha-2 codes, uppercase: SE,NO,DK. |
score | 8 | Minimum breaking-news score. Robot headlines are model-scored 1 to 10; app notifications carry 8. Keep 8 unless you want more robot items. |
consensus | 1 | Minimum number of distinct sources on the item's story cluster. 1 disables. 3 is a good "corroborated news" setting. |
consensus_lt | none | Upper bound on cluster sources (requires consensus > 1). Useful to hide runaway mega-clusters. |
only_latest_in_cluster | false | With consensus > 1: return one item per story. That item is its latest push. |
cluster | 0 | All items of one story cluster (ignored when consensus > 1). |
push_identifier | none | One specific notification. |
sources | apps,robot | apps = news-app notifications; robot = front-page/headline robots. Aliases: android-apps, app, scraper, robots. |
search_filter | none | Word filter over the translated title/text/subtext. See below. |
keys | none | Comma-separated allow-list of fields to return per item. |
cleaned | true | Include cleaned_content / cleaned_content_translation. Set false for a slightly faster, smaller payload. |
skip, limit | 0, see above | Offset and page size. |
Filters
Sources and countries
curl 'https://ao.news/firehose/?tracker=T&countries=SE&sources=apps' # Swedish news apps only
curl 'https://ao.news/firehose/?tracker=T&sources=robot&score=9' # front-page headlines the model rates 9 to 10
curl 'https://ao.news/firehose/?tracker=T&q=Y29tLmV4YW1wbGUubmV3cw%3D%3D' # one specific package (base64 of "com.example.news")
Word filters
search_filter is applied to the English translation, so one query covers every language in the stream. Operators: AND (also implied by a space), OR, NOT / ! / -word, parentheses and quoted phrases. Word filters look back at most 24 hours.
curl 'https://ao.news/firehose/?tracker=T&search_filter=ukraine%20OR%20iran'
curl 'https://ao.news/firehose/?tracker=T&search_filter=(corona%20OR%20covid)%20AND%20!cases'
curl 'https://ao.news/firehose/?tracker=T&search_filter=%22climate%20change%22%20OR%20flooding'
Consensus and clusters
AO.news clusters notifications about the same story in real time. consensus is the number of distinct sources on that cluster and is the single most useful filter for a machine consumer: it turns "someone pushed something" into "several newsrooms agree this is a story".
# One item per story, only stories 3+ sources have pushed. A clean breaking-news feed
curl 'https://ao.news/firehose/?tracker=T&consensus=3&only_latest_in_cluster=true'
# Mid-size stories only (3 to 9 sources), all their pushes
curl 'https://ao.news/firehose/?tracker=T&consensus=3&consensus_lt=10'
# Everything on one story
curl 'https://ao.news/firehose/?tracker=T&cluster=12345'
Time
curl 'https://ao.news/firehose/?tracker=T&from_minutes_ago=60' # last hour
curl 'https://ao.news/firehose/?tracker=T&from_minutes_ago=1440&to_minutes_ago=60' # 24h ago … 1h ago
Response fields
| Field | Type | Description |
|---|---|---|
identifier | string | Stable id of the notification. Permalink: https://ao.news/push/{identifier}. |
timestamp_utc | ISO datetime | When the notification was sent (or the article's publish time for robot items, if earlier). |
app_name, package_id | string | Display name and package/source id of the outlet's app or robot. |
country, country_code | string | Country name and ISO code of the source (empty / null when unknown). |
package_url | string | The outlet's website, when known. |
title, text, sub_text | string | Display versions. English when a translation exists. text may contain simple HTML separators. |
title_orig, text_orig, sub_text_orig | string | The original-language notification, untouched. |
title_translation, text_translation | string | The stored English translation. |
cleaned_content, cleaned_content_translation | string | Plain-text one-liner (title + text + subtext) with HTML and common prefixes such as "Breaking:", "Just nu:", "TV:" removed. The field to feed a model or a headline ticker. |
score | int | Breaking-news score from 1 to 10 (model-scored for robots; 8 for app notifications). |
source | "apps" | "robot" | Kind of source. |
url | string | null | Direct article URL when AO.news has resolved one. |
redirect_url | string | AO.news resolver that redirects to the article. Always present. |
cleaned_url | string | url if known, else redirect_url. Use this one. |
Use keys to request a subset; if keys includes a cleaned field it is computed even when cleaned=false.
Deck links endpoint
The fastest way to build a Firehose query is not to build it: set up the column in the deck (sources, keywords, consensus) and ask AO.news for the equivalent URL. GET /{tracker}/d/{deck}/firehose returns one ready-made Firehose URL per column, keyed by column name.
curl 'https://ao.news/YOUR_TRACKER/d/your-deck/firehose'
{
"World 3+": "https://ao.news/firehose/?tracker=YOUR_TRACKER&consensus=3&consensus_lt=28&only_latest_in_cluster=true",
"Ukraine": "https://ao.news/firehose/?tracker=YOUR_TRACKER&q=Y29tLmV4YW1wbGUubmV3cw%3D%3D&score=8&search_filter=ukraine"
}
Recipes
Newsroom wall screen
Poll consensus=3&only_latest_in_cluster=true&keys=timestamp_utc,app_name,country_code,cleaned_content_translation,cleaned_url every 5 s and render the last 20. That is a live "what the world's newsrooms are pushing" board in an afternoon.
Teams / Discord / e-mail
Slack is built in (Slack integration). For anything else, one polling loop and your webhook of choice: post cleaned_content_translation with cleaned_url.
Feed a language model
cleaned_content_translation is a clean, English, one-line summary of every notification. Batch the last hour with from_minutes_ago=60 and ask your model "what happened, and what is my desk missing?"
Competitor timing
Filter q to your competitors' packages and log timestamp_utc against your own push log. You now have a first-mover scoreboard. It is the same analysis we ran in our year-of-pushes report.
Errors, limits, fair use
- Rate limits: 15 requests/second and 300 requests/minute per client IP. Exceeding them returns HTTP 429. Polling every 3 to 5 seconds per stream stays well inside.
- 24-hour window:
since_utcolder than 24 h andto_minutes_ago ≥ 1440return an error body;from_minutes_ago > 1440is capped. - Error bodies are JSON with an
errorkey. An expired or non-API tracker gets you{"error": "tracker is expired"}or{"error": "tracker is not on enterprise plan"}. Check for the key; do not rely on the HTTP status alone. - Server-to-server. The endpoint is meant to be called from your backend, not from a browser page; keep your tracker out of client-side code.
- Fair use. The stream is for your newsroom's own monitoring and products. Notification text belongs to the publishers who wrote it; link to the article (that is what
cleaned_urlis for) rather than republishing feeds wholesale. Redistribution and archive access are by agreement.
Getting an API key
Firehose access is included in newsroom plans and enabled on your tracker on request. Write to hello@ao.news with a sentence on what you are building; a tracker is usually set up the same day. If you already use a deck, your existing tracker becomes your key.