API
API reference
The coverage-metadata endpoints — authentication, rate limits, every parameter and field, with real examples. The open story feeds need no key and are listed at /api.json.
Two layers
The open layer — the story feeds, country editions, RSS and the MCP server — is free, unmetered and needs no key. This page documents the metered layer: the propagation record, which is what a key unlocks. Three endpoints, all authenticated, all JSON, all CORS-open.
| Endpoint | Returns | Plan |
|---|---|---|
GET /api/propagation.json | What is spreading fastest now, ranked by how many outlets carry it | any key |
GET /api/coverage/{id}.json | The full propagation record for one story | any key |
GET /api/bulk/coverage.json | Coverage records for up to 500 stories in one request | Desk |
Authentication
Send your key in the Authorization header on every request:
curl -H "Authorization: Bearer un_your_key_here" \
https://urgent.news/api/propagation.json?hours=24
An x-api-key: un_… header is accepted as an alternative. Never put the key in the URL — query strings land in server logs, browser history and Referer headers. Keys are issued instantly at /keys, never expire, and an account may hold up to 10. A missing or bad key does not fail the open endpoints — it is served at the anonymous tier; on the metered endpoints it returns 401.
Rate limits
Calls are counted per account, per UTC day — pooled across all your keys, not per key. Every response, success or error, reports where you stand:
| Response header | Meaning |
|---|---|
x-plan | The plan that answered — open, signal or desk |
x-ratelimit-limit | Your daily allowance |
x-ratelimit-remaining | Calls left today |
x-key-status | ok, or why a key resolved to a lower tier (e.g. unknown-key) |
Allowances: Open 1,000/day · Signal 25,000/day · Desk 250,000/day (see /pricing). Over the limit is a clean 429 with the reset time — never a silently truncated result. Open-endpoint requests are not counted. Responses are Cache-Control: private, no-store and vary by key: cache them locally per key if you cache at all, never in a shared cache.
GET /api/propagation.json
The stories moving fastest across outlets right now, ranked by distinct outlet count. This is the discovery endpoint — use it to find clusters, then follow each coverage link for the full record.
| Parameter | Default | Range | Notes |
|---|---|---|---|
hours | 24 | 1 – 168 | Look-back window. Capped by plan: Open 24h, Signal/Desk up to 168h. Ask for more than your plan allows and the response tells you it was capped. |
min_outlets | 3 | 2 – 50 | Only clusters carried by at least this many outlets. |
limit | 25 | 1 – 100 | Maximum clusters returned. |
Example — GET https://urgent.news/api/propagation.json?hours=24&min_outlets=4&limit=1
{
"self": "https://urgent.news/api/propagation.json",
"window_hours": 24,
"min_outlets": 4,
"generated": "2026-08-18T11:58:43.705Z",
"count": 1,
"stories": [
{
"cluster_id": 1615193,
"title": "At least 2 hit in a shooting at a school in the southern Philippines",
"url": "https://urgent.news/2026/08/18/at-least-2-hit-in-a-shooting-at-a-school",
"outlets": 10,
"countries": 9,
"first_seen": "2026-08-18T02:20:50.000Z",
"latest_seen": "2026-08-18T04:17:03.000Z",
"span_hours": 1.94,
"velocity_outlets_per_hour": 5.16,
"coverage": "https://urgent.news/api/coverage/1615193.json"
}
],
"plan": "signal"
}
When your requested hours exceeds your plan cap, the response adds window_capped_from, window_cap_reason and an upgrade pointer, and window_hours reflects what you actually got.
GET /api/coverage/{id}.json
The complete propagation record for one story, keyed by its cluster_id (or any story id in the cluster). Takes no query parameters.
Example — GET https://urgent.news/api/coverage/1615193.json
{
"story": { "id": 1615193, "title": "…", "url": "https://urgent.news/2026/08/18/…" },
"cluster_id": 1615193,
"outlets": 10,
"outlets_discovered": 0,
"countries": 9,
"first_seen": "2026-08-18T02:20:50.000Z",
"latest_seen": "2026-08-18T03:47:02.000Z",
"span_hours": 1.44,
"velocity_outlets_per_hour": 6.96,
"by_country": [ { "country": "IN", "name": "India", "outlets": 2 }, … ],
"by_tier": [ { "tier": "news", "outlets": 7 }, … ],
"timeline": [
{
"outlet": "Kyiv Independent",
"slug": "kyiv-independent",
"domain": "kyivindependent.com",
"country": "UA",
"tier": "news",
"first_seen": "2026-08-18T02:20:50.000Z",
"minutes_after_first": 0,
"discovered": false
}, …
],
"plan": "signal",
"disclaimer": "…"
}
An unknown or hidden story returns 404 {"error":"unknown story"}.
GET /api/bulk/coverage.json — Desk plan
The coverage record — timelines included — for many stories in a single request, so a caller ingesting a window of news makes one call instead of one per story. Requires the Desk plan; other keys receive 403.
| Parameter | Default | Range | Notes |
|---|---|---|---|
hours | 24 | 1 – 168 | Look-back window (Desk allows the full 168h). |
min_outlets | 2 | 2 – 50 | Only clusters with at least this many outlets. |
limit | 100 | 1 – 500 | Maximum stories returned. truncated: true in the response means more matched than were returned. |
Each entry in stories[] has the same shape as a single coverage response (outlets, countries, span, velocity, and a full timeline).
Field glossary
| Field | What it means |
|---|---|
outlets | Distinct outlets this pipeline polled that carried the story. This is the headline "N outlets" figure. |
outlets_discovered | Additional outlets seen in the discovered corpus (a wider crawl) rather than polled directly. Reported separately because it carries a different clock — do not add the two into one figure. |
countries | Distinct countries among the carrying outlets (ISO-3166 alpha-2). |
span_hours | Hours between the first and last outlet filing. |
velocity_outlets_per_hour | Outlets per hour across the span. null — never Infinity — when every outlet filed in the same instant. |
minutes_after_first | In the timeline: minutes between the first outlet and this one. The corroboration clock. |
discovered | true if this outlet came from the discovered corpus rather than a direct poll. |
tier | Outlet class: news, wire, or community. |
A note on honesty: the outlet count is not yet reprint-adjusted — a wire story and the outlets that reprint it verbatim currently each count as one outlet. Treat the count as carrying breadth, not independent corroboration. A distinct independent figure is planned.
Errors
| Status | When | Body |
|---|---|---|
401 | No key, or an unrecognised one, on a metered endpoint | error: "api key required" + a get_one pointer |
429 | Daily quota exhausted | error: "daily quota exceeded" + used, limit, resets (ISO), upgrade |
403 | Bulk endpoint on a non-Desk plan | error: "plan does not include bulk export" + upgrade |
404 | Unknown or hidden story on the coverage endpoint | error: "unknown story" |
A worked example
Find what is spreading, then pull the full record for the top story:
# 1. what is moving right now
curl -H "Authorization: Bearer un_…" \
"https://urgent.news/api/propagation.json?hours=6&min_outlets=5"
# 2. take a cluster_id from the response and get its record
curl -H "Authorization: Bearer un_…" \
https://urgent.news/api/coverage/1615193.json
Using the data
Use it in your products, research and reporting, including commercially. Please attribute urgent.news as the source of the coverage data, keep the publisher links intact where you show individual stories, and don't present machine-written summaries as human reporting. The full terms are at /terms. Every response carries a disclaimer field restating that the prose is machine-written from other outlets' reporting.