Point a Statuspage client at https://statuspage.sandboxapis.dev — paths are /v1/… exactly as on api.statuspage.io, so a caller keeps writing /v1/pages/{page_id}/incidents unchanged. Auth is 'Authorization: OAuth <anything>' (the prefix is OAuth, NOT Bearer — that is Statuspage's own scheme), and no token at all works too; the ?api_key= query param the docs describe is accepted as well. A .json suffix on any path is tolerated, as the real API's own examples use. WHAT IS RENDERED: the customer-facing status story, end to end — the org's two status pages (one public, one partner-only), the components each page publishes, every incident and scheduled maintenance posted to them, each incident's full update cadence (investigating -> identified -> monitoring -> resolved) with per-component old_status/new_status transitions, the published postmortems, and the page's subscribers with their counts and by-state histogram. THE INCIDENTS ARE THE SAME OUTAGES sentry./pd. serve the responder-facing half of: one canonical incident, the same timestamps, the same affected services — a Statuspage incident and a PagerDuty status-page post are one event in two dialects. Component status is LIVE and derived from the incident record: a component reads degraded_performance or major_outage only while a post that impacts it is still open. WHAT IS NOT: component groups, metrics and metrics providers, uptime calculations, page access users and groups, incident templates and the status embed config — this universe carries no canonical fact behind any of them, so each answers an explicit coverage 404 rather than an invented value or an empty list that would assert the fact is absent rather than unmodelled. ONE GENERATION NOTE: the unsubscribed roster IS served, and it needs generation hello-15 — the generation that gave this universe a subscription state meaning "unsubscribed" at all. Live rides hello-19, which inherits it, and answers it; so do the statuspage-v1-g11 pin on hello-16 and the statuspage-v1-g10 pin on hello-15 itself; a pin frozen earlier answers a 404 naming the generation rather than an empty list that would read as "nobody ever unsubscribed". Check https://sandboxapis.dev/providers/statuspage for the row-by-row picture.
Coverage badge
Deep37/38 · 97%
The share of read rows with a final answer — served and verified, a deviation, retired upstream, or a reviewed empty/refusal. Computed in the coverage manifest, copied here.
statuspage.sandboxapis.devServed & verified
27 / 71%
Answering with real universe data, each response checked against Statuspage's published spec by the conformance suite on this build. 1 of them is marked a deviation — served faithfully, but failing the vendored spec.
The full read API
38
Every read surface Statuspage publishes, deferred long tail included, minus the rows excluded by policy. Writes are out of scope: this universe is read-only.
Not served yet
11
Each returns an explicit, provider-shaped coverage error naming the gap — never invented data.
01 / The swap
No SDK of ours, no shim, no recorded fixtures. The same client library you already use, one environment variable different.
curl "https://statuspage.sandboxapis.dev/v1/pages"Verified drop-in clients
No client library is version-pinned against Statuspage yet, so this page claims none. What is asserted is the wire: every response above is validated against Statuspage's own published spec by the conformance suite. Client pins live in coverage/client-pins.yaml and are added as each SDK joins the suite.
02 / Coverage by family
All 38 rows the coverage manifest carries for Statuspage, deferred long tail included and nothing capped. Open a family, or filter by path to find the exact endpoint your client calls.
Status — what a conformance test found
Mode — what kind of answer a row gets
A row with no mode shown has not been judged yet. Modes are the manifest's own words, from coverage/MODES.yaml; every empty and refuse carries a written reason and a review date before it counts as final.
Badge — where Deep starts
REST + GraphQL + git rows that are not `excluded`. Write operations are NOT rows (DECISIONS 2026-09-01 decision 9): they are counted in meta.write_operations and never enter this ratio.
38 read surfaces in 14 families
StatusPageSubscription.status carries "unsubscribed" as of hello-15 (features.subscriptionStates, the founder's 2026-09-06 ruling), with unsubscribed_epoch for the instant. This serves exactly the rows subscribed() excludes from the list, the count, the histogram and the by-id lookup, because Statuspage's own state filter is closed at active/unconfirmed/quarantined and gives a departed subscriber their own collection instead of a fourth state. A pre-hello-15 artifact answers the generation gap, not [], which would assert that nobody in a universe that cannot represent unsubscribing ever did
THIS PAGE IS NOT AUDIENCE-SPECIFIC, and the fact rests on something a caller can already read on this same host rather than on an assertion. The vendored document says it in its own tag prose, twice and in the same words - "You will need Statuspage to switch your page type to audience-specific before these API endpoints have any effect on your status page" - and shapePage() publishes viewers_must_be_team_members off canon's status_page.kind, which is Statuspage's TEAM-MEMBERS-ONLY page and a different type from an audience-specific one. Canon carries exactly two page kinds, public and private, and no third. So nobody logs in to see this status page. The ONE person-to-page link canon does hold is status_page_subscription.person, and it is a NOTIFICATION RECIPIENT off the engineering roster rather than a login-holding viewer - which is exactly why the subscriber rows on this host serve real rows while this one is empty, and the conformance entry asserts BOTH halves on one run so a later wave cannot make the two agree by habit rather than by evidence. Statuspage's own answer for a page with none is [] at 200. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
refuse — ENTAILED by the empty beside it: the page-access-user list serves [] on this same host, so no page access user id resolves and Statuspage's own 404 is the whole answer. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
refuse — ENTAILED by the same empty: no page access user resolves, so no per-user component list exists to serve. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
refuse — ENTAILED TWICE OVER, and both halves are worth stating because either alone would carry it: no page access user resolves, AND /pages/{page_id}/metrics is itself a ruled honest empty on this host (2026-09-12, no metrics provider is connected to this page), so even a user who did resolve could own no metric. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
this universe has no component group at all - status_page_service carries no group column and renderer-statuspage/src/render.ts already says so in as many words while omitting `group_id` from every served component; Statuspage's own answer for a page with no groups is `[]` at 200. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
refuse — ENTAILED by the empty beside it: the group list serves `[]` on this same host, so no id resolves and Statuspage's own 404 is the whole answer. The body is the document's own declared 404 description in the captured `{"error": ...}` envelope - Statuspage's 404 can never be captured (its auth layer answers 401 before its router), so an authored sentence would be a claim this repository cannot check, and `x-sandboxapis-refusal` is the whole wire difference. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
refuse — ENTAILED by the same empty: with no group, there is no group to compute uptime over. Same body and same header as the row above. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
SERVED 2026-09-12 (wave W6): the component's uptime, COMPUTED from the page's own incident record rather than stored - the same walk shapeIncidentUpdate already makes to resolve old_status/new_status, with a clock attached: the seconds between consecutive reported_epoch instants are seconds that component spent in the status the earlier update gave it. DOWNTIME IS BOTH KINDS OF OUTAGE, and the rule was chosen for a reason the caller can check: Statuspage publishes uptime_percentage, major_outage and partial_outage and no formula relating them, and its own example (96.67 with partial_outage 0) cannot discriminate - so the rule taken is the one under which the object is SELF-CONSISTENT, and a caller can recompute the percentage from the two second-counts and the range this same response publishes. degraded_performance is not an outage and contributes nothing. The default window is the document's own ninety days ending at the ARTIFACT'S ANCHOR - 'today in the page's time zone' for a frozen universe is the instant it is frozen at - and `start`/`end` are honoured as the partial dates the document describes, with its own clamp warning when an end date is past today and its documented 422 for a range over six calendar months. `related_events` is OMITTED: the document types it as a single object carrying one `id` while describing it as 'Related incidents', and three of this page's posts touch the fixture component - emitting one would be picking and an array would fail the document's own schema. range_start/range_end are full ISO date-times because the field's `format: date-time` is what a generated client enforces, though the example beside it prints a bare date
this status page publishes no metric - Statuspage requires every published metric to belong to a metrics provider and this page has none (the row below); canon's metric_series and sample rows belong to the internal observability canon (metric_series.service names a TECHNICAL service) and no status_page_service publishes one. NOTE the document types this collection as a single `Metric` where the live API returns a list - the sixth of the seven bugs coverage/provider-pins.yaml catalogues for this provider - so the array is the mirror and `[]` is its empty shape either way. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
refuse — ENTAILED by the same empty: no provider id resolves, so there is no provider whose metrics could be listed. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
refuse — ENTAILED by the empty above: the page publishes no metric, so no metric id resolves and Statuspage's own 404 is the whole answer, in the document's own declared 404 description. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
THIS PAGE IS NOT AUDIENCE-SPECIFIC, and the fact rests on something a caller can already read on this same host rather than on an assertion. The vendored document says it in its own tag prose, twice and in the same words - "You will need Statuspage to switch your page type to audience-specific before these API endpoints have any effect on your status page" - and shapePage() publishes viewers_must_be_team_members off canon's status_page.kind, which is Statuspage's TEAM-MEMBERS-ONLY page and a different type from an audience-specific one. Canon carries exactly two page kinds, public and private, and no third. So this page has no page access group - not because the generator forgot a roster, but because the page has no audience to roster. Statuspage's own answer for a page with none is [] at 200. THE PAGE STILL HAS TO RESOLVE: an id this universe does not have is the ordinary entity 404, never a cheerful empty. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed; this SUPERSEDES wave W6's framing of these rows as a canon question.
refuse — ENTAILED by the empty beside it: the group list serves [] on this same host, so no group id resolves and Statuspage's own 404 is the whole answer - the component-groups precedent this fence ruled on 2026-09-12, one family over. The body is the document's own declared 404 description in the captured {"error": ...} envelope, and x-sandboxapis-refusal is the whole wire difference. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
refuse — ENTAILED by the same empty: with no page access group there is no group whose components could be listed. Same body and same header as the row above. RULED by the founder 2026-09-14 on the unjudged-47 pass's proposal, as proposed.
the canon models subscription scope on a real column (subscribable_kind is one of status_page / status_page_service / status_page_post) and the renderer queries it. As of hello-15 (features.subscriptionStates) this universe EMITS post-scoped subscriptions, so the row answers non-empty rather than the [] the 2026-09-01 note flagged. The founder confirmed derive over empty on 2026-09-06
SERVED 2026-09-12 (wave W6): one post-scoped subscriber, addressed by id. THE ROUTE WAS ALREADY REGISTERED AND SERVING - its list twin .../subscribers has been `derive` since 2026-09-06 and this by-id row simply had no MODES entry - so the work was a manifest entry plus a conformance assertion. The lookup is scoped to the POST, which is what makes it a different operation from /pages/{page_id}/subscribers/{subscriber_id}: a page-scoped subscriber does not resolve here, and conformance asserts it
no metrics provider is connected to this page - canon carries no Datadog/Pingdom/New Relic connection and no self-hosted provider row, and without a provider Statuspage can publish no metric at all; its own answer for a page with none is `[]` at 200. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
refuse — ENTAILED by the empty beside it: the provider list serves `[]`, so no provider id resolves. Statuspage's own 404, not the 403 the same operation also declares: that 403 is about a CALLER's authorization, and this ruling is about an object nobody created. RULED by the founder 2026-09-12 (round 19), on the coverage audit's proposal
SERVED 2026-09-12 (wave W6): one member's access across the organization's pages - the SAME user set /organizations/{id}/users serves (org members who are not bots) and both real page ids, so a caller cannot find somebody in one list and be told they do not exist in the other. THE THREE RBAC BOOLEANS ARE OMITTED ON THE DOCUMENT'S OWN SENTENCE: page_configuration, incident_manager and maintenance_manager each carry the description 'This field will only be present if the organization has Role Based Access Control', and this canon models none - emitting them would assert an RBAC organization. STATUS `deviation`, NOT `green`, and the reason is the document rather than us: it types `data.pages` as a single OBJECT and prints an ARRAY of two page entries in its own example three lines below. This organization really has two pages, so one object could not carry the answer; the row mirrors the live API and the example, not the declared type, which is what `deviation` means. The eighth document bug catalogued for this provider
SERVED 2026-09-12 (wave W6): the universe's own three status-update templates - reader.listStatusUpdateTemplates(), the same three renderer-pagerduty publishes, so one canonical template is one template on both hosts. `name` is the label the Templates tab shows and `title` the subject line canon stores; `update_status` is emitted for the two slots that name a Statuspage status exactly (the holding statement is `investigating`, the resolution notice says in its own text that the incident is resolved) and OMITTED for the progress update, whose message renders {{incident.status}} rather than setting one. THE BODY KEEPS ITS PLACEHOLDERS, and that is the wave's one ruling here: canon's template text carries the field keywords template_field publishes, Statuspage's own templates have no interpolation, and resolving is not available because a template is attached to no incident and no component - so there is no incident number and no business service to resolve against. Omitting the body would leave a template with no content; the text is served verbatim and the deviation from Statuspage's plain-text templates is recorded. components/group_id/should_tweet/should_send_notifications are omitted: canon's template names no service, belongs to no group, and this universe has no Twitter integration
generate — A WIDENING, NOT A BUILD, and the one of the eight unjudged Statuspage rows that is NOT the audience question. A status embed config is a page's own PRESENTATION settings - `position`, the corner the iframe sits in, and four colours for the incident and maintenance states - and canon's status_page table carries six columns of which none is presentational (packages/artifact/src/rows.ts:1122: id, kind, name, url, published_epoch, content_seed). Real Statuspage answers 200 here for every page, so neither cheap answer is available: there is no collection to be empty, and a 404 would deny an operation Statuspage serves. The document names no default for any of the five fields and publishes no example, so nothing is derivable and nothing may be authored - the placeholder invariant. The widening is five columns on status_page, themed at compile time like every other page string. It unblocks ONE row, on this provider only: no other host in the fleet publishes an embed configuration. CLASSIFIED 2026-09-13 (the unjudged-47 pass); the 2026-09-12 W6 fence grouped it with the seven page-access rows as "one canon question", and it is a different one
03 / What's simulated
Each line below is one assertion in the conformance suite named beside it — run on every build, over one artifact, through the real renderers. Nothing is claimed here that no expect checks.
An incident here is the same publication PagerDuty serves as a status-page post: the same title, the same updates in the same order at the same instants with the same text, the same postmortem, and the same affected services — an `incident_update.affected_components` entry and a `post_update.impacted` entry name one business service, and the two hosts' impact vocabularies (`major_outage` / `full-outage`) are two spellings of one canonical level.
Against PagerDuty · parity/status-page-parity.test.ts
The outage a page told customers about is the one responders were paged for: every incident post here resolves to a canonical incident PagerDuty serves, and the page never announced an outage before that incident opened — so the customer-facing story and the responder-facing one are the same event, not two.
Against PagerDuty · parity/status-page-parity.test.ts
04 / Known deviations
These answer with real data and match what Statuspage actually returns, but fail the spec we vendor from Statuspage — nearly always a bug in that published description. They are listed rather than counted so you can see whether one of them is the endpoint your client calls, and they are never counted as verified.
GET /organizations/{organization_id}/permissions/{user_id}usersSERVED 2026-09-12 (wave W6): one member's access across the organization's pages - the SAME user set /organizations/{id}/users serves (org members who are not bots) and both real page ids, so a caller cannot find somebody in one list and be told they do not exist in the other. THE THREE RBAC BOOLEANS ARE OMITTED ON THE DOCUMENT'S OWN SENTENCE: page_configuration, incident_manager and maintenance_manager each carry the description 'This field will only be present if the organization has Role Based Access Control', and this canon models none - emitting them would assert an RBAC organization. STATUS `deviation`, NOT `green`, and the reason is the document rather than us: it types `data.pages` as a single OBJECT and prints an ARRAY of two page entries in its own example three lines below. This organization really has two pages, so one object could not carry the answer; the row mirrors the live API and the example, not the declared type, which is what `deviation` means. The eighth document bug catalogued for this provider05 / Pinned snapshots
Each pin regenerates byte-identically on every request, so a test written against one never drifts. Generations are DIFFERENT universes, not versions of one — never swap a suffix expecting the same data.
statuspage-v1-g10.snap.sandboxapis.devgeneration 10v1servedstatuspage-v1-g11.snap.sandboxapis.devgeneration 11v1servedstatuspage-v1-g9.snap.sandboxapis.devgeneration 9v1servedNeed an endpoint that is not served yet?
Every row Statuspage's manifest carries is on this page, so “not here” is an answer rather than a gap in the rendering. Tell us which path and which client, and it moves up the queue — the order is set by what people ask for.