Build reports and summaries
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.
Which call answers which question
Section titled “Which call answers which question”| 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. |
Limits to plan around
Section titled “Limits to plan around”- Default window. On every windowed read, an omitted
tois now and an omittedfromis 30 days beforeto. Send both to be explicit. - Raw results: 30 days per request. A wider
from/toonGET /monitor/{id}/resultorGET /monitor/resultis refused with422 invalid_range; read longer periods one window at a time. Withoutmonitor=, the account-wideGET /monitor/resultaccepts 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=accountcovers at most 30 days and 2,000 monitors. - Incidents are not clipped. An incident that started before
fromor ended aftertois returned whole, with its fullstart,endanddurationSec. To count downtime inside the window, use the summary’sdownSec(which is clipped) or clip each incident yourself:min(end, to) - max(start, from), withend= 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,nextCursorandhasMore. Keep calling withcursor=<nextCursor>whilehasMoreis true. The API takeslimit1-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 toPOST <path>/q. - Generated reports. Up to 500 monitors per report. Defaults: format
pdf, the last 30 days, sectionsstats. The report id stays downloadable for 7 days.
Reading a summary row
Section titled “Reading a summary row”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.
Sort a summary, worst first
Section titled “Sort a summary, worst first”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:
- Collect the ids.
GET /monitor?limit=500(follownextCursor), or MCPlist_monitors(50 per page). - Fetch the rows.
GET /monitor/result/summary?monitor=ID1,...&from=F&to=Tin batches of up to 500 ids (addexpand=incidentCountsfor outage counts). FollownextCursoruntilhasMoreis 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. - Sort. Ascending by
uptimePercentputs the worst first. Tie-break bydownSecdescending, then byincidents.openeddescending. Rows withoutuptimePercent(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 %. - Report
uptimePercentexactly 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.
-
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:FandT. -
Per-monitor uptime:
GET /monitor?expand=uptime&from=F&to=T&limit=500Follow
nextCursoruntilhasMoreis false. Each row carriesid,name,url,stateanduptime. -
The account-wide figure and outage counts:
GET /monitor/result/summary?groupBy=account&from=F&to=T&expand=incidentCountsOne row:
uptimePercent,downSec,monitors,incidents.opened. -
The outages:
GET /monitor/incident?from=F&to=T&sort=monitor&expand=monitor&limit=500sort=monitorgroups them by monitor name;expand=monitoradds each monitor’s name and url. -
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”-
Window: yesterday 00:00 to today 00:00 in the user’s zone, as Unix seconds
F,T. -
List the incidents that touched that day:
GET /monitor/incident?from=F&to=T&expand=monitor&limit=500MCP:
list_incidents(from=F, to=T). -
For each incident report the monitor,
startandendin the user’s zone,durationSecin human units (for example “12 min 30 s”),severityandcause.codename. Markstate: "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 (itsendis 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, readdownSecfromGET /monitor/result/summary?monitor=ID&from=F&to=T(downtime inside maintenance windows included;maintenance.downSecis that part). -
Explain the long ones:
GET /monitor/incident/{id}?expand=recheck(MCPget_incident) shows which locations confirmed the outage and the error each saw. -
If the user wants a record, add their note with
POST /monitor/incident/{id}/comment{"comment": "Database failover"}(MCPcomment_incident, scopemonitor: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”-
Window: the first day of the month 00:00 to the first day of the next month 00:00, in the user’s zone.
-
The SLA figures:
GET /monitor/result/summary?monitor=ID&from=F&to=T&sla=99.9&expand=incidentCountsRead
uptimePercent,slaMet,errorBudgetSecRemaining,downSec,maintenance.downSecandincidents.opened. Leave outsla=to measure against the monitor’s ownslaTarget. MCP:get_uptime_summary(monitor="ID", from=F, to=T, sla=99.9). -
A day-by-day breakdown (UTC days): the same call with
bucket=day. -
The outages behind the number:
GET /monitor/ID/incident?from=F&to=T. -
A document to send:
POST /monitor/reportIdempotency-Key: 6f1c1d2e-monthly-sla-2026-09{"monitorIds": ["ID"],"from": F,"to": T,"format": "pdf","sections": ["stats", "outages", "incidents"],"timezone": "Europe/Berlin"}The answer is
202with ajobIdand aRetry-After. PollGET /job/{jobId}until the state issucceeded; the finished job names the report and its download url.GET /monitor/report/{id}describes it andGET /monitor/report/{id}/contentreturns the file. Sections:state,stats,outages,incidents,log. MCP:generate_report(monitorIds="ID", from=F, to=T, format="pdf", sections="stats,outages,incidents"), thenwait_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”-
Pick the monitor that checks the page - a Website/HTTPS or API monitor records every timing phase.
-
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,ttfbEach row’s
metrics.responseTimeis a list of points:t(the bucket’s midpoint),value(mean, milliseconds),p95(95th percentile) andsamples(how many checks). MCP:get_uptime_summary(monitor="ID", from=F, to=T, bucket="day", metrics="responseTime,ttfb"). -
Choose the bucket for the window:
hourfor a few days (the 1,000-bucket limit allows about 41 days),dayfor weeks to months,weekormonthfor longer. -
Read it carefully: compare
p95withvalue- a rising p95 with a flat mean means occasional slow checks. A bucket with fewsamplesis weak evidence. Anullvalue means nothing was measured in that bucket. -
Find the slow phase:
metrics=dns,connect,tls,ttfb,transfersplits the time into DNS lookup, connection, TLS handshake, waiting for the first byte and download. -
Drill into one slow check:
GET /monitor/ID/result?from=...&to=...&expand=metrics(MCPlist_monitor_resultswithexpand="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.
Presenting the numbers
Section titled “Presenting the numbers”- Quote
uptimePercentexactly 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.

