AO.news

Developers · Firehose API

The Firehose API

Everything the deck shows, as JSON: translated push notifications from news apps and headline robots worldwide, with cluster consensus, keyword filters and a built-in polling cursor. One GET endpoint.

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.

1endpointGET /firehose/
24 hwindowlive stream, not an archive
15/srate limit300 per minute

Three things to know before you start:

  • Authentication is a tracker. Your API key is a tracker string 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

ParameterDefaultDescription
trackernoneYour API key / tracker identifier. Required.
since_utcnoneReturn items translated after this UTC timestamp (ISO 8601). Must be within the last 24 hours. Normally you take it from next.
from_minutes_ago1440Only items whose push time is newer than N minutes ago. Capped at 1440.
to_minutes_agononeWith from_minutes_ago: only items older than or equal to N minutes ago. Must be < 1440.
qnoneBase64 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.
countriesnoneComma-separated ISO 3166-1 alpha-2 codes, uppercase: SE,NO,DK.
score8Minimum breaking-news score. Robot headlines are model-scored 1 to 10; app notifications carry 8. Keep 8 unless you want more robot items.
consensus1Minimum number of distinct sources on the item's story cluster. 1 disables. 3 is a good "corroborated news" setting.
consensus_ltnoneUpper bound on cluster sources (requires consensus > 1). Useful to hide runaway mega-clusters.
only_latest_in_clusterfalseWith consensus > 1: return one item per story. That item is its latest push.
cluster0All items of one story cluster (ignored when consensus > 1).
push_identifiernoneOne specific notification.
sourcesapps,robotapps = news-app notifications; robot = front-page/headline robots. Aliases: android-apps, app, scraper, robots.
search_filternoneWord filter over the translated title/text/subtext. See below.
keysnoneComma-separated allow-list of fields to return per item.
cleanedtrueInclude cleaned_content / cleaned_content_translation. Set false for a slightly faster, smaller payload.
skip, limit0, see aboveOffset 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

FieldTypeDescription
identifierstringStable id of the notification. Permalink: https://ao.news/push/{identifier}.
timestamp_utcISO datetimeWhen the notification was sent (or the article's publish time for robot items, if earlier).
app_name, package_idstringDisplay name and package/source id of the outlet's app or robot.
country, country_codestringCountry name and ISO code of the source (empty / null when unknown).
package_urlstringThe outlet's website, when known.
title, text, sub_textstringDisplay versions. English when a translation exists. text may contain simple HTML separators.
title_orig, text_orig, sub_text_origstringThe original-language notification, untouched.
title_translation, text_translationstringThe stored English translation.
cleaned_content, cleaned_content_translationstringPlain-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.
scoreintBreaking-news score from 1 to 10 (model-scored for robots; 8 for app notifications).
source"apps" | "robot"Kind of source.
urlstring | nullDirect article URL when AO.news has resolved one.
redirect_urlstringAO.news resolver that redirects to the article. Always present.
cleaned_urlstringurl 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.

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_utc older than 24 h and to_minutes_ago ≥ 1440 return an error body; from_minutes_ago > 1440 is capped.
  • Error bodies are JSON with an error key. 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_url is 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.

Access

Get an API key.

Firehose access is part of newsroom plans. Tell us what you're building and we'll set up a tracker, usually the same day.