---
title: "Per-day uptime grid for a monitor"
method: GET
path: "/api/v1/monitors/{monitorID}/uptime"
tags: ["Monitors"]
---

# Per-day uptime grid for a monitor

`GET /api/v1/monitors/{monitorID}/uptime`

## Request

- Base URL: `https://api.uptimemonitoring.com`
- URL: `https://api.uptimemonitoring.com/api/v1/monitors/{monitorID}/uptime`
- Auth: HTTP bearer

Returns a 30-element array describing the monitor's uptime over
the last 30 UTC days, ordered today-first. Each entry is computed
from `monitor_uptime_day` (authoritative scheduled count per day)
and from `down`-typed incidents that overlap that day.

`pct` is `null` when `scheduled = 0` for that day — the dispatcher
never tried to check the monitor (account suspended, kill switch
on, monitor disabled, dispatcher down, monitor freshly created).
The dashboard renders this as the grey/no-data state and must
treat null as "no information", NOT as "0%". A `pct` value of `0`
means the monitor was checked and was down for every second of
the day.

`pct` is normally derived purely from down-incident overlap, but
is reconciled against `monitor_uptime_day.succeeded`: a day with
zero successful checks is floored to `0` when corroborated as
down — an unresolved down incident overlaps the day (any day),
or the monitor's current resolved status is `down` and its
current outage (the last down-transition) began before that day
ended (only when the monitor is enabled and not currently
flapping) — even if the `incidents` table has no row covering
that day (a dropped runtime transition write). A currently-down
monitor is floored across every day of its continuous outage,
but never a day that predates the current down-period: the
down-since timestamp bounds which days the live status describes.
It is never floored when the monitor's resolved status is `up`,
so a day where the primary region failed every check but
cross-region confirmation kept the monitor up still reads its
incident-derived percentage.

`flapping` incidents are intentionally excluded from the downtime
math — uptime semantics treat a flapping monitor as "answering,
just bouncing".

Each entry also carries a `latency` daily candle — open, high,
low, close, and average total response time in whole
milliseconds, built only from successful checks against the
monitor's sticky primary region (wopher's `total_ms`: the full
request round-trip, DNS through last byte of download). `latency`
is `null` when the
day has no successful samples to build a candle from — this
includes days predating the feature, and is a distinct null
condition from `pct` (which keys off `scheduled = 0`): a day can
have a non-null `pct` (fully scheduled, down the entire day) and
still carry a null `latency`. `avg` is the arithmetic mean of
the day's successful-check response times. Latency history is
free-tier for every account — no plan gating.

v1 supports `window=30d` only. The 30-day cap is a visibility and
API limit, independent of the 95-day physical retention window for
incidents and `monitor_uptime_day` (see internal/retention/
DECISIONS.md). Wider or narrower windows are out of scope for v1.

## Path parameters

- `monitorID` integer, required

## Query parameters

- `window` '30d'

## Response `200`

30-element array of per-day uptime entries, today-first.

- UptimeDay[]
  - `day` string, date, required: UTC calendar day (YYYY-MM-DD).
  - `scheduled` integer, required: Number of checks scheduled for this monitor on this day, sourced from `monitor_uptime_day.scheduled`. `0` means the dispatcher never tried to check this monitor (suspended, disabled, kill switch, dispatcher outage, freshly created).
  - `pct` number, float, nullable, required: Uptime percentage for the day, derived from down-incident overlap. `null` when `scheduled = 0`. Floored to `0` when the day had zero successful checks and the monitor is corroborated down by either signal, both netted against overlapping flapping-incident windows so a flapping monitor's masked-downtime treatment is never overridden: an unresolved down incident whose net (post flap-mask) overlap with the day is greater than zero (any day), or the monitor's current resolved status being `down` — only when the monitor is enabled, not currently flapping, AND the current status began before the day ended, i.e. the day falls inside the monitor's current continuous down-period. This reconciliation catches days where a runtime incident write was dropped and no `incidents` row covers the outage — including every day of a multi-week outage, not just the most recent one. A day that predates the current down-period (status flipped down after that day already ended) is never floored by current status alone. Never applied when the monitor's resolved status is `up`. Always present in the JSON object — never omitted.
  - `latency` object, nullable, required: The day's latency candle: open/high/low/close/avg total response time in whole milliseconds (wopher `total_ms`: the full request round-trip, DNS through last byte of download), built only from successful checks against the monitor's sticky primary region. All five values are rounded to the nearest whole millisecond server-side (half away from zero). `null` when the day has no successful samples to build a candle from — this includes days predating the feature. This is a distinct null condition from `pct`: a fully scheduled, all-down day has a non-null `pct` but a null `latency`. Always present in the JSON object — never omitted.
    - `open` number, float, required: First successful check's total response time of the day, in whole ms.
    - `high` number, float, required: Highest total response time among the day's successful checks, in whole ms.
    - `low` number, float, required: Lowest total response time among the day's successful checks, in whole ms.
    - `close` number, float, required: Most recent successful check's total response time of the day, in whole ms.
    - `avg` number, float, required: Arithmetic mean of the day's successful-check total response times (`latency_sum_ms / latency_count`, computed server-side; the underlying sum/count are not exposed on the wire), in whole ms.

## Other responses

- `400`: Invalid request.
- `401`: Missing or invalid credentials.
- `403`: Account is currently suspended. On `POST /api/v1/monitors`, and on a `PUT /api/v1/monitors/{monitorID}` that changes `url` or `type` (issue #335), this can also be returned by the per-account port-scan heuristic when the triggering request crossed the port-fan threshold inside the sliding window (10 minutes by default; `SCAN_WINDOW`). A host-fan trip does NOT suspend the account — see the 429 response instead.
- `404`: Monitor not found or belongs to another account.
- `429`: Per-account dispatcher rate limit exceeded (200 requests/minute). The `Retry-After` header gives the integer seconds until at least one token has refilled. The `X-RateLimit-*` trio reflects the bucket whose rejection produced this response — usually the global 200/min cap; on `POST /api/v1/monitors` a 429 may instead reflect the per-route 20/min creation limit.

---

[API](https://skmtc.dev/uptimemonitoring/apis/uptimemonitoring-com-api.md) · [All operations](https://skmtc.dev/uptimemonitoring/apis/uptimemonitoring-com-api/llms.txt) · [OpenAPI document](https://skmtc.dev/uptimemonitoring/apis/uptimemonitoring-com-api/revisions/ddcb2a8bc70f?raw)
