---
title: "Service-Status"
description: "Öffentlicher, unauthentifizierter Endpunkt mit Uptimeifys eigener Plattform-Verfügbarkeit — Check- und Alert-Delivery-Quoten, Komponenten-Status und die hash-verketteten eingefrorenen Monatswerte."
---

`GET /api/service-status`

Keine Authentifizierung nötig. Liefert keine Kundendaten — nur die eigenen Delivery-Kennzahlen der Plattform, den Status der veröffentlichten Komponenten und die eingefrorenen Monatswerte dahinter.

## Beispiel (cURL)

```bash
curl -X GET "https://uptimeify.io/api/service-status" \
  -H "Accept: application/json"
```

## Antwort (Response)

```json
{
  "checkDelivery": {
    "pct": 99.97,
    "coveragePct": 99.94,
    "insufficientData": false,
    "windowDays": 90
  },
  "alertDelivery": {
    "pct": 97.31,
    "sampleSize": 2454,
    "endpointFailures": 140,
    "latencyP90Ms": 1727,
    "latencySampleSize": 812,
    "insufficientData": false,
    "latencyInsufficientData": false
  },
  "components": [
    { "key": "de-fra", "displayName": "Frankfurt (DE)", "state": "operational", "coveragePct": 99.94 },
    { "key": "alerting", "displayName": "Alert Delivery", "state": "operational", "coveragePct": 100 }
  ],
  "frozenMonths": [
    {
      "month": "2026-06-01",
      "sliKind": "check_delivery",
      "scopeKey": "mesh",
      "numerator": 4318204,
      "denominator": 4319850,
      "unknownUnits": 612,
      "rowHash": "9f2c1a7e4b8d0f3a6c5e2b1d8a7f4e9c0b3d6a1f8e5c2b9d7a4f1e8c5b2d9a6f"
    },
    {
      "month": "2026-06-01",
      "sliKind": "measurement_coverage",
      "scopeKey": "mesh",
      "numerator": 43176,
      "denominator": 43200,
      "unknownUnits": 24,
      "rowHash": "4d1b8e0cc0a2f7593e6b4c1d8a05f2e7b9c3d6a1f8e5c2b9d7a4f1e8c5b2d9a6"
    }
  ]
}
```

Hinweise:

- `checkDelivery.pct` und `alertDelivery.pct` sind rollierende `windowDays`-Werte (aktuell 90 Tage), auf zwei Nachkommastellen gekappt — nie aufgerundet.
- **`unknownUnits` steckt in KEINEM Nenner auf diesem Endpunkt.** Jede Quote wird über das gemessen, was die Plattform tatsächlich belegen kann: `denominator` ist die Erwartung über die erfassten Einheiten, `unknownUnits` die Erwartung über die nie erfassten — eine Lücke im minütlichen Recorder, ein hängengebliebener Rollup. Beides sind getrennte Achsen, weil „wir waren nicht verfügbar" und „wir konnten nicht messen" zwei verschiedene Tatsachen sind: Die zweite in die erste einzurechnen, veröffentlicht einen Ausfall, den es nicht gegeben hat, und macht die Zahl zum Maß für die Zuverlässigkeit des Recorders statt für die des Dienstes.
- **`coveragePct` ist die zweite Hälfte jeder Aussage und wird nie weggelassen.** Es ist der Anteil des Fensters, den die Plattform belegen kann, `0`-`100`, auf zwei Nachkommastellen gekappt — `null` nur dann, wenn im Fenster noch gar nichts geschuldet war. `checkDelivery.coveragePct` gilt für das gesamte Prüfnetz, `components[].coveragePct` je Komponente; für eine nie gemessene Komponente ist es `null`. Die konservative Lesart — der Anteil des Fensters, der positiv als ausgeliefert bestätigt ist — ist das Produkt beider Werte: `pct x coveragePct / 100`.
- Aus jeder `frozenMonths`-Zeile lässt sich die Abdeckung selbst nachrechnen: `denominator / (denominator + unknownUnits)`. Zeilen mit `"sliKind": "measurement_coverage"` geben sie direkt in Minuten an: `numerator` sind die erfassten, `denominator` die geschuldeten Minuten. Diese Art existiert, weil an einem Tag, an dem der Recorder gar nicht lief, `check_delivery` bei `0/0` steht — es gibt keine beobachtete Rate, mit der sich eine fehlende Check-Minute bewerten ließe — und nur eine minutenbasierte Zeile so einen Tag sichtbar hält.
- **Alarm-Zustellung sind zwei Zahlen — und keine davon ist eine 60-Sekunden-Schwelle.** `alertDelivery.pct` ist der Anteil der versuchten Zustellungen, die **am Ende angekommen sind**; `alertDelivery.latencyP90Ms` ist die p90-Zeit von der Eröffnung des Incidents bis zur ersten Benachrichtigung, die unsere Systeme tatsächlich verlassen hat, in Millisekunden. Die erste Zahl beantwortet „ist sie angekommen", die zweite „wie schnell" — eine einzelne Zahl kann beides nicht leisten. Der zuvor veröffentlichte Wert (Anteil der Zustellungen innerhalb von 60 Sekunden) leistete keines von beidem: Bei einem Median unter einer Sekunde konnte er eine schnelle Zustellung nicht von einer unterscheiden, die fast eine Minute brauchte — und er bewertete eine Zustellung, die nie ankam, genauso wie eine langsame.
- **`alertDelivery.pct` zählt jede Zustellung einmal, nicht jeden Versuch.** Die Einheit ist ein Paar aus `(Incident, Benachrichtigungskanal)`; das Paar gilt als zugestellt, wenn **irgendein** Versuch dafür erfolgreich war. Eine Benachrichtigung, die zweimal scheitert und im dritten Anlauf durchgeht, ist damit ein Erfolg — würden wir Versuche zählen, fiele die Zahl umso schlechter aus, je härter unsere Wiederholungslogik arbeitet. `alertDelivery.sampleSize` ist die Anzahl dieser Paare. Trägt eine Zustellung keine Kanal-ID (E-Mail an die hinterlegte Adresse, die Incident-Management-Brücke), tritt der Transportweg an ihre Stelle.
- **`alertDelivery.pct` zählt die Fehler, die wir zu verantworten haben — `endpointFailures` zählt die anderen.** Eine Zustellung, die nie ankam, weil das Ziel nicht mehr existierte (`404`, `410`), weil die uns anvertrauten Zugangsdaten zurückgezogen wurden (`401`, `403`), weil dein Dienst uns gedrosselt hat (`429`), weil er mit `5xx` antwortete oder weil sein Host überhaupt nicht erreichbar war (DNS-Fehler, abgewiesene Verbindung, TLS-Fehler, Zeitüberschreitung), ist ein Fehler dieses Ziels und keiner unserer Alarmierung. Solche Zustellungen verlassen `pct` — sie stehen weder im Zähler noch in `sampleSize` — und werden stattdessen in `alertDelivery.endpointFailures` gezählt. Fehler auf unserer Seite bleiben drin und gehen zulasten der Quote: eine von einem Chat-Dienst als fehlerhaft abgewiesene Nachricht, ein Kanaltyp, für den unser Versand keine Behandlung hatte, ein von unserem eigenen Relay oder unserer Sendebremse übersprungener Versand — und jeder Fehler, den wir der Gegenseite nicht zweifelsfrei zuordnen können. **Im Zweifel entscheiden wir gegen uns**: Was sich nicht zuordnen lässt, zählt als unser Fehler. Die umgekehrte Regel würde die Zahl jedes Mal verbessern, wenn wir auf eine neue Art scheitern.
- **`endpointFailures` wird nie unterdrückt und nie weggelassen.** Es ist eine Anzahl, also bei jeder Stichprobengröße exakt richtig, und hat deshalb keine Veröffentlichungsschwelle: Der Wert steht auch dann da, wenn `pct` `null` ist. Zusammen mit den Fehlern innerhalb von `pct` ergibt er alle Alarme, die im Zeitfenster nicht angekommen sind — aus welchem Grund auch immer. Der Ausschluss oben ändert, in welcher Zahl ein Fehler auftaucht, nie ob er auftaucht.
- **Was die beiden Alarm-Zahlen ausschließen — und was nicht.** Außen vor bleiben Incidents, für die nie eine Benachrichtigung geschuldet war: eine durch ein Wartungsfenster unterdrückte Alarmierung, ein Monitor ohne konfigurierten Benachrichtigungskanal, ein durch die Flapping-Dämpfung zurückgehaltener Incident oder einer, der sich erledigt hat, bevor eine Benachrichtigung fällig wurde. Eine Zustellung, bei der **jeder** Versuch auf unserer Seite fehlgeschlagen ist, steht in `alertDelivery.pct` und geht zulasten der Quote — ein fehlgeschlagener Versand ist ein Zustellfehler, kein Ausschlussgrund. Eine, die an deinem Ziel gescheitert ist, steht nach der Regel oben stattdessen in `endpointFailures`. Im p90 fehlt sie, denn ein Alarm, der nie hinausging, hat keine Versandzeit; ihm eine zuzuschreiben, würde einen Zustellfehler in einer Laufzeit verstecken. `alertDelivery.latencySampleSize` ist deshalb die Anzahl der erfolgreich benachrichtigten Incidents und stets kleiner als `sampleSize`.
- **Keine Alarm-Zahl reicht hinter den Beginn der Messung zurück.** `alertDelivery.pct`, `endpointFailures` und `latencyP90Ms` sind nach unten auf den Moment begrenzt, in dem unser Recorder den ersten Messwert geschrieben hat — genau den Zeitpunkt, den die öffentliche Statusseite als „Messung seit" ausweist. Unser Benachrichtigungsprotokoll enthält echte Historie von lange davor; sie zählt nicht mit, denn eine Zahl, die hinter das daneben gedruckte Startdatum zurückreicht, wäre eine andere Aussage als die, die dieses Datum trägt. Die Datensätze bleiben in der Datenbank — sie liegen nur außerhalb des Fensters.
- **Versuche zählen bis 24 Stunden nach dem Tag des Incidents.** Eine Zustellung, die einen vollen Tag nach Eröffnung des Incidents noch nicht durch ist, ist eine fehlgeschlagene Zustellung — unabhängig davon, was danach passiert. Über echte 90 Tage gemessen verschiebt diese Grenze 2 von 2590 Zustell-Paaren, und zwar zu unseren Lasten.
- **Eine Laufzeit ist ein Perzentil und wird deshalb weder summiert noch eingefroren.** Der veröffentlichte p90 wird über die Verteilung des Fensters selbst berechnet, nicht aus den Tageswerten dahinter abgeleitet: Die Summe täglicher Perzentile ist kein Perzentil, ihr gewichteter Mittelwert ebenso wenig. Aus demselben Grund taucht in `frozenMonths` keine Zeile `alert_dispatch_latency_p90` auf — ein Monatswert, der sich nicht durch Summieren von Tagen bilden lässt, fehlt besser, als dass er erfunden wird. Die Tageswerte stehen in unserer Belegtabelle, jeder mit seiner eigenen Stichprobengröße.
- **Eine Quote erscheint nie ohne ihre Stichprobengröße — und gar nicht, wenn diese zu klein ist.** `alertDelivery.pct` und `alertDelivery.latencyP90Ms` sind `null`, und das jeweilige Flag `insufficientData` bzw. `latencyInsufficientData` steht auf `true`, solange weniger als **20** Ereignisse dahinterstehen. `checkDelivery.pct` ist `null` mit `checkDelivery.insufficientData` auf `true`, bis unsere Messreihe **24 Stunden** abdeckt: Was eine Minute schuldet, ist eine Rate pro Minute, was sie liefert, eine ganze Prüfung — auf kurzer Strecke liegt der Wert dadurch knapp unter 100 %, ohne dass irgendetwas nicht stimmt. Stichprobengröße und `coveragePct` veröffentlichen wir in jedem dieser Fälle weiter; sie sind die Begründung für die fehlende Zahl. Ein `null` bei Flag `false` bedeutet etwas anderes: Es wurde noch überhaupt nichts gemessen.
- `components[].state` ist einer von `operational`, `degraded`, `down`, `unknown`. `unknown` heißt: Für diese Komponente liegt kein aktueller Sample vor — der Endpunkt füllt diese Lücke nie mit dem zuletzt bekannten Status auf. Eine Komponente auf `unknown` ist keine ausgefallene Komponente; wie lange das im Fenster schon gilt, zeigt ihr `coveragePct`.
- `frozenMonths` listet jeden append-only Monatswert aus `platform_sli_month`, identifiziert über `month` + `sliKind` + `scopeKey`. `sliKind` ist einer von `check_delivery`, `component_availability`, `alert_delivery`, `alert_endpoint_failure` oder `measurement_coverage` — das Laufzeit-Perzentil gehört bewusst nicht dazu (siehe oben). Der `rowHash` jeder Zeile ist an die vorherige Zeile mit demselben `sliKind`/`scopeKey` verkettet, und ein Datenbank-Trigger weist jedes UPDATE oder DELETE an der Tabelle zurück. Wenn du eine frühere Kopie dieser Antwort gespeichert hast: Ein `rowHash`, der für ein bestimmtes `month`/`sliKind`/`scopeKey` nicht mehr übereinstimmt, bedeutet, dass die Werte dieses Monats nach dem Einfrieren nachträglich verändert wurden.

## Häufige Fehler

- `500 Unexpected server error` bei einem internen Fehler

