At some point every B2B SaaS company gets the email: "Per Section 7 of our MSA, please provide a quarterly SLA report showing your service uptime for Q1." This usually arrives right before renewal, which means a scramble to figure out what your actual uptime was, whether you met your commitments, and how to present the data in a way that does not accidentally reveal more than the customer needs to know.

CronAlert is designed to make this easy. The same check data that powers your alerts is structured for retrospective reporting -- incidents are stored, maintenance windows are excluded, uptime is calculated per monitor and across groups. This guide walks through how to turn that data into an SLA report that satisfies enterprise customers.

What an SLA report actually contains

Before you can generate one, it helps to know what a good SLA report looks like. The standard format across most B2B SaaS companies includes:

  • Reporting period. "Q1 2026: January 1 through March 31." Usually monthly or quarterly.
  • Services covered. Which endpoints, regions, or tenants this report applies to. Often "production API" and "web application" as separate lines.
  • Commitment. The uptime percentage you promised in the contract, e.g., "99.9% monthly uptime."
  • Actual uptime. The measured percentage for the period, with enough precision to compare against the commitment (usually 4 decimal places).
  • Total downtime. Aggregate minutes of downtime in the period.
  • Incident summary. One row per incident: start time, end time, duration, affected services, root cause, and resolution.
  • Excluded periods. Scheduled maintenance windows, force majeure, or anything else excluded per the contract.
  • Credit calculation, if applicable. If you missed the commitment, what service credits are owed and how they were calculated.

Some reports also include response-time statistics, regional breakdowns, or a narrative explanation of larger incidents. Keep it clean and factual -- an SLA report is not the place for marketing language.

Calculating uptime correctly

The basic formula is simple:

uptime% = (total_period_minutes - downtime_minutes - excluded_minutes)
         / (total_period_minutes - excluded_minutes)
         * 100

The details are where reports go wrong. A few things to watch for:

  • Downtime rounding. If your check interval is 1 minute, the minimum detectable downtime is roughly 1 minute (worst case: 2 minutes until consecutive-check verification confirms). A report at 99.99% precision assumes 1-minute granularity -- do not claim higher precision than your data supports.
  • What counts as "down." An HTTP 500 is obviously down. A 200 with an error page in the body? That depends on whether you have keyword monitoring set up. Be consistent across reports.
  • Regional failures. With multi-region monitoring, does "down" mean "all regions failed" or "any region failed"? Standard industry practice is "majority of regions failed" (quorum-based). Document your definition.
  • Maintenance windows. Scheduled maintenance is typically excluded, but only if announced in advance per the SLA terms. CronAlert's maintenance windows automatically exclude their duration from uptime stats.
  • Partial outages. If one out of four endpoints was down, uptime was not 100% for "the service" but also was not 0%. Report per-monitor uptime and aggregate carefully.

For a reference on the math and what each uptime tier actually requires, see how to calculate uptime percentage.

Using CronAlert's API to export data

Every check result, incident, and maintenance window is accessible via the CronAlert REST API. A typical SLA report pipeline:

  1. For each monitor covered by the SLA, fetch check results for the reporting period.
  2. Fetch incidents for the same period.
  3. Fetch maintenance windows for the same period.
  4. Calculate uptime using the formula above, excluding maintenance windows.
  5. Render the report in whatever format the customer wants (PDF, CSV, HTML).

Here is what this looks like in practice for a single monitor. The API is documented in the REST API guide:

#!/bin/bash
# Fetch incidents for Q1 2026
curl -s "https://cronalert.com/api/v1/monitors/MONITOR_ID/incidents?\
start=2026-01-01T00:00:00Z&end=2026-03-31T23:59:59Z" \
  -H "Authorization: Bearer $CRONALERT_API_KEY" \
  | jq '[.incidents[] | {
      start: .startedAt,
      end: .resolvedAt,
      duration_min: ((.resolvedAt | fromdateiso8601) - (.startedAt | fromdateiso8601)) / 60
    }]'

The output is a JSON array of incidents with start time, end time, and duration. Pipe this into a Python script, a spreadsheet, or a PDF generator -- whatever fits your customer communication workflow.

Python example: generate a full quarterly report

import os
import requests
from datetime import datetime, timezone
from decimal import Decimal

API = "https://cronalert.com/api/v1"
KEY = os.environ["CRONALERT_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

def fetch(path, params):
    return requests.get(f"{API}{path}", headers=HEADERS, params=params).json()

def report(monitor_id, start, end, commitment=99.9):
    incidents = fetch(f"/monitors/{monitor_id}/incidents",
                      {"start": start, "end": end})["incidents"]
    windows = fetch(f"/monitors/{monitor_id}/maintenance",
                    {"start": start, "end": end})["windows"]

    total_sec = (datetime.fromisoformat(end) -
                 datetime.fromisoformat(start)).total_seconds()
    excluded_sec = sum((datetime.fromisoformat(w["end"]) -
                        datetime.fromisoformat(w["start"])).total_seconds()
                       for w in windows)
    down_sec = sum((datetime.fromisoformat(i["resolvedAt"]) -
                    datetime.fromisoformat(i["startedAt"])).total_seconds()
                   for i in incidents if i.get("resolvedAt"))

    uptime = (total_sec - down_sec - excluded_sec) / (total_sec - excluded_sec) * 100
    met = uptime >= commitment

    return {
        "period": f"{start} to {end}",
        "commitment_pct": commitment,
        "actual_pct": round(uptime, 4),
        "met": met,
        "downtime_minutes": round(down_sec / 60, 1),
        "excluded_minutes": round(excluded_sec / 60, 1),
        "incidents": incidents,
    }

This is a starting point -- the output dictionary has enough data to render into any format a customer asks for. Most teams generate PDFs with a templating library, or paste the summary into a Google Doc.

Handling maintenance windows correctly

Maintenance windows are the most common source of disagreements on SLA reports. The customer sees downtime in their own logs; you see an exclusion. To avoid these conflicts:

  1. Announce in advance. Most SLAs require 24-72 hours notice for planned maintenance to count as excluded. Always post to your status page and email affected customers.
  2. Configure the maintenance window in CronAlert. This suppresses alerts during the window and excludes the time from uptime stats automatically.
  3. Keep a log of announcements. When a customer disputes a reported uptime number, you can point to the specific announcement for the specific window.
  4. Be conservative about what counts. "We rebooted a server" is usually not planned maintenance if it was not announced. Mark it as an incident, take the hit, and move on.

Read more in the maintenance windows guide.

What to do when you miss your SLA

At some point you will miss a commitment. A bad deploy, a cloud provider outage, a database corruption. The SLA report still has to go out, and it has to be honest.

  • Lead with the number. Do not bury the miss -- state it upfront. "Our Q1 uptime was 99.82%, below our 99.9% commitment."
  • Explain the root cause. A one-paragraph summary per significant incident. Do not over-share internal details, but give enough for the customer to understand what happened.
  • Describe the fix. What changed so this does not happen again. This matters more to the customer than the size of the credit.
  • Calculate the credit accurately. SLA credits are usually a percentage of the monthly fee, tiered by how badly the SLA was missed. Apply the formula in the contract exactly.
  • Do not argue. If you missed, you missed. Disputing it on a technicality destroys trust.

A good post-incident follow-up is worth more than perfect uptime. Customers remember how you handled the bad quarter far more than they remember the 99.95% quarter.

What commitment should you actually promise?

If you are writing an SLA for the first time, do not commit to more uptime than your infrastructure can deliver. Some pragmatic starting points:

Commitment Downtime per month Downtime per year Typical tier
99% 7h 14m 3d 15h Early SaaS, no commitment strong incentive to beat
99.9% 43m 28s 8h 41m Standard B2B SaaS commitment
99.95% 21m 44s 4h 21m Mid-market SaaS, serious infrastructure
99.99% 4m 21s 52m 34s Enterprise SaaS, strong redundancy required
99.999% 26s 5m 15s Critical infrastructure (payments, telecoms). Rare.

Three nines (99.9%) is the honest default for most SaaS companies. If you are running on a single region, four nines is probably a stretch. If your stack is multi-region with failover, four nines is achievable. Five nines requires dedicated effort that most startups do not need.

Retention: how far back does CronAlert store data?

SLA reports often require looking back months or quarters. CronAlert's log retention by plan:

  • Free: 7 days. Enough for weekly reports, not enough for a quarterly SLA.
  • Pro ($5/mo): 30 days. Works for monthly reports.
  • Team ($20/mo): 90 days. Works for quarterly reports.
  • Business ($50/mo): 1 year. Works for annual reports and multi-quarter comparisons.

If you are committing to SLAs with enterprise customers, Business plan is usually the right tier -- the log retention and audit log features are exactly what procurement teams want to see.

Automating SLA reports

Once you have the pipeline working manually, automate it. A reasonable setup:

  1. A scheduled job runs on the first of every month.
  2. It pulls incidents, maintenance windows, and check results for the previous month via the API.
  3. It generates a PDF using a template (or an HTML page, or a Slack message for internal visibility).
  4. It emails the PDF to a customer success alias, or uploads it to a shared Google Drive, or posts it to your status page archive.
  5. A heartbeat monitor on CronAlert pings when the job finishes, so if the report job itself fails you find out. See heartbeat monitoring for how to set this up.

This is the kind of thing that should run unattended -- writing the report from scratch every quarter is how reports get skipped until a customer asks, and then they reveal missing data.

Frequently asked questions

What is an SLA report?

An SLA report is a periodic (usually monthly or quarterly) document that shows whether a service met its Service Level Agreement commitments. It typically includes total uptime percentage, a list of incidents, their duration and root cause, and any SLA credits owed to customers who were affected. Enterprise customers often require SLA reports as part of procurement or renewal.

What uptime percentage counts as good?

99.9% (three nines) is a typical SaaS commitment and allows 43 minutes of downtime per month. 99.95% allows 22 minutes. 99.99% (four nines) allows 4.3 minutes and is what larger enterprise SaaS companies commit to. Only commit to what your infrastructure can realistically deliver -- missed SLA commitments mean real credits or refunds.

Do scheduled maintenance windows count as downtime?

In most SLA agreements, no -- scheduled maintenance announced in advance is excluded from uptime calculations. CronAlert supports maintenance windows that exclude specific time ranges from uptime stats. The exact definition should be explicit in your SLA agreement (e.g., announced at least 48 hours in advance, maximum 4 hours per month).

How do I export CronAlert data for a custom SLA report?

Use the CronAlert REST API to pull check results, incidents, and uptime statistics for any date range. Results can be exported as JSON and transformed into CSV, PDF, or whatever format your customer requires. Business plan includes longer log retention (1 year) for multi-quarter SLA reports.

Turn monitoring data into customer-facing proof

Uptime monitoring is only the first half of the problem. Turning that data into reports that satisfy enterprise customers is what closes the loop. CronAlert stores every check result, incident, and maintenance window, exposes all of it via a REST API, and makes it straightforward to build the reports you need.

Start with the free plan to test the setup, or jump straight to Team or Business if you know you will need the log retention for SLA reporting. See the pricing page for plan details.