1. Documentation
  2. HTTP API
  3. Badges, feeds and metrics
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

Badges, feeds and metrics

Embeddable SVG badges, RSS and Atom feeds, and the Prometheus exposition.

Three machine-readable surfaces, all public, each gated by its own configuration flag.

Badges

Enabled by metrics.badges (default true).

text
GET /api/heyo-status/badge/:name.svg
QueryValuesDefault
metricstatus, uptime, latencystatus
period24h, 7d, 30d, 90d24h (uptime only)
labelany stringthe monitor name, or status
md
![API](https://status.acme.com/api/heyo-status/badge/API.svg)![Uptime](https://status.acme.com/api/heyo-status/badge/API.svg?metric=uptime&period=30d)![Latency](https://status.acme.com/api/heyo-status/badge/API.svg?metric=latency)![Overall](https://status.acme.com/api/heyo-status/badge/overall.svg?label=Acme)

overall is reserved for the page-level state. Percent-encode names with spaces: /badge/Web%20App.svg.

ResponseWhen
200SVG, cache-control: public, max-age=60.
400Unknown metric or period, or invalid percent-encoding.
404Unknown monitor, a path not ending in .svg, or badges off.

Colours follow the value: green at or above 99.9% uptime (or healthy), amber at or above 95% (or degraded), red below, grey when there is no data.

The one-minute cache is deliberate: badges are embedded in third-party READMEs, and without it every page view would become a database read.

RSS and Atom

Enabled by incidents.enabled (default true).

text
GET /api/heyo-status/rss.xmlGET /api/heyo-status/atom.xml

Both publish the newest 50 incidents — feeds are public and unauthenticated, so they serve a bounded window rather than a history that only grows.

xml
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">  <channel>    <title>Acme Status incidents</title>    <link>https://status.acme.com</link>    <atom:link href="https://status.acme.com/api/heyo-status/rss.xml" rel="self" type="application/rss+xml"/>    <description>Real-time status for Acme services.</description>    <lastBuildDate>Thu, 01 Jan 2026 10:41:00 GMT</lastBuildDate>    <item>      <title>Elevated API latency (monitoring)</title>      <link>https://status.acme.com#incident-inc_2f8c1d</link>      <guid isPermaLink="false">heyo-status:incident:inc_2f8c1d</guid>      <pubDate>Thu, 01 Jan 2026 10:41:00 GMT</pubDate>      <description>…the incident timeline…</description>    </item>  </channel></rss>

Entries link to the #incident-<id> anchor that every bundled theme renders, so a reader lands on the incident itself. Absolute URLs come from siteUrl, falling back to the request origin — set siteUrl before publishing, or a feed behind a proxy will advertise the wrong host.

Build the same XML yourself from any incident list:

ts
import { createAtomFeed, createRssFeed } from "@heyo-sh/heyo-status/feeds";const incidents = await statusPage.database.listIncidents({ limit: 50 });const xml = createRssFeed(statusPage, incidents, "https://status.acme.com");

Prometheus

Enabled by metrics.prometheus (default false).

text
GET /api/heyo-status/metrics

Served as text/plain; version=0.0.4; charset=utf-8.

text
# HELP heyo_status_up Current monitor status (1 = operational, 0 = failing).# TYPE heyo_status_up gaugeheyo_status_up{monitor="API",type="http",group="API"} 0heyo_status_up{monitor="Website",type="http",group="Web"} 1# HELP heyo_status_uptime_ratio Uptime ratio per reporting period (0-1).# TYPE heyo_status_uptime_ratio gaugeheyo_status_uptime_ratio{monitor="API",period="24h"} 0.972# HELP heyo_status_overall Overall status page state.# TYPE heyo_status_overall gaugeheyo_status_overall{state="operational"} 0heyo_status_overall{state="degraded"} 1
MetricLabels
heyo_status_upmonitor, type, group
heyo_status_latency_millisecondsmonitor, type, group
heyo_status_uptime_ratiomonitor, period
heyo_status_last_check_timestamp_secondsmonitor
heyo_status_overallstate
heyo_status_monitors—
heyo_status_active_incidents—
heyo_status_scheduled_maintenances—
heyo_status_snapshot_timestamp_seconds—
prometheus.yml
scrape_configs:  - job_name: heyo-status    metrics_path: /api/heyo-status/metrics    scheme: https    static_configs:      - targets: ["status.acme.com"]

Only aggregate, already-public data is exposed: no incident content, no monitor targets, nothing from a private monitor.

/metrics is not behind the admin token

If a scrape endpoint should not be world-readable, leave prometheus: false and expose the same numbers behind your own gateway with createPrometheusMetrics(), or restrict the path at the edge.

Health

text
GET /api/heyo-status/healthz  →  { "ok": true }

Answers whether the status API is serving. It does not read the database and does not reflect monitor state.

Related

  • Metrics configuration
  • Status endpoints
  • Package exports
Subscriber endpoints< PreviousAdmin endpointsNext >

Powered by heyo

On this page

BadgesRSS and AtomPrometheusHealth