Contact
API Reference

WorthClient Status API

Everything you need to build on top of the public status data: current service health, uptime statistics, incidents, maintenance windows, embeddable badges and real-time notifications — with live examples you can run right here.

No API key required JSON responses 15 public endpoints Rate limited: 240 req/min

Introduction

The WorthClient Status API exposes live monitoring data from the public status page in a machine-readable format. You can use it to build status widgets, dashboards, Discord or Telegram bots, CI/CD health gates, or any subsystem that needs to know whether WorthClient services are healthy.

Base URL

All endpoints below are relative to your status page origin. If your status page is hosted at https://status.worthclient.com, the base URL is:

Base URL
https://status.worthclient.com/api

Response format

All responses are application/json; charset=utf-8 unless stated otherwise. Timestamps are ISO 8601 strings in UTC (2026-08-18T12:00:00.000Z). The badge, favicon and event stream endpoints return their own content types.

Caching

Status data is rebuilt in memory every few seconds (60 seconds by default, configurable via the admin panel). JSON endpoints are served with Cache-Control: no-store so clients always receive fresh data. Images (badge.svg, favicon.svg) are cached for 30–60 seconds for safe embed use.

Same-origin policy

The API does not send CORS headers. Browser calls must come from the same origin as the status page. Server-side clients and native tools are unaffected.

Interactive testing

Every endpoint in this documentation has a Try it button and a built-in API Tester panel on the right side of the screen — no setup needed, you can call the live API directly from the browser.

Quick Start

The fastest way to check overall service health is a single request to GET /api/status. The examples below use the same request in the five most common languages:

const res = await fetch('/api/status');
const data = await res.json();

console.log(data.overall);   // { code, label, message }
console.log(data.counts);    // { total, up, down, degraded, paused, unknown, incidentsOpen }
console.log(data.monitors);  // every public monitor with uptime stats
import requests

data = requests.get("https://status.worthclient.com/api/status").json()

print(data["overall"]["code"])   # 'up' | 'down' | 'incident' | ...
print(data["counts"])
for monitor in data["monitors"]:
    print(monitor["name"], monitor["status"])
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(
        URI.create("https://status.worthclient.com/api/status"))
        .GET()
        .build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type Status struct {
	Overall struct {
		Code string `json:"code"`
	} `json:"overall"`
	Counts map[string]int `json:"counts"`
}

func main() {
	resp, err := http.Get("https://status.worthclient.com/api/status")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var status Status
	json.NewDecoder(resp.Body).Decode(&status)
	fmt.Println(status.Overall.Code, status.Counts)
}
# Overall status, counts and services
curl https://status.worthclient.com/api/status

# Same payload, without the full monitors array
curl "https://status.worthclient.com/api/status?compact=1"

Authentication

All endpoints documented on this page are public. No API key, token, session or header is required for reads.

Two exceptions to keep in mind:
  • POST /api/status/refresh is a write operation and requires a CSRF token header (see its section below).
  • The administration API (monitors, incidents, maintenance, categories, settings and authentication routes under /api) is private and requires an authenticated session. It is intentionally not documented here.

Rate Limits

The public API is protected by an IP-based rate limiter:

  • 240 requests per minute per IP address across all /api endpoints. Exceeding it returns 429.
  • Limiter headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) are included in every response.
  • POST /api/status/refresh has an additional global throttle: at most one manual refresh every 30 seconds (configurable via the STATUS_REFRESH_MIN_SECONDS environment variable).
Best practices: poll GET /api/status every 30–60 seconds at most, or subscribe to the real-time event stream instead of polling. Cache the payload on your side — it only changes when a check runs.

Errors

Errors are always returned as JSON with a human-readable message. The HTTP status code tells you what went wrong:

Status Meaning
400 Invalid request — malformed body or missing required fields.
403 CSRF token missing or invalid (write requests).
404 Resource not found — unknown monitor, incident or maintenance ID, or unknown API route.
429 Too many requests — rate limit reached, or refresh throttle active.
500 Internal server error.

Error body format:

Error response
{
  "error": "Monitor not found."
}

Some endpoints include extra context on specific errors — for example, POST /api/status/refresh returns a retryAfter field with the number of seconds to wait before trying again.

Status API

Live health data: overall status, monitors, uptime statistics, incidents and maintenance. All endpoints under /api/status.

GET /api/status Full status payload

Returns the complete public status payload: overall status, counts, categories, monitors with uptime stats, recent incidents and upcoming maintenance windows.

Query parameters

Parameter Type Default Description
compact 1 Omits the top-level monitors array (the same monitors are still nested inside categories).
fresh 1 Bypasses the in-memory cache and rebuilds the payload on the spot.

Response 200 OK

Response · application/json
{
  "appName": "WorthClient Status",
  "title": "WorthClient Status - Service Status",
  "generatedAt": "2026-08-18T12:00:00.000Z",
  "nextUpdateAt": "2026-08-18T12:01:00.000Z",
  "refreshSeconds": 60,
  "overall": {
    "code": "up",
    "label": "All systems operational",
    "message": "Every monitored service is responding."
  },
  "counts": {
    "total": 4,
    "up": 3,
    "down": 0,
    "degraded": 1,
    "paused": 0,
    "unknown": 0,
    "incidentsOpen": 0
  },
  "categories": [
    {
      "id": "01j1f2a3b4c5d6e7f8a9b0c1d",
      "name": "Core services",
      "description": "",
      "display_order": 1,
      "monitors": [
        {
          "id": "01j1f2a3b4c5d6e7f8a9b0c2e",
          "name": "Main Website",
          "url": "https://worthclient.com",
          "status": "up",
          "enabled": true,
          "show_on_public": true,
          "show_response_chart": true,
          "category_id": "01j1f2a3b4c5d6e7f8a9b0c1d",
          "display_order": 1,
          "last_checked_at": "2026-08-18T11:59:46.000Z",
          "last_response_time_ms": 84,
          "last_status_code": 200,
          "category_name": "Core services",
          "category_description": "",
          "category_order": 1,
          "stats": {
            "timezone": "America/Sao_Paulo",
            "uptimeToday": 100,
            "checksToday": 120,
            "avgResponseToday": 91,
            "uptime24h": 100,
            "uptime7d": 99.98,
            "uptime30d": 99.96,
            "uptime90d": 99.97,
            "avgResponse24h": 93,
            "avgResponse90d": 102,
            "checks24h": 240,
            "checks90d": 21600,
            "bars90d": [
              {
                "status": "up",
                "uptime": 100,
                "checks": 240,
                "downSeconds": 0,
                "degradedSeconds": 0,
                "date": "2026-08-18",
                "timezone": "America/Sao_Paulo",
                "from": "2026-08-18T03:00:00.000Z",
                "to": "2026-08-19T03:00:00.000Z"
              }
            ],
            "bars24h": [
              {
                "status": "up",
                "uptime": 100,
                "checks": 15,
                "downSeconds": 0,
                "degradedSeconds": 0,
                "from": "2026-08-18T10:30:00.000Z",
                "to": "2026-08-18T11:00:00.000Z"
              }
            ],
            "responseTimes24h": [95, 88, 102, null]
          }
        }
      ]
    }
  ],
  "monitors": [],
  "incidents": [],
  "maintenance": []
}

Notes on the payload:

  • overall.code is one of up, down, incident, degraded, maintenance or unknown — see the Overall Status reference.
  • Uptime fields are percentages (0–100) or null when there is not enough data yet.
  • bars90d is always present; bars24h and responseTimes24h are present when the monitor has the response chart enabled.
  • Incident and maintenance objects inside this payload include their updates arrays; the dedicated list endpoints below do not.
  • Outage bars may also include incidents / maintenance annotation objects with { count, titles, items } when an incident or maintenance overlaps that day.

Examples

const res = await fetch('/api/status');
const data = await res.json();

console.log(data.overall.code);
console.log(data.counts.up, '/', data.counts.total, 'services up');
import requests

data = requests.get("https://status.worthclient.com/api/status").json()

print(data["overall"]["code"])
print(data["counts"]["up"], "/", data["counts"]["total"], "services up")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(
        URI.create("https://status.worthclient.com/api/status"))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	resp, err := http.Get("https://status.worthclient.com/api/status")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status

# Compact: without the top-level monitors array
curl "https://status.worthclient.com/api/status?compact=1"
POST /api/status/refresh Trigger a manual re-check

Runs a check against every monitor right away and rebuilds the public status. Useful for on-demand verification (for example, right after a deployment). Globally throttled to one run every 30 seconds.

CSRF token

This is the only public endpoint that accepts a write method. Like every write request in the app, it must include an x-csrf-token header. The token is the value of the vx_status_csrf cookie, which is set automatically on any page visit (or by calling GET /api/auth/csrf).

Request body

None.

Response 200 OK

Response · application/json
{
  "ok": true,
  "checked": 4,
  "failed": 0,
  "generatedAt": "2026-08-18T12:00:00.000Z",
  "nextUpdateAt": "2026-08-18T12:01:00.000Z"
}

Errors

Status Body When
403 {"error": "Invalid or missing CSRF token."} The x-csrf-token header does not match the cookie.
429 {"error": "…", "retryAfter": 21} Another refresh ran less than 30 seconds ago. retryAfter is the number of seconds to wait.

Examples

const token = document.cookie.match(/vx_status_csrf=([^;]+)/)?.[1];

const res = await fetch('/api/status/refresh', {
  method: 'POST',
  headers: { 'x-csrf-token': token }
});

const result = await res.json();
console.log(result); // { ok, checked, failed, generatedAt, nextUpdateAt }
import requests

session = requests.Session()
# First request sets the vx_status_csrf cookie
session.get("https://status.worthclient.com/api/auth/csrf")

csrf = session.cookies.get("vx_status_csrf")
result = session.post(
    "https://status.worthclient.com/api/status/refresh",
    headers={"x-csrf-token": csrf},
).json()

print(result)
import java.net.*;
import java.net.http.*;

var cookieManager = new CookieManager();
HttpClient client = HttpClient.newBuilder()
        .cookieHandler(cookieManager)
        .build();

// First request sets the vx_status_csrf cookie
client.send(HttpRequest.newBuilder(
                URI.create("https://status.worthclient.com/api/auth/csrf"))
                .GET().build(),
        HttpResponse.BodyHandlers.discarding());

String csrf = cookieManager.getCookieStore().getCookies().stream()
        .filter(c -> c.getName().equals("vx_status_csrf"))
        .map(HttpCookie::getValue)
        .findFirst().orElse("");

var request = HttpRequest.newBuilder(
                URI.create("https://status.worthclient.com/api/status/refresh"))
        .header("x-csrf-token", csrf)
        .POST(HttpRequest.BodyPublishers.noBody())
        .build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
)

func main() {
	jar, _ := cookiejar.New(nil)
	client := &http.Client{Jar: jar}

	// First request sets the vx_status_csrf cookie
	client.Get("https://status.worthclient.com/api/auth/csrf")

	req, _ := http.NewRequest(http.MethodPost,
		"https://status.worthclient.com/api/status/refresh", nil)

	for _, cookie := range jar.Cookies(req.URL) {
		if cookie.Name == "vx_status_csrf" {
			req.Header.Set("x-csrf-token", cookie.Value)
		}
	}

	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
# The CSRF token lives in the vx_status_csrf cookie.
# Grab it after visiting any page, then send it as a header:
curl -X POST https://status.worthclient.com/api/status/refresh \
  -H "x-csrf-token: $CSRF_TOKEN" \
  -b "vx_status_csrf=$CSRF_TOKEN"
GET /api/status/monitors All public monitors

Returns only the monitors portion of the status payload — every public monitor with its uptime stats.

Query parameters

Parameter Type Default Description
fresh 1 Bypasses the in-memory cache and rebuilds the payload on the spot.

Response 200 OK

Response · application/json
{
  "monitors": [
    {
      "id": "01j1f2a3b4c5d6e7f8a9b0c2e",
      "name": "Main Website",
      "url": "https://worthclient.com",
      "status": "up",
      "enabled": true,
      "show_on_public": true,
      "show_response_chart": true,
      "category_id": "01j1f2a3b4c5d6e7f8a9b0c1d",
      "display_order": 1,
      "last_checked_at": "2026-08-18T11:59:46.000Z",
      "last_response_time_ms": 84,
      "last_status_code": 200,
      "category_name": "Core services",
      "category_description": "",
      "category_order": 1,
      "stats": {
        "timezone": "America/Sao_Paulo",
        "uptimeToday": 100,
        "uptime24h": 100,
        "uptime7d": 99.98,
        "uptime30d": 99.96,
        "uptime90d": 99.97,
        "avgResponse24h": 93,
        "avgResponse90d": 102,
        "checks24h": 240,
        "checks90d": 21600,
        "bars90d": []
      }
    }
  ]
}

Examples

const { monitors } = await fetch('/api/status/monitors').then((r) => r.json());

for (const monitor of monitors) {
  console.log(`${monitor.name}: ${monitor.status} (${monitor.stats?.uptime30d ?? 'n/a'}% 30d)`);
}
import requests

monitors = requests.get("https://status.worthclient.com/api/status/monitors").json()["monitors"]

for monitor in monitors:
    uptime = monitor["stats"]["uptime30d"] or "n/a"
    print(f"{monitor['name']}: {monitor['status']} ({uptime}% 30d)")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(
        URI.create("https://status.worthclient.com/api/status/monitors"))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type Monitor struct {
	Name   string `json:"name"`
	Status string `json:"status"`
	Stats  struct {
		Uptime30d *float64 `json:"uptime30d"`
	} `json:"stats"`
}

func main() {
	resp, _ := http.Get("https://status.worthclient.com/api/status/monitors")
	defer resp.Body.Close()

	var payload struct {
		Monitors []Monitor `json:"monitors"`
	}
	json.NewDecoder(resp.Body).Decode(&payload)

	for _, monitor := range payload.Monitors {
		fmt.Println(monitor.Name, monitor.Status)
	}
}
curl https://status.worthclient.com/api/status/monitors
GET /api/status/monitors/{id} Single monitor detail

Returns one public monitor with its stats, the 30 most recent checks, the 10 most recent incidents and the 10 most recent maintenance windows that affect it.

Path parameters

Parameter Type Description
id string The monitor ID (UUID). Only monitors with show_on_public enabled can be fetched.

Response 200 OK

Response · application/json
{
  "monitor": {
    "id": "01j1f2a3b4c5d6e7f8a9b0c2e",
    "name": "Main Website",
    "url": "https://worthclient.com",
    "status": "up",
    "enabled": true,
    "show_on_public": true,
    "show_response_chart": true,
    "last_checked_at": "2026-08-18T11:59:46.000Z",
    "last_response_time_ms": 84,
    "last_status_code": 200,
    "ssl_valid_until": "2026-11-02T00:00:00.000Z",
    "stats": {
      "timezone": "America/Sao_Paulo",
      "uptime24h": 100,
      "uptime7d": 99.98,
      "uptime30d": 99.96,
      "avgResponse24h": 93,
      "checks24h": 240,
      "bars90d": []
    },
    "checks": [
      {
        "checked_at": "2026-08-18T11:59:46.000Z",
        "status": "up",
        "status_code": 200,
        "response_time_ms": 84,
        "error": null
      }
    ],
    "incidents": [
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c3f",
        "title": "Slow responses on Main Website",
        "severity": "degraded",
        "status": "resolved",
        "created_at": "2026-08-12T14:00:00.000Z",
        "updated_at": "2026-08-12T16:30:00.000Z",
        "resolved_at": "2026-08-12T16:30:00.000Z"
      }
    ],
    "maintenance": [
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c4g",
        "title": "Database upgrade",
        "status": "scheduled",
        "starts_at": "2026-08-20T02:00:00.000Z",
        "ends_at": "2026-08-20T04:00:00.000Z"
      }
    ]
  }
}

Field reference:

  • checks: the 30 most recent checks, newest first. status is up, down or degraded.
  • incidents / maintenance: the 10 most recent related events, newest first.
  • ssl_valid_until is only present when SSL monitoring is active for the monitor.

Errors

Status Body When
404 {"error": "Monitor not found."} The ID does not exist, or the monitor is not public.

Examples

const MONITOR_ID = '01j1f2a3b4c5d6e7f8a9b0c2e';

const { monitor } = await fetch(`/api/status/monitors/${MONITOR_ID}`).then((r) => r.json());

console.log(monitor.stats.uptime90d);   // 90-day uptime percentage
console.log(monitor.checks[0]);         // most recent check
import requests

monitor_id = "01j1f2a3b4c5d6e7f8a9b0c2e"
monitor = requests.get(
    f"https://status.worthclient.com/api/status/monitors/{monitor_id}"
).json()["monitor"]

print(monitor["stats"]["uptime90d"])
print(monitor["checks"][0])
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String monitorId = "01j1f2a3b4c5d6e7f8a9b0c2e";

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(
        "https://status.worthclient.com/api/status/monitors/" + monitorId))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	const monitorID = "01j1f2a3b4c5d6e7f8a9b0c2e"

	resp, err := http.Get(
		"https://status.worthclient.com/api/status/monitors/" + monitorID)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status/monitors/01j1f2a3b4c5d6e7f8a9b0c2e
GET /api/status/monitors/{id}/chart Response-time chart series

Returns a response-time series for a monitor, bucketed for the requested period. Perfect for rendering a latency chart.

Query parameters

Parameter Type Default Description
period string h24 One of h24 (1-minute buckets), d7 (5-minute buckets) or d30 (15-minute buckets).

Response 200 OK

Response · application/json
{
  "period": "h24",
  "bucketSeconds": 60,
  "buckets": [
    {
      "from": "2026-08-18T10:59:00.000Z",
      "to": "2026-08-18T11:00:00.000Z",
      "value": 95
    },
    {
      "from": "2026-08-18T11:00:00.000Z",
      "to": "2026-08-18T11:01:00.000Z",
      "value": null
    }
  ]
}

value is the average response time in milliseconds for the bucket, or null when no check happened in that window. Bucket counts: h24 → 1440, d7 → 2016, d30 → 2880.

Errors

Status Body When
404 {"error": "Monitor not found."} The ID does not exist, or the monitor is not public.

Examples

const MONITOR_ID = '01j1f2a3b4c5d6e7f8a9b0c2e';

const chart = await fetch(`/api/status/monitors/${MONITOR_ID}/chart?period=d7`)
  .then((r) => r.json());

console.log(chart.period, chart.bucketSeconds, 's buckets');
console.log(chart.buckets.filter((b) => b.value !== null).length, 'samples');
import requests

monitor_id = "01j1f2a3b4c5d6e7f8a9b0c2e"
chart = requests.get(
    f"https://status.worthclient.com/api/status/monitors/{monitor_id}/chart",
    params={"period": "d7"},
).json()

print(chart["period"], chart["bucketSeconds"])
print(len([b for b in chart["buckets"] if b["value"] is not None]), "samples")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String monitorId = "01j1f2a3b4c5d6e7f8a9b0c2e";

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(
        "https://status.worthclient.com/api/status/monitors/" + monitorId
        + "/chart?period=d7"))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	const monitorID = "01j1f2a3b4c5d6e7f8a9b0c2e"

	resp, _ := http.Get(
		"https://status.worthclient.com/api/status/monitors/" + monitorID + "/chart?period=d7")
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl "https://status.worthclient.com/api/status/monitors/01j1f2a3b4c5d6e7f8a9b0c2e/chart?period=d7"
GET /api/status/incidents List all public incidents

Returns all incidents, most recent first, each with the monitors affected by it.

Response 200 OK

Response · application/json
{
  "incidents": [
    {
      "id": "01j1f2a3b4c5d6e7f8a9b0c3f",
      "monitor_id": "01j1f2a3b4c5d6e7f8a9b0c2e",
      "title": "Slow responses on Main Website",
      "message": "Increased latency observed on the main website.",
      "severity": "degraded",
      "status": "resolved",
      "created_at": "2026-08-12T14:00:00.000Z",
      "updated_at": "2026-08-12T16:30:00.000Z",
      "resolved_at": "2026-08-12T16:30:00.000Z",
      "monitors": [
        {
          "id": "01j1f2a3b4c5d6e7f8a9b0c2e",
          "name": "Main Website"
        }
      ],
      "monitor": {
        "id": "01j1f2a3b4c5d6e7f8a9b0c2e",
        "name": "Main Website"
      }
    }
  ]
}

Field reference:

  • severity: incident, degraded or major.
  • status: open, monitoring or resolved — an incident is active while status !== "resolved".
  • monitors: every affected monitor; an empty array means all services. monitor is a convenience alias for the first entry (or null).
  • This list endpoint does not include the updates timeline — use GET /api/status/incidents/{id} for that.

Examples

const { incidents } = await fetch('/api/status/incidents').then((r) => r.json());

const active = incidents.filter((i) => i.status !== 'resolved');
console.log(`${active.length} active incident(s)`);
console.log(incidents[0]?.title);
import requests

incidents = requests.get("https://status.worthclient.com/api/status/incidents").json()["incidents"]

active = [i for i in incidents if i["status"] != "resolved"]
print(len(active), "active incident(s)")
print(incidents[0]["title"] if incidents else "none")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(
        URI.create("https://status.worthclient.com/api/status/incidents"))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	resp, _ := http.Get("https://status.worthclient.com/api/status/incidents")
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status/incidents
GET /api/status/incidents/{id} Single incident with timeline

Returns one incident including its full update timeline (each update has a status, title, message and timestamp).

Path parameters

Parameter Type Description
id string The incident ID (UUID).

Response 200 OK

Response · application/json
{
  "incident": {
    "id": "01j1f2a3b4c5d6e7f8a9b0c3f",
    "monitor_id": "01j1f2a3b4c5d6e7f8a9b0c2e",
    "title": "Slow responses on Main Website",
    "message": "Increased latency observed on the main website.",
    "severity": "degraded",
    "status": "resolved",
    "created_at": "2026-08-12T14:00:00.000Z",
    "updated_at": "2026-08-12T16:30:00.000Z",
    "resolved_at": "2026-08-12T16:30:00.000Z",
    "monitors": [],
    "monitor": null,
    "updates": [
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c5h",
        "incident_id": "01j1f2a3b4c5d6e7f8a9b0c3f",
        "status": "investigating",
        "title": "Investigating",
        "message": "We are looking into the issue.",
        "created_at": "2026-08-12T14:00:00.000Z"
      },
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c6i",
        "incident_id": "01j1f2a3b4c5d6e7f8a9b0c3f",
        "status": "monitoring",
        "title": "Monitoring",
        "message": "Traffic shifted; latency back to normal.",
        "created_at": "2026-08-12T15:45:00.000Z"
      },
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c7j",
        "incident_id": "01j1f2a3b4c5d6e7f8a9b0c3f",
        "status": "resolved",
        "title": "Resolved",
        "message": "All clear.",
        "created_at": "2026-08-12T16:30:00.000Z"
      }
    ]
  }
}

Timeline status values: investigating, identified, monitoring or resolved.

Errors

Status Body When
404 {"error": "Incident not found."} The ID does not exist.

Examples

const INCIDENT_ID = '01j1f2a3b4c5d6e7f8a9b0c3f';

const { incident } = await fetch(`/api/status/incidents/${INCIDENT_ID}`).then((r) => r.json());

for (const update of incident.updates) {
  console.log(update.created_at, update.status, '-', update.message);
}
import requests

incident_id = "01j1f2a3b4c5d6e7f8a9b0c3f"
incident = requests.get(
    f"https://status.worthclient.com/api/status/incidents/{incident_id}"
).json()["incident"]

for update in incident["updates"]:
    print(update["created_at"], update["status"], "-", update["message"])
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String incidentId = "01j1f2a3b4c5d6e7f8a9b0c3f";

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(
        "https://status.worthclient.com/api/status/incidents/" + incidentId))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	const incidentID = "01j1f2a3b4c5d6e7f8a9b0c3f"

	resp, _ := http.Get(
		"https://status.worthclient.com/api/status/incidents/" + incidentID)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status/incidents/01j1f2a3b4c5d6e7f8a9b0c3f
GET /api/status/maintenance List all public maintenance windows

Returns all maintenance windows, most recent first, each with the monitors affected by it.

Response 200 OK

Response · application/json
{
  "maintenance": [
    {
      "id": "01j1f2a3b4c5d6e7f8a9b0c4g",
      "monitor_id": null,
      "title": "Database upgrade",
      "message": "We are upgrading the database cluster.",
      "status": "scheduled",
      "starts_at": "2026-08-20T02:00:00.000Z",
      "ends_at": "2026-08-20T04:00:00.000Z",
      "created_at": "2026-08-15T10:00:00.000Z",
      "updated_at": "2026-08-15T10:00:00.000Z",
      "monitors": [],
      "monitor": null
    }
  ]
}

status values: scheduled, in_progress, completed or cancelled. An empty monitors array means the maintenance affects all services.

Examples

const { maintenance } = await fetch('/api/status/maintenance').then((r) => r.json());

const upcoming = maintenance.filter((m) => m.status === 'scheduled');
console.log(`${upcoming.length} scheduled window(s)`);
console.log(upcoming[0]?.starts_at);
import requests

maintenance = requests.get("https://status.worthclient.com/api/status/maintenance").json()["maintenance"]

upcoming = [m for m in maintenance if m["status"] == "scheduled"]
print(len(upcoming), "scheduled window(s)")
print(upcoming[0]["starts_at"] if upcoming else "none")
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(
        URI.create("https://status.worthclient.com/api/status/maintenance"))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	resp, _ := http.Get("https://status.worthclient.com/api/status/maintenance")
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status/maintenance
GET /api/status/maintenance/{id} Single maintenance window with timeline

Returns one maintenance window including its full update timeline.

Path parameters

Parameter Type Description
id string The maintenance ID (UUID).

Response 200 OK

Response · application/json
{
  "maintenance": {
    "id": "01j1f2a3b4c5d6e7f8a9b0c4g",
    "monitor_id": null,
    "title": "Database upgrade",
    "message": "We are upgrading the database cluster.",
    "status": "scheduled",
    "starts_at": "2026-08-20T02:00:00.000Z",
    "ends_at": "2026-08-20T04:00:00.000Z",
    "created_at": "2026-08-15T10:00:00.000Z",
    "updated_at": "2026-08-15T10:00:00.000Z",
    "monitors": [],
    "monitor": null,
    "updates": [
      {
        "id": "01j1f2a3b4c5d6e7f8a9b0c8k",
        "maintenance_id": "01j1f2a3b4c5d6e7f8a9b0c4g",
        "status": "scheduled",
        "title": "Scheduled",
        "message": "Maintenance window created.",
        "created_at": "2026-08-15T10:00:00.000Z"
      }
    ]
  }
}

Errors

Status Body When
404 {"error": "Maintenance not found."} The ID does not exist.

Examples

const MAINTENANCE_ID = '01j1f2a3b4c5d6e7f8a9b0c4g';

const { maintenance } = await fetch(`/api/status/maintenance/${MAINTENANCE_ID}`).then((r) => r.json());

console.log(maintenance.title, maintenance.status);
console.log(maintenance.starts_at, '->', maintenance.ends_at);
import requests

maintenance_id = "01j1f2a3b4c5d6e7f8a9b0c4g"
maintenance = requests.get(
    f"https://status.worthclient.com/api/status/maintenance/{maintenance_id}"
).json()["maintenance"]

print(maintenance["title"], maintenance["status"])
print(maintenance["starts_at"], "->", maintenance["ends_at"])
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String maintenanceId = "01j1f2a3b4c5d6e7f8a9b0c4g";

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(
        "https://status.worthclient.com/api/status/maintenance/" + maintenanceId))
        .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	const maintenanceID = "01j1f2a3b4c5d6e7f8a9b0c4g"

	resp, _ := http.Get(
		"https://status.worthclient.com/api/status/maintenance/" + maintenanceID)
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}
curl https://status.worthclient.com/api/status/maintenance/01j1f2a3b4c5d6e7f8a9b0c4g
GET /api/status/badge.svg Embeddable status badge

Returns an SVG badge with the current overall status, ready to embed in any website, README or forum signature. Cached for 60 seconds.

Query parameters

Parameter Type Default Description
lang string pt Badge label language: pt, en or es.
format string svg Image format: svg, png, jpg, jpeg, webp or avif. The format also works as a file extension: /api/status/badge.png, /api/status/badge.webp, etc.

Response 200 OK

Content type image/svg+xml. The dot color follows the overall status:

Status Color
up#35d66b green
degraded#f5b942 yellow
down / incident#ff5a65 red
maintenance#5865f2 indigo
unknown#8ea0b8 gray

Usage in HTML:

HTML
<img src="https://status.worthclient.com/api/status/badge.svg?lang=en"
     alt="WorthClient Status">

Usage in Markdown (any format works — raster formats are rasterized server-side):

Markdown
![WorthClient Status](https://status.worthclient.com/api/status/badge.svg?lang=en)

# PNG badge (same for .jpg, .jpeg, .webp, .avif)
![WorthClient Status](https://status.worthclient.com/api/status/badge.png)
GET /api/status/favicon.svg Status-aware favicon

Returns the app icon as SVG with a status-colored dot in the bottom-right corner — the same favicon used by the public page itself. Cached for 30 seconds.

Query parameters

Parameter Type Default Description
format string svg Image format: svg, png, jpg, jpeg, webp or avif. Also works as a file extension: /api/status/favicon.png, etc.

Response 200 OK

Content type image/svg+xml, 192×192. The dot color follows the same mapping as the badge.

Notifications API

Web Push and Server-Sent Events for real-time status updates. These endpoints are public so that any visitor can subscribe to notifications without an account.

GET /api/notifications/vapid-public-key Web Push VAPID key

Returns the public VAPID key used to subscribe browsers to push notifications.

Response 200 OK

Response · application/json
{
  "enabled": true,
  "publicKey": "BNcHxJ9sYJ4wC1n0q3r5t7v9x11z13b15d17f19h21j23l25m27o29q31s33u35w37y39a41"
}
GET /api/notifications/events Real-time event stream (SSE)

Opens a Server-Sent Events stream that pushes status notifications to connected clients. The connection stays open; the server sends a heartbeat every 25 seconds to keep it alive.

Event types

Event Data When
ready {"ok": true, "at": "…"} Sent immediately when the connection opens.
heartbeat {"at": "…"} Every 25 seconds to keep the connection alive.
notification Push notification payload, see below Whenever an incident or maintenance notification is published.

Notification payload

event: notification
{
  "title": "WorthClient Status",
  "body": "Incident: Slow responses on Main Website",
  "url": "/incidents/01j1f2a3b4c5d6e7f8a9b0c3f",
  "tag": "incident-01j1f2a3b4c5d6e7f8a9b0c3f",
  "icon": "/assets/icon-192.png",
  "badge": "/assets/icon-192.png",
  "requireInteraction": false
}

Client example

JavaScript
const events = new EventSource('/api/notifications/events');

events.addEventListener('ready', (event) => {
  console.log('Connected:', JSON.parse(event.data));
});

events.addEventListener('notification', (event) => {
  const payload = JSON.parse(event.data);
  console.log(payload.title, payload.body, payload.url);
});

events.addEventListener('heartbeat', () => {
  /* connection is alive */
});
POST /api/notifications/subscribe Subscribe to Web Push

Registers a browser push subscription so the visitor receives notifications about new incidents and maintenance. Requires HTTPS (or localhost) in the browser.

Request body

The full PushSubscription JSON from the browser, or wrapped in a subscription property. Fields:

Field Type Required Description
endpoint string yes Push service endpoint URL (https://).
keys.p256dh string yes Base64url-encoded public P-256 key.
keys.auth string yes Base64url-encoded auth secret.
contentEncoding string no aes128gcm (default) or aesgcm.

Client example

JavaScript
// 1. Get the VAPID public key
const { publicKey } = await fetch('/api/notifications/vapid-public-key').then((r) => r.json());

// 2. Ask the browser for a subscription
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(publicKey)
});

// 3. Register it with the API
await fetch('/api/notifications/subscribe', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(subscription)
});

Response 201 Created

Response · application/json
{
  "ok": true,
  "endpoint": "https://fcm.googleapis.com/fcm/send/..."
}

Errors

Status Body When
400 {"error": "Invalid notification subscription."} Missing or invalid endpoint, p256dh or auth.
DELETE /api/notifications/subscribe Unsubscribe from Web Push

Removes a previously registered push subscription.

Request body

Field Type Required Description
endpoint string no The endpoint to remove. The request succeeds even without it.

Response 200 OK

Response · application/json
{
  "ok": true
}

Data Model

Field-by-field reference for every object returned by the public API.

OBJECT overall Overall service status

Present in GET /api/status. The status is computed with a fixed priority order: an in-progress maintenance beats incidents, incidents beat down monitors, and so on.

Field Type Description
codestringMachine-readable status: up, down, incident, degraded, maintenance or unknown.
labelstringShort human-readable label (in the configured language).
messagestringOne-sentence explanation.
OBJECT monitor A monitored service
Field Type Description
idstringMonitor ID (UUID).
namestringDisplay name.
urlstringMonitored URL.
statusstringup, down, degraded, paused or unknown.
enabledbooleanWhether checks are running.
show_on_publicbooleanAlways true — private monitors are never exposed.
show_response_chartbooleanWhether bars24h / responseTimes24h are included in stats.
category_idstring | nullCategory ID; null for the fallback "Other services" group.
category_name / category_descriptionstringCategory display data.
category_order / display_ordernumberSort positions.
last_checked_atstringISO 8601 timestamp of the last check.
last_response_time_msnumber | nullResponse time of the last check in milliseconds.
last_status_codenumber | nullHTTP status of the last check.
ssl_valid_untilstring | nullSSL certificate expiry (detail endpoint only).
statsobjectUptime statistics, see Monitor Stats.
OBJECT monitor.stats Uptime statistics
Field Type Description
timezonestringTimezone used for calendar-day boundaries (e.g. America/Sao_Paulo).
uptimeToday / uptime24h / uptime7d / uptime30d / uptime90dnumber | nullUptime percentage (0–100); null when there is not enough data.
checksToday / checks24h / checks90dnumberNumber of checks in the period.
avgResponseToday / avgResponse24h / avgResponse90dnumber | nullAverage response time in milliseconds.
bars90darray90 daily bars: { status, uptime, checks, downSeconds, degradedSeconds, date, timezone, from, to }. Bars may include incidents / maintenance annotation objects.
bars24harray48 half-hour bars (only when the response chart is enabled): { status, uptime, checks, downSeconds, degradedSeconds, from, to }.
responseTimes24harray48 average response times, one per half-hour (only when the response chart is enabled).
OBJECT incident Incident and its updates
Field Type Description
idstringIncident ID (UUID).
monitor_idstring | nullPrimary monitor, when the incident targets one.
titlestringShort title.
messagestringDescription (Markdown allowed).
severitystringincident, degraded or major.
statusstringopen, monitoring or resolved.
created_at / updated_at / resolved_atstring | nullISO 8601 timestamps; resolved_at is null while open.
monitorsarray[{ id, name }] of affected monitors; empty = all services.
monitorobject | nullFirst affected monitor, for convenience.
updatesarrayTimeline entries: { id, incident_id, status, title, message, created_at } with status one of investigating, identified, monitoring, resolved. Only present in GET /api/status/incidents/{id} and inside the /api/status payload.
OBJECT maintenance Maintenance window and its updates
Field Type Description
idstringMaintenance ID (UUID).
monitor_idstring | nullPrimary monitor, when the window targets one.
titlestringShort title.
messagestringDescription (Markdown allowed).
statusstringscheduled, in_progress, completed or cancelled.
starts_at / ends_atstringISO 8601 window boundaries.
created_at / updated_atstringISO 8601 timestamps.
monitorsarray[{ id, name }] of affected monitors; empty = all services.
monitorobject | nullFirst affected monitor, for convenience.
updatesarrayTimeline entries: { id, maintenance_id, status, title, message, created_at }. Only present in GET /api/status/maintenance/{id} and inside the /api/status payload.