Documentation

Quickstart

Paging incident history, and reading uptime without dropping its denominator. Both are written out in full because both are places a caller gets a plausible wrong answer rather than an error.

Walking the history

limit stops at 200 and one venue having a bad week exceeds that on its own, so the whole history is a walk. The cursor is before — a timestamp, not an offset, because new incidents are inserted at the front of the ordering and an offset would skip a row every time one opened mid-walk.

import time, httpx

BASE = "https://api.up24.app"

def incidents(venue: str, days: int = 90):
    """Every incident for one venue, walking the cursor rather than raising limit."""
    before = before_id = None
    with httpx.Client(base_url=BASE, timeout=10) as http:
        while True:
            params = {"venue": venue, "days": days, "limit": 200}
            if before:
                params["before"], params["beforeId"] = before, before_id
            r = http.get("/v1/incidents", params=params)

            # The one status worth handling: the API says when to come back.
            if r.status_code == 429:
                time.sleep(int(r.headers.get("retry-after", "5")))
                continue
            r.raise_for_status()

            page = r.json()["incidents"]
            if not page:
                return
            yield from page
            # A short page is the end. The cursor is the last row's start and
            # id, never an offset: new incidents are inserted at the front, and
            # incidents opened in one tick share a start.
            if len(page) < 200:
                return
            before, before_id = page[-1]["startedAt"], page[-1]["id"]

for incident in incidents("binance"):
    print(incident["slug"], incident["severity"], incident["durationMs"])

The one status worth handling is 429: the API says when to come back in retry-after, and that header is the whole of the backoff you need. Nothing else here is retryable in a way you can guess at.

Quoting uptime honestly

Every number here is a measurement with a denominator attached. “99.98% uptime” without the check count or the window is not a claim up24 makes anywhere on this site, and anything named typical is a median of daily percentiles rather than the window's own — percentiles do not compose.

import httpx

BASE = "https://api.up24.app"

r = httpx.get(f"{BASE}/v1/reliability", params={"days": 90}, timeout=10)
if r.status_code == 429:
    raise SystemExit(f"rate limited, retry in {r.headers['retry-after']}s")
r.raise_for_status()

for v in r.json()["venues"]:
    s = v["summary"]
    # measuredDays beside days on purpose: a venue watched for eight days of
    # ninety has not been 99.99% for a quarter.
    print(
        f"{v['name']}: {s['uptimePct']:.2f}% of {s['checks']} checks "
        f"over {s['measuredDays']}/{s['days']} days, typical p95 {s['typicalP95Ms']}ms"
    )

measuredDays is printed beside days on purpose: a venue watched for eight days of ninety has not been 99.99% for a quarter, and a dashboard that drops the first number is publishing a claim the data does not support.