Skip to Content
API ReferenceSLA Reports

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 403 with "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-reports

Returns 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

FieldTypeDescription
idstringReport ID
namestringReport name, usually the client or contract
targetPercentnumberAvailability target, for example 99.9
timezonestringIANA time zone the months are counted in
checkCountnumber | nullChecks in this month’s numbers, or null if the month has not been computed
createdAtstringWhen the report was created
current.periodstringThe current month in the report’s zone, YYYY-MM
current.availabilitynumber | nullMonth-to-date availability, or null with no measured time
current.statusstring | nullon_track, at_risk, breached, met or no_data
current.budgetUsednumber | nullShare of the error budget used. Can exceed 1
current.coveragenumber | nullMeasured time divided by the time in the period so far
current.dataThroughstring | nullThe 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

ParameterTypeDescription
idstringReport ID from the list

Query Parameters

ParameterTypeDefaultDescription
periodstringcurrent monthMonth 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

FieldTypeDescription
reportIdstringReport ID
namestringReport name
periodstringThe month, YYYY-MM in the report’s zone
timezonestringIANA zone the month was computed in
statusstringopen while the month is in progress or not yet final, final once its data is complete
periodStartstringLocal midnight on the 1st, in UTC
periodEndstringLocal midnight on the 1st of the next month, in UTC
dataThroughstringThe numbers are complete up to this moment
computedAtstringWhen the month was last computed
targetPercentnumberAvailability target the month was measured against
countBlockedAsDownbooleanWhether blocked probes counted as downtime
overallobjectNumbers for the whole report, time-weighted across checks. See below
dailyarrayPer local day: day (YYYY-MM-DD), upMs, downMs and excludedMs
checksarrayPer check: the same time totals plus availability, coverage, incidents, longestIncidentMs, status and insufficientData. A deleted check has "deleted": true
incidentsarrayConfirmed outages, longest first, up to 500: checkId, start, end (null while ongoing), durationMs and ongoing
incidentsTotalnumberThe true number of incidents, also when the list is capped
exclusionsarrayExcluded periods, longest first, up to 300: checkId, state (maintenance, blocked or no_data), start, end and durationMs
exclusionsTotalnumberThe true number of excluded periods
sentobject | nullThe monthly email for this month, if it was sent: sentAt, timezone, availability and status as sent

The overall Object

FieldTypeDescription
upMsnumberUp time
downMsnumberConfirmed downtime. The only time that uses error budget
maintenanceMsnumberTime in maintenance windows, excluded
blockedMsnumberTime a firewall blocked the probe, excluded unless countBlockedAsDown is true
noDataMsnumberTime with no results to go on, excluded
availabilitynumber | nullupMs / (upMs + downMs), or null with no measured time
coveragenumberMeasured time divided by the time in the period
budgetMsnumberDowntime the target allows for the measured time so far
budgetUsednumberdownMs / budgetMs. Can exceed 1
projectedBudgetMsnumberDuring the month, the budget for the whole month, projected. Equals budgetMs once final
statusstringon_track, at_risk, breached, met or no_data
insufficientDatabooleantrue 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

StatusDescription
400period is not YYYY-MM
401Missing, invalid, disabled or expired API key
403Credential missing checks:read scope, or the account is not on Pro (feature_not_in_plan)
404Report not found, outside the key’s grant, or the month has not been computed yet
429Rate 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.

Last updated on