Skip to content
BotStable
  • Features
  • Limits
  • How it works
  • FAQ
  • Status
Join Discord

On this page

  1. 1. Base URL and basics
  2. 2. Endpoints
  3. 3. Status summary
  4. 4. One service
  5. 5. Daily report
  6. 6. Incidents
  7. 7. Rate limit and caching
  8. 8. Errors
  9. 9. Fair use

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 (and HEAD, 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 tz parameter (for example Europe/Budapest) only decides which local days and hours the 90 day bar and the daily report are grouped into. An unknown value falls back to UTC.

2. Endpoints

GET /api/v1/statusOverall status, every service, the last hour and 90 days.
GET /api/v1/services/{id}One service in detail: uptime, latency, today.
GET /api/v1/incidentsIncidents 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/reportThe 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_atWhen this answer was built (it can be up to a minute old, see below).
overalloperational 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_24hPercent of checks that were up in the last 24 hours, or null.
red_over_minutesA bar block turns red when a service was down for more than this many minutes in it.
groupsServices in groups: name, count, uptime_24h, avg_ms_24h, days and services.
groups[].daysThe 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, subId, display name and place.
stateup, down, maintenance (switched to maintenance by staff, never counted as downtime) or unknown (no check for 5 minutes).
maintenancenull, or { "reason": string or null, "since": unix time } while state is maintenance.
down_sinceUnix time when the current down streak began. Only set while state is down, else null.
down_minutes_nowWhole minutes down so far, as of generated_at. Only set while down. To show a live number, keep counting from down_since.
since_exacttrue 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, msTime of the last check and its latency in ms (null if it was down).
uptime_24hPercent of checks that were up in the last 24 hours.
avg_ms_hourAverage latency of the successful checks of the last hour.
seriesThe 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_exactWhen the current state (up or down) began, and whether that time is exact. Always set.
uptimePercent up over 24h, 30d, 90d and 365d (UTC days). null without data.
down_minutes_90dDown minutes in the last 90 days.
reports_todayIssue reports sent by visitors today.
todayThe 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[].stateok, 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[].downEvery 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_minutesRuns of consecutive hours with an issue, and their average length.
dates, years, months, statsDays 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": [ ... ] }.

typeincident or maintenance.
severityminor, major or critical for an incident, null for maintenance.
stateactive (ongoing, or upcoming maintenance) or closed.
stageThe 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.
servicesThe affected services, id and display name.
scheduled_start, scheduled_endPlanned window of a maintenance (unix time), or null. Always null for incidents.
created_at, closed_at, updated_atWhen the post started, when it closed (null while open) and when it last changed.
urlPermanent link to the post.
updatesThe 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 429 with a Retry-After header (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: /status and /services/{id} for 45 seconds (plus up to 15 seconds while refreshing), /report for 2 to 5 minutes, /incidents for 60 seconds. The Cache-Control header 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 ip of 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-4001Your request: unknown service, incident or path (404), bad parameter (400), method not allowed (405), too many requests (429).
BS-1001, BS-1002, BS-9000A 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-Agent with 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).
BotStable

Free hosting for Discord bots

  • Discord
  • Panel
  • Status
  • Terms
  • Privacy
  • API

© 2026 BotStable