Vendor SLA Attainment and Credit-Claim Report
Pricing
from $10.00 / 1,000 run_starts
Vendor SLA Attainment and Credit-Claim Report
Turn a vendor's public status page into one number for the billing period. For each vendor: the uptime percentage, the downtime minutes split by severity, the incident count, the longest outage, the breach flag against your contracted threshold, the claim
Pricing
from $10.00 / 1,000 run_starts
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Get one number for each vendor at the end of a billing period.
This Actor reads a vendor's public status page and computes the uptime that the vendor actually delivered over your billing period. It then compares that number against the uptime your contract promises, and shows the minutes you can claim back when the vendor falls short.
It is built for the person who approves a renewal or files a credit claim, not for the person who watches a dashboard. There is no alert and no schedule to babysit. You run it once at the end of the month or the quarter, and you get a row for each vendor with the number and the evidence behind it.
What you get for each vendor
One dataset record for each vendor and period:
| Field | Meaning |
|---|---|
uptime_pct | The delivered uptime over the period, to six decimal places. |
sla_threshold_pct | The uptime your contract promises for that vendor. |
breach | true when the downtime is larger than the allowed budget. |
downtime_minutes | Total downtime minutes inside the period. |
downtime_minutes_critical / _major / _minor / _none | The same downtime split by severity. |
allowed_downtime_minutes | The budget the threshold allows over this period. |
credit_claim_minutes | The minutes above that budget. This is what you claim. |
incident_count | Incidents that touch the period, after your exclusions. |
counted_incident_count | Of those, the ones that removed uptime. |
longest_outage_minutes | The longest single stretch of downtime. |
longest_outage_incident | The incident that drove that stretch. |
incidents | The incident list that produced the number, with the minutes each one spent in the period. |
history_coverage | complete, or truncated when the vendor publishes no further back. |
A run_summary record adds the counts for the whole run: how many vendors were
measured, how many breached, and the total claimable minutes.
How the number is computed
Three rules decide whether the number survives an audit.
- Concurrent incidents share one clock. Two overlapping outages of 3 hours and 4 hours are 5 hours of downtime, not 7. Every interval set is merged before it is measured.
- An incident is clipped to the period. An outage that began last month spends only its minutes of this month in this report.
- Severity decides what counts. By default only a
criticalor amajorincident removes uptime, because most contracts call aminorincident degradation and not downtime. ChangedowntimeImpactsto match your contract. Planned maintenance never counts.
The breach flag compares seconds against the allowed budget, and not the rounded percentage, so a vendor can never slip under the threshold through display rounding. The budget itself is computed with exact decimal arithmetic, which matters at 99.99 percent.
An incident that names only components you excluded is dropped. An incident that names no component at all is kept, because the status page could not attribute it and dropping it would understate the downtime.
Which status pages work
Public Statuspage and Instatus JSON feeds. No account and no API key.
The usual URL is:
https://status.<vendor>.com/api/v2/incidents.json
Some vendors use a different hostname, for example
https://www.githubstatus.com/api/v2/incidents.json. A
/api/v2/summary.json URL and a /history.json URL also work.
Reaching behind the 50-incident cap
The public Statuspage API answers with the 50 most recent incidents and has no page parameter. For a busy vendor and a long period, those 50 incidents may not reach back to your period start.
When that happens, this Actor reads the /history.json pages of the same
status page, which hold three months each, until the period is covered or
maxHistoryPages is reached. It keeps the API record of any incident that
appears in both, because the API carries exact timestamps and the history page
only carries minutes.
If the vendor publishes no history that far back, the report says
history_coverage: "truncated" and names the oldest instant it could confirm.
The number is still produced; you simply know it may understate the downtime.
Input
Every field has a default, so a run with an empty input works.
| Field | Default | Meaning |
|---|---|---|
vendors | three public status pages | 1 to 50 entries of {name, status_json_url, sla_threshold_pct, excluded_components}. A plain URL string also works. |
periodStart | empty | The first instant of the period. A date begins at 00:00 UTC. |
periodEnd | empty | The last instant. A date such as 2026-08-31 covers the whole of that day. |
downtimeImpacts | ["critical","major"] | The severities that remove uptime. |
maxHistoryPages | 3 | History pages read for each vendor behind the API cap. 0 reads the given URL alone. |
maxIncidentsPerReport | 200 | Incident records kept in the evidence list. The longest ones are kept. |
requestTimeoutSeconds | 30 | Timeout for each read. |
concurrency | 5 | Vendors read at the same time. |
maxRedirects | 5 | Redirects followed for one read. Every hop is checked again. |
maxResponseBytes | 5000000 | Size cap for one JSON answer. |
Leave both period fields empty to report the last complete calendar month. A monthly schedule then stays correct for ever, with no input to edit.
The period must be at most 400 days. A period that has not finished yet is
measured up to now, and the report sets period_complete: false.
Example
{"vendors": [{"name": "GitHub","status_json_url": "https://www.githubstatus.com/api/v2/incidents.json","sla_threshold_pct": 99.9,"excluded_components": ["Visit www.githubstatus.com for more information"]},{"name": "Cloudflare","status_json_url": "https://www.cloudflarestatus.com/api/v2/incidents.json","sla_threshold_pct": 99.99,"excluded_components": []}],"periodStart": "2026-08-01","periodEnd": "2026-08-31","downtimeImpacts": ["critical", "major"]}
Pricing
This Actor is billed per event.
| Event | Charged | Counted |
|---|---|---|
run_start | once for each run | The container start and the store init. Charged even when a run is stopped early. |
vendor_period_report | once for each vendor-period record measured | The history reads and the interval arithmetic for one vendor and one period. |
breach_evidence_pack | once for each vendor below its threshold | The extra incident roll-up that supports the credit claim. |
A vendor that could not be read, an input entry that could not be read, and a vendor left out by the run's charge limit are not charged, because no report was produced for them. They still appear in the dataset so that nothing is missing from your review.
The run never reads more vendors than its maximum total charge allows.
Limits and safety
- 50 vendors for each run, and a period of at most 400 days.
- HTTP and HTTPS only. A URL with credentials is refused.
- A target that resolves to a private, loopback, link-local or otherwise reserved address is refused, and every redirect hop is checked again.
- The redirect chain, the response size and the request timeout are all bounded.
- No browser, no proxy, no paid API, no account and no stored state. Each run computes the result from the published history, so it is re-runnable.
When something goes wrong
A business verdict is never a failed run. A vendor below its threshold, a status page that answers HTTP 503, a payload that is not a status page and an input that breaks a bound all end with a succeeded run that carries the finding in the dataset and in the status message. A failed run means this Actor itself malfunctioned.
Development
uv syncuv run pytestuv run ruff check .