Docs API
Status & Edge
Liveness checks, the footer status pill, and the per-visitor edge facts — four small endpoints that never cache and never fail loudly.
Four endpoints that answer “is anything alive, and where am I talking to it from”. None of them are cached at the edge, and two of them are deliberately incapable of returning an error — read on for why that matters when you build a status indicator on top of them.
Health
GET /api/health
HEAD /health
No auth, no rate limit, no query parameters. Always 200:
{ "status": "ok", "service": "site-api", "checkedAt": "2026-08-23T05:12:44.310Z" }
HEAD returns the same headers with an empty body. checkedAt is generated
per request, so a changing timestamp is your proof the response is not coming
from a cache. Cache-Control: no-store, max-age=0.
This endpoint proves the Worker booted and can run a handler. It does not
touch D1, KV, R2, or the queue — a 200 here tells you nothing about whether
the mood archive is readable. Nothing behind this route can fail, which is the
point: it is the check that isolates “the Worker is down” from “a dependency
is down”.
/api/v2/health is a legacy alias and answers GET/HEAD with a redirect
to /api/health.
Ping
GET /api/ping
HEAD /ping
204 No Content, empty body, Cache-Control: no-store, max-age=0. No auth,
no rate limit.
Cheaper than /api/health — there is no JSON to serialize — so it is the better
target for a latency probe or an uptime monitor polling every few seconds. Use
/api/health when you want to read something back, /api/ping when you only want the
round-trip time.
Footer status
GET /api/footer
The data behind the status pill in the site footer. Rate limit: 60 requests /
60s. Cache-Control: no-store, max-age=0.
{ "status": "operational", "provider": "betterstack", "updatedAt": "2026-08-23T05:12:44.310Z" }
status is one of operational, degraded, down, maintenance, or
unknown. Upstream is Better Stack’s public status JSON, whose
aggregate_state maps across almost verbatim — the one rename is Better
Stack’s downtime, which becomes down here.
This endpoint never returns an error for an upstream failure. If Better
Stack is unreachable, times out (there is a 5s abort), or answers with
something unparseable, the handler logs a warning and still returns 200 with
status: "unknown". So unknown is not a null value you can skip — it is the
signal that the status check itself failed, and a UI that treats it as “no
data yet” will silently show a stale-looking pill forever. Render it as its own
state.
The only non-200 you will see is 429 {"error":"Too Many Requests"} from the
rate limiter.
Two caching layers are at work and they are easy to confuse: the response you
get is no-store and never cached, but the Better Stack probe behind it is
cached inside the Worker for 45 seconds. So updatedAt can be up to 45s old on
a response that was itself generated just now.
Responses also carry x-cloudflare-colo: <XXX> when Cloudflare reports a
three-letter colo for the request, which is the cheapest way to tell whether
two callers are hitting the same edge location.
Edge
GET /api/edge
What Cloudflare knows about the connection that asked. Cache-Control: no-store, max-age=0 — these are per-visitor facts and sharing them across
requests would hand one visitor another’s location.
{
"colo": "MEL",
"country": "AU",
"city": "Melbourne",
"region": "Victoria",
"protocol": "HTTP/3",
"tls": "TLSv1.3",
"rtt": 12,
"network": "Telstra Limited"
}
Every field is nullable and frequently null — city, region, and network
are absent for plenty of real networks, rtt is missing on a connection
Cloudflare has not measured yet, and everything is null when the request did
not arrive through Cloudflare at all (local astro dev, for instance). Type
the response as Partial and render around the gaps rather than asserting
them.
colo is validated against /^[A-Z]{3}$/ before it is returned, and rtt is
rounded to a whole millisecond and clamped at zero, so neither can surprise you
with a malformed value — they are either well-formed or null.