Developers
Public status API
Version 1 · read only · no API key
BotStable has a free JSON API with the same status data as status.botstable.eu. Use it for a status bot, a widget or a dashboard.
1. Base URL and basics
- Base URL:
https://botstable.eu/api/v1 - Only
GET(andHEAD,OPTIONS). No keys, no login, nothing to sign up for. There are no write endpoints. - CORS is open (
Access-Control-Allow-Origin: *), so you can call it from a browser app. - Responses are JSON. Times are unix timestamps in seconds (UTC). Service ids:
website,panel,api(this API itself),na-1,eu-1. - The optional
tzparameter (for exampleEurope/Budapest) only decides which local days and hours the 90 day bar and the daily report are grouped into. An unknown value falls back toUTC.
2. Endpoints
GET /api/v1/status | Overall status, every service, the last hour and 90 days. |
|---|---|
GET /api/v1/services/{id} | One service in detail: uptime, latency, today. |
GET /api/v1/incidents | Incidents and maintenance of the last 90 days. Parameter: limit (1 to 50). |
GET /api/v1/incidents/{id} | One incident or maintenance with its updates. |
GET /api/v1/report | The daily report: hour by hour for one day. Parameters: date (YYYY-MM-DD, default today), year, tz. |
curl https://botstable.eu/api/v1/status
curl https://botstable.eu/api/v1/services/eu-1
curl "https://botstable.eu/api/v1/report?date=2026-11-08&tz=Europe/Budapest"
# only the state of each service
curl -s https://botstable.eu/api/v1/status | jq '.groups[].services[] | {id, state, ms}'
3. Status summary
GET /api/v1/status, trimmed sample:
{
"generated_at": 1794139200,
"tz": "UTC",
"red_over_minutes": 10,
"overall": "partial",
"uptime_24h": 99.62,
"groups": [
{
"name": "Bot nodes",
"count": 2,
"uptime_24h": 99.3,
"avg_ms_24h": 64,
"days": [ { "date": "2026-11-08", "state": "minor", "fine": 1, "issues": 1 } ],
"services": [
{
"id": "na-1",
"name": "NA-1",
"sub": "Beauharnois, Canada",
"state": "down",
"maintenance": null,
"down_since": 1794138900,
"down_minutes_now": 5,
"since_exact": true,
"last_checked": 1794139170,
"ms": null,
"uptime_24h": 98.6,
"avg_ms_hour": 70,
"series": [ [1794135660, 70, 1], [1794135720, null, 0] ]
},
{
"id": "eu-1",
"name": "EU-1",
"sub": "Frankfurt, Germany",
"state": "maintenance",
"maintenance": { "reason": "Hardware upgrade", "since": 1794135000 },
"down_since": null,
"down_minutes_now": null,
"since_exact": null,
"last_checked": 1794139170,
"ms": 61,
"uptime_24h": 100,
"avg_ms_hour": 63,
"series": [ [1794135660, 62, 1] ]
}
]
}
]
}
Top level
generated_at | When this answer was built (it can be up to a minute old, see below). |
|---|---|
overall | operational everything is up, partial some services are down, major all services are down, maintenance everything else is fine and some services are under maintenance, unknown no recent data from our monitor. |
uptime_24h | Percent of checks that were up in the last 24 hours, or null. |
red_over_minutes | A bar block turns red when a service was down for more than this many minutes in it. |
groups | Services in groups: name, count, uptime_24h, avg_ms_24h, days and services. |
groups[].days | The last 90 local days, oldest first: date, state (ok, minor, major, down, none), fine and issues (services without and with an issue). A day with downtime also has down: [service, minutes down, [[start, end]]], where end is null while it goes on. |
Each service
id, name, sub | Id, display name and place. |
|---|---|
state | up, down, maintenance (switched to maintenance by staff, never counted as downtime) or unknown (no check for 5 minutes). |
maintenance | null, or { "reason": string or null, "since": unix time } while state is maintenance. |
down_since | Unix time when the current down streak began. Only set while state is down, else null. |
down_minutes_now | Whole minutes down so far, as of generated_at. Only set while down. To show a live number, keep counting from down_since. |
since_exact | true if down_since is exact. false means the history is shorter than the outage, so it was down for at least that long. null unless down. |
last_checked, ms | Time of the last check and its latency in ms (null if it was down). |
uptime_24h | Percent of checks that were up in the last 24 hours. |
avg_ms_hour | Average latency of the successful checks of the last hour. |
series | The last 60 minutes: [unix time, ms or null, 1 up / 0 down / null no data]. |
4. One service
GET /api/v1/services/eu-1, trimmed sample. It has the same state, maintenance, down_since, down_minutes_now and series as above. An unknown id answers 404.
{
"generated_at": 1794139200,
"tz": "UTC",
"service": { "id": "eu-1", "name": "EU-1", "sub": "Frankfurt, Germany", "type": "node", "group": "Bot nodes" },
"state": "up",
"maintenance": null,
"down_since": null,
"down_minutes_now": null,
"since": 1794052800,
"since_exact": true,
"last_checked": 1794139170,
"ms": 61,
"avg_ms_24h": 63,
"reports_today": 0,
"uptime": { "24h": 99.93, "30d": 99.97, "90d": 99.95, "365d": 99.91 },
"down_minutes_90d": 41,
"series": [ [1794135660, 62, 1], [1794135720, 60, 1] ],
"today": [ { "start": 1794096000, "label": "00", "end_label": "01", "state": "ok", "down_minutes": 0, "uptime": 100 } ]
}
since, since_exact | When the current state (up or down) began, and whether that time is exact. Always set. |
|---|---|
uptime | Percent up over 24h, 30d, 90d and 365d (UTC days). null without data. |
down_minutes_90d | Down minutes in the last 90 days. |
reports_today | Issue reports sent by visitors today. |
today | The visitor day hour by hour: state ok, down or none. |
5. Daily report
GET /api/v1/report?date=2026-11-08&tz=Europe/Budapest, trimmed sample:
{
"tz": "Europe/Budapest",
"red_over_minutes": 10,
"date": "2026-11-08",
"has_data": true,
"hour_count": 24,
"groups": [
{
"name": "Web services",
"count": 2,
"uptime": 98.2,
"outages": 1,
"avg_outage_minutes": 25,
"hours": [
{
"start": 1794114000, "label": "07", "end_label": "08",
"state": "down", "fine": 0, "issues": 2,
"down": [ ["Website", 25, [[1794114000, 1794115500]], 0] ]
}
]
}
],
"dates": ["2026-11-07", "2026-11-08"],
"stats": { "total_days": 2, "earliest": "2026-11-07", "latest": "2026-11-08" },
"years": [2026], "year": 2026,
"months": [ { "month": 11, "uptime": 99.4 } ]
}
hours[].state | ok, minor, major, down or none (no data). A service counts as having an issue (red) in an hour when it was down for more than red_over_minutes minutes. |
|---|---|
hours[].down | Every service that was down at all in that hour, also in a green one: [name, minutes down, [[outage start, outage end or null]], more]. |
outages, avg_outage_minutes | Runs of consecutive hours with an issue, and their average length. |
dates, years, months, stats | Days with data, and the monthly uptime of the year. |
6. Incidents
Incidents and planned maintenance, the same posts as on status.botstable.eu/incidents. GET /api/v1/incidents lists the posts of the last 90 days (and every open one), newest first. ?limit= is a whole number from 1 to 50 (default 50), anything else is a 400. GET /api/v1/incidents/{id} returns one post, also an older one, or 404.
curl https://botstable.eu/api/v1/incidents?limit=5
curl https://botstable.eu/api/v1/incidents/12
GET /api/v1/incidents/12, sample:
{
"generated_at": 1794139200,
"incident": {
"id": 12,
"type": "incident",
"title": "API errors",
"severity": "major",
"state": "closed",
"stage": "resolved",
"services": [ { "id": "website", "name": "Website" }, { "id": "api", "name": "API" } ],
"scheduled_start": null,
"scheduled_end": null,
"created_at": 1794120000,
"closed_at": 1794135600,
"updated_at": 1794135600,
"url": "https://status.botstable.eu/incidents/12",
"updates": [
{ "stage": "resolved", "body": "Fixed. Everything is back to normal.", "created_at": 1794135600 },
{ "stage": "investigating", "body": "We are looking into **errors** on the API.", "created_at": 1794120000 }
]
}
}
The list has the same shape: { "generated_at", "incidents": [ ... ] }.
type | incident or maintenance. |
|---|---|
severity | minor, major or critical for an incident, null for maintenance. |
state | active (ongoing, or upcoming maintenance) or closed. |
stage | The stage of the newest update. Incidents: investigating, identified, monitoring, update, resolved. Maintenance: scheduled, inprogress, extended, update, complete, cancelled. resolved, complete and cancelled close the post; a later update with another stage reopens it. |
services | The affected services, id and display name. |
scheduled_start, scheduled_end | Planned window of a maintenance (unix time), or null. Always null for incidents. |
created_at, closed_at, updated_at | When the post started, when it closed (null while open) and when it last changed. |
url | Permanent link to the post. |
updates | The timeline, newest first: stage, body and created_at. body is plain text with a tiny markdown subset: **bold**, *italic*, `code` and line breaks. Never render it as HTML. |
The status summary (/api/v1/status) also carries active_posts: up to 5 open posts, each with id, type, severity, title, stage, scheduled_start, scheduled_end and url. They never change overall.
7. Rate limit and caching
- At most 30 requests per minute per IP, shared by all endpoints and by the status website itself. Over the limit you get
429with aRetry-Afterheader (seconds) and an error body. An IP that goes over the limit several times in a minute is blocked for a few seconds. - Answers are cached at our edge:
/statusand/services/{id}for 45 seconds (plus up to 15 seconds while refreshing),/reportfor 2 to 5 minutes,/incidentsfor 60 seconds. TheCache-Controlheader says so. The data can be up to about a minute old. A change such as maintenance mode shows up within about a minute. If our database has a problem you may get the last good answer (X-Cache: stale) for up to 30 minutes. - The
ipof the nodes and other internal fields are not part of this API.
8. Errors
Errors are JSON with a short code, never details: { "error": "...", "code": "BS-4001" }.
BS-4001 | Your request: unknown service, incident or path (404), bad parameter (400), method not allowed (405), too many requests (429). |
|---|---|
BS-1001, BS-1002, BS-9000 | A problem on our side (503 or 500). Try again in a few minutes, and mention the code if you ask for help in our Discord. |
9. Fair use
- Please poll no more than once a minute. The data only changes about once a minute anyway.
- Cache on your side and share one request between your users. Do not call the API once per visitor.
- Send a clear
User-Agentwith the name of your project. - This is a free service run as a hobby. It is provided as is, can change, and we may block IPs that hammer it. Breaking changes will use a new version (
/api/v2).