AI Squawk the real-time terminal for AI news

↗

JSON API

Every item on the feed is available as JSON, built to be polled by a script rather than read by a human. No API key, no account. Responses carry an ETag; send back If-None-Match and a poll that found nothing new costs a 304 with no body.

parammeaning
modenew (default: the / tape in its order, 50 rows unless limit), top (the top 24h tab's rows, newest first) or all
sincean item id or an ISO 8601 timestamp — only items newer than that
min_scoreonly items scoring at least this
limitcap the number of rows returned (max 500)
qcase-insensitive substring match on title or summary
watchcomma-separated provider slugs (up to 8) — matches a row from that provider's own channel, or naming one of its models, e.g. watch=anthropic,openai
idlook up one item by id, any age, ignores every other param
explainexplain=1 adds a explain score breakdown (tier/category weight, diff and burst adjustments, velocity, decay) to every item — the same data /i/<id> renders

An unrecognized parameter returns 400 with a JSON error body instead of being silently ignored.

Examples

curl 'https://aisquawk.io/api/items.json'
curl 'https://aisquawk.io/api/items.json?mode=all&limit=20'
curl 'https://aisquawk.io/api/items.json?min_score=70'
curl 'https://aisquawk.io/api/items.json?q=anthropic'
curl 'https://aisquawk.io/api/items.json?watch=anthropic,openai'
curl 'https://aisquawk.io/api/items.json?since=2026-09-16T00:00:00Z'
curl 'https://aisquawk.io/api/items.json?id=1'
curl 'https://aisquawk.io/api/items.json?id=1&explain=1'

Polling for new items every minute: keep the highest id you've seen and pass it back as since — every response is only rows you don't already have, so there's nothing to dedup:

SINCE=0
while true; do
  BATCH=$(curl -s "https://aisquawk.io/api/items.json?mode=all&since=$SINCE&limit=200")
  echo "$BATCH" | jq -c '.[]'
  SINCE=$(echo "$BATCH" | jq '([.[].id] + ['"$SINCE"']) | max')
  sleep 60
done

Each item: id, title, url (the original source), permalink (this site's own stable link), source (feed name), publisher (registrable domain), tier, category, summary, score, scoreAtPublish, publishedAt (ISO 8601), dateOnly, sourceDate, alsoSources, relevant, models (up to 2 model slugs this item names), provider (a provider slug), changes — an array, empty except on a value-diff PRICING row, of {provider, model, field, old, new, pct, unit} so a script can react to one price moving without parsing the prose summary — incident, null except on an OUTAGE row: {state: "open"|"resolved", openedAt, resolvedAt, durationMin}, resolvedAt/durationMin null until it resolves — and deadline, null except on a shutdown-reminder DEPRECATION row or a model-list “removed” row whose id sits in the provider's deprecated table: {provider, model, deprecatedAt, shutdownAt, replacement, daysLeft, sourceUrl}, daysLeft recomputed on every request so it never goes stale — and breaking, null except on a breaking library release (DEPRECATION from GitHub releases): up to 3 short strings, verbatim from the notes' own breaking section, of what was removed or changed.

Every response also carries Access-Control-Allow-Origin: * so it's fetchable straight from a browser page on another domain, and Cache-Control: max-age=20.

Also see RSS (per-category with ?category=, or add ?mode=top/?min_score= — same vocabulary as above, silently ignored if invalid rather than a 400, since a feed reader can't fix its own URL) and push webhooks for the same feed delivered to you instead of polled. All /api/* routes share one per-IP limit: 60 requests/minute, 429 with Retry-After past that — a poller checking every 20–60s never gets close.

/tail — the feed as a stream

No polling, no client library: curl -N against /tail prints the last 20 rows, then every new row as it lands, one line each. A reload ends the stream, so leave it in a loop if you want it to survive a restart:

while true; do curl -N 'https://aisquawk.io/tail'; sleep 1; done

Filters match /api/items.json's vocabulary: min_score, category, provider (a provider slug), and q. Add ?format=ndjson for one JSON object per line instead of the plain HH:MMZ CATEGORY score title — source url text format — ndjson lines also carry the same changes/incident fields /api/items.json does. An OUTAGE row that resolves after you've already seen it open prints a second line (HH:MMZ OUTAGE RESOLVED after Nm ... in text, {"event": "resolved", ...} in ndjson) rather than staying silent until you re-poll:

curl -N 'https://aisquawk.io/tail?category=OUTAGE'
curl -N 'https://aisquawk.io/tail?format=ndjson&min_score=70' | jq -c .

Push webhooks

No accounts: the URL you register is the subscription. POST /api/webhooks with a JSON body registers one, DELETE /api/webhooks with the same {"url": ...} body unsubscribes it. We POST new items that clear the top-mode score bar (and every filter you set) as they publish, capped at 10 items/delivery, 10s timeout. An open OUTAGE you were sent fires one more time when it resolves — same filters, payload marked "event": "resolved" (a ✅ Resolved prefix on discord/slack) — so you're not stuck polling to find out an incident ended. A webhook failing 5 cycles in a row, or failing for 24h straight, is paused — check /stats for the delivery log.

fieldmeaning
urlrequired; https/http only, no localhost or private-network hosts
formatrequired; generic, discord, or slack
categoriesoptional array, e.g. ["PRICING", "OUTAGE"] — omit for every category
providersoptional array of slugs from /providers, e.g. ["anthropic", "openai"]
sourcesoptional array of exact source names, e.g. ["Anthropic Blog"]
min_scoreoptional integer 0-100 — raises the delivery bar above the default 50
modelsoptional array of model ids, same grammar as /check (explicit, suffix-stripped, never fuzzy) — delivers only rows concerning those ids: a price change, retirement, delisting, or an incident on one of their providers

Every given filter must pass (an AND, not an OR) — "Anthropic pricing moves or an OpenAI incident opens" is two webhooks (providers: ["anthropic"], categories: ["PRICING"] and providers: ["openai"], categories: ["OUTAGE"]), not one.

Register, per format

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint", "format": "generic"}'

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://discord.com/api/webhooks/...", "format": "discord", "categories": ["RELEASE"]}'

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://hooks.slack.com/services/...", "format": "slack", "providers": ["anthropic"], "categories": ["PRICING"]}'

Filter examples

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint", "format": "generic", "min_score": 80}'

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint", "format": "generic", "sources": ["OpenAI Blog"]}'

curl -X POST 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint", "format": "generic", "models": ["gpt-4o-mini (2024-07-18)"]}'

Test a registration

POST /api/webhooks/test sends the most recent item that matches an already-registered webhook's own filters, so you see a real delivery without waiting for one to publish:

curl -X POST 'https://aisquawk.io/api/webhooks/test' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint"}'

Unsubscribe

curl -X DELETE 'https://aisquawk.io/api/webhooks' -H 'Content-Type: application/json' \
  -d '{"url": "https://your-endpoint"}'

Payload shapes: generic sends {"items": [...]} matching the JSON API row shape, changes array included; discord sends native embeds (title/url/ description/color-by-category/footer/timestamp), a value-diff row appends one line per change to the description; slack sends Block Kit (blocks) for a Slack incoming webhook — linked title, one-line summary (a value-diff row appends the same per-change lines), and a context line with a score chip, category, and source.

MCP server

An MCP (Model Context Protocol) server at https://aisquawk.io/mcp so an agent living in Claude Code, Cursor, or your own tooling can query the feed, pricing, outages, deprecations, models and calendar directly — no API key, stateless JSON-RPC over plain HTTP (no SSE, no session id). Tools: latest_items, search_items, pricing, pricing_changes, deprecations, outages, models, calendar.

Claude Code

claude mcp add --transport http aisquawk https://aisquawk.io/mcp

Cursor / Claude Desktop mcp.json

{
  "mcpServers": {
    "aisquawk": { "url": "https://aisquawk.io/mcp" }
  }
}

Raw curl

curl -X POST 'https://aisquawk.io/mcp' -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"outages","arguments":{}}}'