When we shipped the dashboard, the RSS endpoint at /rss.xml was an afterthought. A footer link with a small icon, easy to miss. The first month we tracked it, the feed was getting a third of all the page traffic. People were not reading it in browsers — they were piping it into Zapier, Slack, IFTTT, and homegrown notification rigs.
That made us rethink what the feed was for. RSS in 2026 is mostly a machine protocol. The right design choices for a machine consumer are different from a human one. This post is about the choices we ended up making.
What the feed is
A standard RSS 2.0 XML document at https://claudestatus.com/rss.xml. Each <item> represents one Claude incident from the last few weeks. Each item has:
<title>— the incident’s short name from Statuspage.<link>— the canonical short link to the incident onstatus.claude.com.<guid isPermaLink="false">— the Statuspage incident ID (stable, never reused).<pubDate>— when the incident was created.<description>— the most recent up to three updates, formatted as<strong>Status:</strong> bodyper update, joined by<br/>.<category>— the impact level:none,minor,major, orcritical.
The endpoint serves up to 30 items per fetch, sorted newest first. We cache the rendered XML for five minutes in Cloudflare KV so a sudden burst of subscribers does not hammer Statuspage upstream.
What it is not
The feed is not a blog feed. It is not a “subscribe to all dashboard posts” feed. The blog has its own — every post listing on /blog — but the /rss.xml endpoint is exclusively about incidents. We did not want to mix incidents and editorial content in the same feed because the consumers are different: a human might read both, but a machine watching for incidents wants exactly the incident stream and would have to filter blog posts out otherwise.
The feed is also not a complete archive. It carries the most recent 30 incidents — enough for any reasonable consumer to detect new ones across reasonable poll intervals (typical RSS readers poll every 15–60 minutes; in 30 incidents you would have to be silent for many days for an incident to roll off without being seen). For full history, the canonical place is status.claude.com’s incident archive.
Design choices that turned out to matter
Several things we got right by accident, and a few we corrected after watching how machines actually consume the feed.
Stable, non-permaLink GUIDs
The <guid> is the upstream Statuspage incident ID. Two consequences:
- Idempotency. A consumer can keep a set of seen IDs and process each incident exactly once, even across feed restarts, even if the title or description changes when Statuspage updates.
- Disconnection from URL changes. If we ever move the dashboard URL, or change how
<link>is constructed, the GUID does not move. Consumers with seen-ID sets keep working.
isPermaLink="false" declares this explicitly. Some RSS readers default to treating GUID as a URL; the attribute prevents misinterpretation.
Description as composed update history, not just the latest update
A naive description would put the most recent update body in <description>. We instead pack the up-to-three most recent updates, each prefixed with their status (Investigating:, Identified:, Monitoring:, Resolved:).
The reason: a Slack-bot consumer that posts the description as a message gets to show the user the lifecycle of the incident in one notification — “Identified at X, monitoring at Y, resolved at Z.” This is dramatically more useful than just the latest line, especially for incidents that have already moved through several states by the time the consumer sees them.
The trade-off is a longer description. We capped at three updates because four-plus starts to feel cluttered in most readers. The on-screen view in a browser still works — it just renders the three most recent updates as bolded labels and lines.
Category for severity filtering
Many consumers want to filter — for example, only forward major and critical events to the on-call channel, ignoring minor events that happen daily and would create noise. The <category> element with the impact level makes this trivial.
Most RSS readers ignore categories. Most automation platforms (Zapier, Make, n8n, Pipedream) expose them as filterable fields. The element exists primarily for the automation use case, and the fact that browsers ignore it is harmless.
XML escaping that actually works
The first version of the feed had a subtle bug: incident bodies containing ]]> would terminate the CDATA section in <description> early, breaking the XML. The fix is to replace ]]> with ]]> inside CDATA, which is the standard escape for that sequence. We mention it because it is a common bug in hand-written RSS generators and it produced a few months of broken feeds for the consumers who fetched during the rare incidents whose bodies contained that exact sequence (it was rare but not zero).
The general rule: every textual field that goes into the XML gets escaped for &, <, >, and " separately, plus the ]]> correction inside CDATA. We have a six-line escapeXml helper in src/pages/rss.xml.ts; that is the entire escaping logic.
How to consume it well
For a human reader: paste https://claudestatus.com/rss.xml into Feedly, NetNewsWire, or whatever you use. New incidents land in your reader within five to ten minutes of Anthropic posting them.
For an automation: poll on whatever cadence you can afford. The cache layer makes 30-second polling fine for a single consumer, but most consumers do not need that frequency. A 5- to 15-minute poll is plenty unless you are operating something with sub-minute incident sensitivity, in which case you should be subscribed to multiple sources, not just one.
For a Slack-style notification bridge: filter on <category> to suppress minor events if you do not want them in a channel. Use the GUID for deduplication. Post the description as a multi-line block; most Slack importers handle the embedded <br/> tags appropriately.
For a homegrown dashboard: the feed is fine, but you might do better fetching the upstream Statuspage JSON feed directly. Statuspage’s incidents.json has more fields than we expose in RSS, including the full update history, component associations, and resolved timestamps. The endpoint is documented on our Methodology page. Use whichever shape is easier to parse for your stack.
The cache layer
Five-minute TTL in Cloudflare KV. Keyed under rss:xml, with a sibling key rss:updated_at for cache validity checking.
Two reasons for the TTL:
- Politeness to Statuspage. Even though the public API has no documented rate limit, every cache miss makes one outbound request to fetch incidents. With a 5-minute cache, the upstream sees one request from us per 5 minutes regardless of subscriber count.
- Predictable freshness for consumers. A polling consumer that hits us every 30 seconds does not get fresh data faster than a consumer that hits every 5 minutes. They both see updates within 5 minutes. This means consumers who poll aggressively are wasting their own resources, not ours.
Cache invalidation happens on TTL expiry — there is no push from upstream that would let us invalidate sooner. We could shorten the TTL during active incidents (when fresh updates matter more) but the added complexity is not worth the marginal freshness improvement.
What we would do differently
Two things, given what we know now.
We would publish a separate <atom:link rel="self"> advertisement for an Atom version of the same feed. Atom has cleaner semantics for some things (especially around update timestamps and content type), and a few sophisticated consumers prefer it. We did not, because the cost of supporting two formats is non-trivial and the rotation of features we want to ship is long.
We would consider exposing a JSON Feed (jsonfeed.org) version. JSON Feed is easier to consume from JavaScript clients without an XML parser, and the format is tiny. This is on the roadmap; it is not the highest-priority thing.
For now, the RSS feed is the cheapest reliable way to bridge Claude incidents into your tooling. It is a single URL, well-shaped XML, stable GUIDs, machine-friendly categories, no authentication. Subscribe once and walk away.