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.

const BASE = 'https://api.up24.app';

async function* incidents(venue, days = 90) {
  let cursor;
  for (;;) {
    const url = new URL(`${BASE}/v1/incidents`);
    url.search = new URLSearchParams({ venue, days, limit: 200, ...cursor });

    const res = await fetch(url);
    // The one status worth handling: the API says when to come back.
    if (res.status === 429) {
      const wait = Number(res.headers.get('retry-after') ?? 5);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    if (!res.ok) throw new Error(`up24 ${res.status}`);

    const { incidents: page } = await res.json();
    if (page.length === 0) return;
    yield* 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 (page.length < 200) return;
    const last = page.at(-1);
    cursor = { before: last.startedAt, beforeId: last.id };
  }
}

for await (const incident of incidents('binance')) {
  console.log(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.

const BASE = 'https://api.up24.app';

// Uptime with its denominator, which is the only way up24 quotes one.
const res = await fetch(`${BASE}/v1/reliability?days=90`);
if (res.status === 429) {
  const wait = Number(res.headers.get('retry-after') ?? 5);
  throw new Error(`rate limited, retry in ${wait}s`);
}
const { venues } = await res.json();

for (const v of venues) {
  const { uptimePct, checks, measuredDays, days, typicalP95Ms } = v.summary;
  // measuredDays beside days on purpose: a venue watched for eight days of
  // ninety has not been 99.99% for a quarter.
  console.log(
    `${v.name}: ${uptimePct?.toFixed(2)}% of ${checks} checks ` +
      `over ${measuredDays}/${days} days, typical p95 ${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.