Skip to content

Build reports and summaries

View as Markdown

This page is for turning monitoring data into an answer: “how did we do this week”, “what went down yesterday”, “did we meet the SLA”, “is the page getting slower”. It lists the calls that answer each question, the limits on each, and four recipes you can run by hand, from a script or as an AI assistant. All calls need the monitor:read scope unless noted; all times are Unix seconds.

For how the figures are calculated, see Uptime percentage and SLA and Reading a monitor’s results and incidents.

Question API MCP Notes
Uptime %, downtime and SLA of chosen monitors over a window GET /monitor/result/summary?monitor=ID1,ID2&from=F&to=T get_uptime_summary One row per monitor, ordered by monitor id - there is no sort (see Sort a summary). monitor= is required here (up to 500 ids).
One uptime figure for the whole account GET /monitor/result/summary?groupBy=account&from=F&to=T get_uptime_summary with monitor="" and groupBy="account" (the tool requires monitor; empty means the whole account) One row, summed over every monitor (or over monitor= if you send it). Window up to 30 days, up to 2,000 monitors.
Uptime of every monitor, as a list GET /monitor?expand=uptime&from=F&to=T - Adds uptime (percent, null when there is no data) to each monitor row. Page through with limit up to 500.
A daily, weekly or monthly series add bucket=hour|day|week|month to the summary get_uptime_summary with bucket One row per monitor per bucket. Buckets are aligned to UTC; weeks start on Monday.
SLA met or missed, error budget left add sla=99.9 to the summary, or set the monitor’s slaTarget get_uptime_summary with sla Adds slaTarget, slaMet, errorBudgetSecRemaining.
Response time and its phases add metrics=responseTime,dns,connect,tls,ttfb,transfer to the summary get_uptime_summary with metrics Each point is {t, value, p95, samples}, in milliseconds.
How many outages opened and closed add expand=incidentCounts to the summary api_request with GET /monitor/result/summary and expand=incidentCounts (get_uptime_summary has no expand) Adds incidents{opened, restored} per row: episodes that opened / closed inside the window or bucket.
The outages themselves, with cause and duration GET /monitor/incident?from=F&to=T (all monitors) or GET /monitor/{id}/incident list_incidents Any incident that overlaps the window, newest first. No window cap.
One outage in detail GET /monitor/incident/{id} (expand=recheck) get_incident Adds the transitions that opened and closed it and which locations confirmed it.
The failing checks inside one outage GET /monitor/incident/{id}/check - Plan feature (full event log); otherwise 403 package_limit.
A monitor’s up/down timeline GET /monitor/{id}/span?from=F&to=T get_monitor with expand="spans" Every continuous up or down period; down spans name their incident.
The check log (raw results) GET /monitor/{id}/result?from=F&to=T list_monitor_results Newest first; expand=metrics adds measurements, expand=recheck what each location saw.
Raw results across monitors GET /monitor/result?monitor=ID1,ID2&from=F&to=T - Without monitor= the window is capped at 24 hours.
What is down right now GET /monitor?state=down&expand=lastIncident list_monitors with state="down"
Who was alerted, and when GET /contact/notification - Scope contact:read.
Planned work during the window GET /maintenance?from=F&to=T list_maintenance
A formatted document to send someone POST /monitor/report (a job) generate_report PDF, CSV, XML or HTML. Scope monitor:write.
  • Default window. On every windowed read, an omitted to is now and an omitted from is 30 days before to. Send both to be explicit.
  • Raw results: 30 days per request. A wider from/to on GET /monitor/{id}/result or GET /monitor/result is refused with 422 invalid_range; read longer periods one window at a time. Without monitor=, the account-wide GET /monitor/result accepts at most 24 hours and defaults to the last 24 hours.
  • Raw results are grouped. Consecutive identical results are stored as one row with a checkCount, so a row is not always one check. Uptime and incident figures are computed from state changes and are exact regardless.
  • Summary limits. At most 1,000 buckets per request and at most 50,000 (monitors x buckets) cells; a request over either is refused (422), never silently truncated - narrow the window or the monitor set, or widen the bucket. groupBy=account covers at most 30 days and 2,000 monitors.
  • Incidents are not clipped. An incident that started before from or ended after to is returned whole, with its full start, end and durationSec. To count downtime inside the window, use the summary’s downSec (which is clipped) or clip each incident yourself: min(end, to) - max(start, from), with end = now for an open incident.
  • No server-side ranking. The summary has no sort; rank rows yourself - see Sort a summary, worst first.
  • Paging. Lists return data, nextCursor and hasMore. Keep calling with cursor=<nextCursor> while hasMore is true. The API takes limit 1-500 (default 50); the MCP tools take 1-50 (default 20). A cursor only works with the same query that produced it. For long id lists, send the query as a JSON body to POST <path>/q.
  • Generated reports. Up to 500 monitors per report. Defaults: format pdf, the last 30 days, sections stats. The report id stays downloadable for 7 days.

GET /monitor/result/summary returns one row per monitor (per bucket):

Member Meaning
monitorId, from, to Which monitor and which window or bucket. Absent monitorId on an account roll-up row.
upSec, downSec Seconds up and down inside the window, maintenance included.
totalSec Seconds with a known state (upSec + downSec). Shorter than the window when the monitor did not exist yet or was paused.
maintenance.upSec, maintenance.downSec The parts that fell inside maintenance windows. maintenance.downSec is the excused downtime.
checks total, up, down, maintenanceUp, maintenanceDown - checks recorded.
downSpans Down periods that started in the window.
uptimePercent The result: upSec / (totalSec - maintenance.downSec), null when nothing was measured. Quote it as given; on the whole-window figure it comes from the stored daily statistics the rest of the product shows.
slaTarget, slaMet, errorBudgetSecRemaining When a target applies. A negative budget means the target was missed by that many seconds.
incidents With expand=incidentCounts: opened, restored.
metrics With metrics=: one series per metric, points {t, value, p95, samples}.
monitors, scope, sampled Account roll-up only: how many monitors, account or selection, and whether the timing figures were sampled.

An incident row carries id, monitorId, start, end, durationSec, state (open, resolved), severity (minor under 5 minutes, major 5 to 60 minutes, critical 1 hour or more), cause (the error, with a codename), underMaintenance, comment and checkCount. For an open incident, end is the last moment the monitor was seen down.

GET /monitor/result/summary has no sort parameter, and neither does get_uptime_summary. It returns rows ordered by monitor id and then by bucket start - a stable order for paging, not a ranking. (GET /monitor takes sort= over name, state, type, interval, lastChange, url, tags or created, but not over uptime.) To rank monitors, fetch every row and sort on your side:

  1. Collect the ids. GET /monitor?limit=500 (follow nextCursor), or MCP list_monitors (50 per page).
  2. Fetch the rows. GET /monitor/result/summary?monitor=ID1,...&from=F&to=T in batches of up to 500 ids (add expand=incidentCounts for outage counts). Follow nextCursor until hasMore is false - a page holds at most 500 rows through the API, 50 through MCP. Keep the window and ids identical while paging; a cursor from a different query is refused.
  3. Sort. Ascending by uptimePercent puts the worst first. Tie-break by downSec descending, then by incidents.opened descending. Rows without uptimePercent (nothing was measured in the window - a paused or brand-new monitor) have no figure at all: put them last and label them “no data”, never treat them as 0 %.
  4. Report uptimePercent exactly as returned; do not recompute it from the seconds.

For “worst by outages” instead, sort by incidents.opened descending. The same client-side sort applies to GET /monitor?expand=uptime rows (by uptime, null = no data).

Recipe 1: a weekly uptime summary for all monitors

Section titled “Recipe 1: a weekly uptime summary for all monitors”

Goal: last week’s uptime per monitor, the account-wide figure, and the outages, for a weekly status email.

  1. Pick the window in the user’s time zone. Read the zone from GET /account (timezone). Last week = Monday 00:00 to the next Monday 00:00 in that zone, converted to Unix seconds: F and T.

  2. Per-monitor uptime:

    GET /monitor?expand=uptime&from=F&to=T&limit=500

    Follow nextCursor until hasMore is false. Each row carries id, name, url, state and uptime.

  3. The account-wide figure and outage counts:

    GET /monitor/result/summary?groupBy=account&from=F&to=T&expand=incidentCounts

    One row: uptimePercent, downSec, monitors, incidents.opened.

  4. The outages:

    GET /monitor/incident?from=F&to=T&sort=monitor&expand=monitor&limit=500

    sort=monitor groups them by monitor name; expand=monitor adds each monitor’s name and url.

  5. Write it up. Lead with the account figure, then the monitors below their SLA or below 100%, lowest first, then one line per outage (monitor, start in the user’s zone, duration, cause). Say which outages fell inside maintenance (underMaintenance), and that maintenance downtime is not counted against uptime.

With MCP: get_uptime_summary(monitor="", groupBy="account", from=F, to=T) for the account figure (the tool requires monitor; an empty value with groupBy="account" means the whole account); get_uptime_summary(monitor="ID1,ID2,...", from=F, to=T) in batches of monitor ids from list_monitors for per-monitor rows, sorted by you (see Sort a summary); list_incidents(from=F, to=T) for the outages. For outage counts per monitor use api_request(method="GET", path="/monitor/result/summary", query="monitor=ID1,ID2&from=F&to=T&expand=incidentCounts").

Recipe 2: what went down yesterday, and for how long

Section titled “Recipe 2: what went down yesterday, and for how long”
  1. Window: yesterday 00:00 to today 00:00 in the user’s zone, as Unix seconds F, T.

  2. List the incidents that touched that day:

    GET /monitor/incident?from=F&to=T&expand=monitor&limit=500

    MCP: list_incidents(from=F, to=T).

  3. For each incident report the monitor, start and end in the user’s zone, durationSec in human units (for example “12 min 30 s”), severity and cause.codename. Mark state: "open" ones as still down. An incident that began the day before or ended today still appears whole; say so, and clip its duration to yesterday if the user asked “how long yesterday”:

    downInWindow = min(end, T) - max(start, F)

    using end = now for an open incident (its end is only the last moment it was seen down). No field carries the clipped value. For a per-monitor total that is already clipped to the window, read downSec from GET /monitor/result/summary?monitor=ID&from=F&to=T (downtime inside maintenance windows included; maintenance.downSec is that part).

  4. Explain the long ones: GET /monitor/incident/{id}?expand=recheck (MCP get_incident) shows which locations confirmed the outage and the error each saw.

  5. If the user wants a record, add their note with POST /monitor/incident/{id}/comment {"comment": "Database failover"} (MCP comment_incident, scope monitor:write). A new comment replaces the previous one.

Only confirmed outages are incidents. A single failed check that the recheck did not confirm is not one - see How down detection works.

Recipe 3: a monthly SLA report for one monitor

Section titled “Recipe 3: a monthly SLA report for one monitor”
  1. Window: the first day of the month 00:00 to the first day of the next month 00:00, in the user’s zone.

  2. The SLA figures:

    GET /monitor/result/summary?monitor=ID&from=F&to=T&sla=99.9&expand=incidentCounts

    Read uptimePercent, slaMet, errorBudgetSecRemaining, downSec, maintenance.downSec and incidents.opened. Leave out sla= to measure against the monitor’s own slaTarget. MCP: get_uptime_summary(monitor="ID", from=F, to=T, sla=99.9).

  3. A day-by-day breakdown (UTC days): the same call with bucket=day.

  4. The outages behind the number: GET /monitor/ID/incident?from=F&to=T.

  5. A document to send:

    POST /monitor/report
    Idempotency-Key: 6f1c1d2e-monthly-sla-2026-09
    {
    "monitorIds": ["ID"],
    "from": F,
    "to": T,
    "format": "pdf",
    "sections": ["stats", "outages", "incidents"],
    "timezone": "Europe/Berlin"
    }

    The answer is 202 with a jobId and a Retry-After. Poll GET /job/{jobId} until the state is succeeded; the finished job names the report and its download url. GET /monitor/report/{id} describes it and GET /monitor/report/{id}/content returns the file. Sections: state, stats, outages, incidents, log. MCP: generate_report(monitorIds="ID", from=F, to=T, format="pdf", sections="stats,outages,incidents"), then wait_for_job; hand the user the download url rather than trying to read the file.

For a report that arrives on its own every month, subscribe an email contact instead: PUT /monitor/ID/report/CONTACT_ID {"frequencies": ["monthly"]} (MCP subscribe_contact with frequencies="monthly"). See Schedule report subscriptions.

Recipe 4: the response-time trend of a page

Section titled “Recipe 4: the response-time trend of a page”
  1. Pick the monitor that checks the page - a Website/HTTPS or API monitor records every timing phase.

  2. Daily averages and the slow tail over the last 30 days:

    GET /monitor/result/summary?monitor=ID&from=F&to=T&bucket=day&metrics=responseTime,ttfb

    Each row’s metrics.responseTime is a list of points: t (the bucket’s midpoint), value (mean, milliseconds), p95 (95th percentile) and samples (how many checks). MCP: get_uptime_summary(monitor="ID", from=F, to=T, bucket="day", metrics="responseTime,ttfb").

  3. Choose the bucket for the window: hour for a few days (the 1,000-bucket limit allows about 41 days), day for weeks to months, week or month for longer.

  4. Read it carefully: compare p95 with value - a rising p95 with a flat mean means occasional slow checks. A bucket with few samples is weak evidence. A null value means nothing was measured in that bucket.

  5. Find the slow phase: metrics=dns,connect,tls,ttfb,transfer splits the time into DNS lookup, connection, TLS handshake, waiting for the first byte and download.

  6. Drill into one slow check: GET /monitor/ID/result?from=...&to=...&expand=metrics (MCP list_monitor_results with expand="metrics").

Other monitor types record fewer phases or none; their series come back empty (samples: 0). For a full page load in a real browser, use a Page speed monitor and read its per-check results.

  • Quote uptimePercent exactly as returned; do not recompute it from the seconds.
  • Show times in the user’s time zone (GET /account -> timezone) and durations in human units.
  • Say which window each figure covers - daily, weekly and monthly uptime are different numbers.
  • Mention maintenance: downtime inside a window that suppresses statistics is excused, and incidents that began in one are flagged underMaintenance.
  • For “why”, link the user to the monitor’s statistics page (/sites/stats/{id}) or the Uptime reports page in the app.