SLA Reports
Read the account’s SLA reports: the current month’s headline for every report, or one month in full with per-check numbers, incidents and exclusions. These endpoints are read-only. Reports are created and edited in the app.
Required scope: checks:read
Pro feature. If the account is not on Pro, both endpoints return
403with"error": "feature_not_in_plan".
The API never recomputes a report. It returns the numbers from the latest refresh, which runs 4 times a day. A month that has never been computed returns 404; open it once in the app to compute it.
Timestamps are UTC ISO 8601 strings. Each report also returns its timezone, the IANA zone its months are counted in. Durations are in milliseconds. availability, coverage and budgetUsed are fractions: 0.9995 means 99.95%.
Restricted keys. A key restricted to checks or folders only sees reports whose checks all fall inside its grant. Other reports are left out of the list and return 404.
List SLA Reports
GET /v1/public/sla-reportsReturns every report with this month’s headline, sorted by name.
200 OK
{
"data": [
{
"id": "Xk2fP9qLr7Tz",
"name": "Acme Corp hosting",
"targetPercent": 99.9,
"timezone": "Europe/Copenhagen",
"checkCount": 2,
"createdAt": "2026-09-14T10:22:05.000Z",
"current": {
"period": "2026-11",
"availability": 0.99962,
"status": "on_track",
"budgetUsed": 0.38,
"coverage": 0.9991,
"dataThrough": "2026-11-08T08:10:00.000Z"
}
}
],
"meta": {
"total": 1
}
}List Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Report ID |
name | string | Report name, usually the client or contract |
targetPercent | number | Availability target, for example 99.9 |
timezone | string | IANA time zone the months are counted in |
checkCount | number | null | Checks in this month’s numbers, or null if the month has not been computed |
createdAt | string | When the report was created |
current.period | string | The current month in the report’s zone, YYYY-MM |
current.availability | number | null | Month-to-date availability, or null with no measured time |
current.status | string | null | on_track, at_risk, breached, met or no_data |
current.budgetUsed | number | null | Share of the error budget used. Can exceed 1 |
current.coverage | number | null | Measured time divided by the time in the period so far |
current.dataThrough | string | null | The numbers are complete up to this moment |
The current fields are null if the current month has not been computed yet.
Get an SLA Report
GET /v1/public/sla-reports/{id}Returns one month of one report.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Report ID from the list |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
period | string | current month | Month as YYYY-MM, in the report’s time zone |
200 OK
October 2026 in Copenhagen, a month with a daylight saving change. It starts at 22:00 UTC (CEST) and ends at 23:00 UTC (CET), so it is 31 days and 1 hour long.
{
"data": {
"reportId": "Xk2fP9qLr7Tz",
"name": "Acme Corp hosting",
"period": "2026-10",
"timezone": "Europe/Copenhagen",
"status": "final",
"periodStart": "2026-09-30T22:00:00.000Z",
"periodEnd": "2026-10-31T23:00:00.000Z",
"dataThrough": "2026-11-01T02:10:00.000Z",
"computedAt": "2026-11-01T02:14:31.000Z",
"targetPercent": 99.9,
"countBlockedAsDown": false,
"overall": {
"upMs": 5361420000,
"downMs": 780000,
"maintenanceMs": 1800000,
"blockedMs": 0,
"noDataMs": 0,
"availability": 0.999854537,
"coverage": 0.999664430,
"budgetMs": 5362200,
"budgetUsed": 0.145462683,
"projectedBudgetMs": 5362200,
"status": "met",
"insufficientData": false
},
"daily": [
{ "day": "2026-10-14", "upMs": 172020000, "downMs": 780000, "excludedMs": 0 }
],
"checks": [
{
"checkId": "abc123",
"name": "Acme API",
"url": "https://api.acme.com/health",
"upMs": 2679420000,
"downMs": 780000,
"maintenanceMs": 1800000,
"blockedMs": 0,
"noDataMs": 0,
"availability": 0.999708977,
"coverage": 0.999328859,
"incidents": 1,
"longestIncidentMs": 780000,
"status": "met",
"insufficientData": false
},
{
"checkId": "def456",
"name": "Acme website",
"url": "https://www.acme.com",
"upMs": 2682000000,
"downMs": 0,
"maintenanceMs": 0,
"blockedMs": 0,
"noDataMs": 0,
"availability": 1,
"coverage": 1,
"incidents": 0,
"longestIncidentMs": 0,
"status": "met",
"insufficientData": false
}
],
"incidents": [
{
"checkId": "abc123",
"start": "2026-10-14T01:12:00.000Z",
"end": "2026-10-14T01:25:00.000Z",
"durationMs": 780000,
"ongoing": false
}
],
"incidentsTotal": 1,
"exclusions": [
{
"checkId": "abc123",
"state": "maintenance",
"start": "2026-10-21T20:00:00.000Z",
"end": "2026-10-21T20:30:00.000Z",
"durationMs": 1800000
}
],
"exclusionsTotal": 1,
"sent": {
"sentAt": "2026-11-01T02:41:07.000Z",
"timezone": "Europe/Copenhagen",
"availability": 0.999854537,
"status": "met"
}
}
}daily has one entry per local day of the month, summed across the checks. One is shown here.
Response Fields
| Field | Type | Description |
|---|---|---|
reportId | string | Report ID |
name | string | Report name |
period | string | The month, YYYY-MM in the report’s zone |
timezone | string | IANA zone the month was computed in |
status | string | open while the month is in progress or not yet final, final once its data is complete |
periodStart | string | Local midnight on the 1st, in UTC |
periodEnd | string | Local midnight on the 1st of the next month, in UTC |
dataThrough | string | The numbers are complete up to this moment |
computedAt | string | When the month was last computed |
targetPercent | number | Availability target the month was measured against |
countBlockedAsDown | boolean | Whether blocked probes counted as downtime |
overall | object | Numbers for the whole report, time-weighted across checks. See below |
daily | array | Per local day: day (YYYY-MM-DD), upMs, downMs and excludedMs |
checks | array | Per check: the same time totals plus availability, coverage, incidents, longestIncidentMs, status and insufficientData. A deleted check has "deleted": true |
incidents | array | Confirmed outages, longest first, up to 500: checkId, start, end (null while ongoing), durationMs and ongoing |
incidentsTotal | number | The true number of incidents, also when the list is capped |
exclusions | array | Excluded periods, longest first, up to 300: checkId, state (maintenance, blocked or no_data), start, end and durationMs |
exclusionsTotal | number | The true number of excluded periods |
sent | object | null | The monthly email for this month, if it was sent: sentAt, timezone, availability and status as sent |
The overall Object
| Field | Type | Description |
|---|---|---|
upMs | number | Up time |
downMs | number | Confirmed downtime. The only time that uses error budget |
maintenanceMs | number | Time in maintenance windows, excluded |
blockedMs | number | Time a firewall blocked the probe, excluded unless countBlockedAsDown is true |
noDataMs | number | Time with no results to go on, excluded |
availability | number | null | upMs / (upMs + downMs), or null with no measured time |
coverage | number | Measured time divided by the time in the period |
budgetMs | number | Downtime the target allows for the measured time so far |
budgetUsed | number | downMs / budgetMs. Can exceed 1 |
projectedBudgetMs | number | During the month, the budget for the whole month, projected. Equals budgetMs once final |
status | string | on_track, at_risk, breached, met or no_data |
insufficientData | boolean | true when less than half of the period was measured |
See How availability is calculated for the counting rules and Status for when each status applies.
Error Responses
| Status | Description |
|---|---|
| 400 | period is not YYYY-MM |
| 401 | Missing, invalid, disabled or expired API key |
| 403 | Credential missing checks:read scope, or the account is not on Pro (feature_not_in_plan) |
| 404 | Report not found, outside the key’s grant, or the month has not been computed yet |
| 429 | Rate limit exceeded |
The plan error looks like this:
{
"error": "feature_not_in_plan",
"feature": "slaReporting",
"message": "SLA reporting is a Pro feature"
}Code Examples
cURL
# All reports with this month's headline
curl -H "X-Api-Key: ek_live_your_key_here" \
"https://us-central1-exit1-dev.cloudfunctions.net/publicApi/v1/public/sla-reports"
# One report, October 2026
curl -H "X-Api-Key: ek_live_your_key_here" \
"https://us-central1-exit1-dev.cloudfunctions.net/publicApi/v1/public/sla-reports/Xk2fP9qLr7Tz?period=2026-10"JavaScript
const base = "https://us-central1-exit1-dev.cloudfunctions.net/publicApi/v1/public";
const headers = { "X-Api-Key": process.env.EXIT1_API_KEY };
const { data: reports } = await (await fetch(`${base}/sla-reports`, { headers })).json();
for (const r of reports) {
const pct = r.current.availability === null ? "no data" : `${(r.current.availability * 100).toFixed(3)}%`;
console.log(`${r.name}: ${pct} against ${r.targetPercent}% (${r.current.status ?? "not computed"})`);
}Python
import os, requests
base = "https://us-central1-exit1-dev.cloudfunctions.net/publicApi/v1/public"
headers = {"X-Api-Key": os.environ["EXIT1_API_KEY"]}
response = requests.get(f"{base}/sla-reports/Xk2fP9qLr7Tz", headers=headers, params={"period": "2026-10"})
response.raise_for_status()
report = response.json()["data"]
print(f"{report['name']} {report['period']}: {report['overall']['availability'] * 100:.3f}% ({report['overall']['status']})")
for incident in report["incidents"][:5]:
print(incident["start"], incident["durationMs"] // 60000, "min")MCP
The get_sla_report tool on the MCP server wraps these endpoints. Without a report ID it lists the reports. With one, it returns the month’s summary and the 20 longest incidents, with times in the report’s zone.