Nodea — logo

Status Pages — API v1 and MCP | Nodea Documentation

The Status Pages module handles public status pages: a public view of the state of selected monitored websites at /status/{slug}. API v1 provides full CRUD over status-page definitions. The public renderer (/status/{slug}) is a separate, unauthenticated web route and is not part of this API.

Authentication, scopes and flag

Passport Bearer token. Scopes: monitoring:read (index, show), monitoring:write (create, update), monitoring:delete (delete). The module is gated by the features:websites flag (status pages ride the same flag as websites).

Ownership — no policy

StatusPage has no policy class. Access is owner-only — every row is scoped to user_id = token user. A foreign or unknown id consistently yields 404 (never confirming another tenant's resource exists). Additionally, websites attached to a status page must be owned by the user (OWNER pivot) — a FOREIGN share does not qualify, because a status page exposes the website publicly. A foreign website_id → 422 (the API raises an error instead of silently dropping it).

API v1 — endpoints

MethodPathScopeDescription
GET/api/v1/status-pagesmonitoring:readList of the user's status pages (with a website count), paginated.
GET/api/v1/status-pages/{statusPage}monitoring:readDetail with the list of attached websites.
POST/api/v1/status-pagesmonitoring:writeCreate a status page. Returns 201.
PUT/PATCH/api/v1/status-pages/{statusPage}monitoring:writeUpdate (partial) + website sync when selectedWebsiteIds is provided.
DELETE/api/v1/status-pages/{statusPage}monitoring:deletePermanent delete. Returns 204.

GET /status-pages — query parameters

  • per_page (int 1–100, default 20), page.

POST/PATCH — body parameters

  • title (string, required, max 120).
  • slug (string, required, max 80, regex ^[a-z0-9-]+$, unique; on update the page's own slug is exempt).
  • description (string, optional, max 1000).
  • accent_color (required, regex ^#[0-9A-Fa-f]{6}$).
  • logo_url (url, optional, max 500).
  • is_public (boolean; on a partial PATCH, omitting it leaves visibility unchanged).
  • selectedWebsiteIds[] — ids of websites owned by the user (OWNER pivot); list order = display order (display_order).

Response shape (StatusPageResource)

Fields: id, slug, title, description, accent_color, logo_url, is_public, public_url (always resolvable — a private page simply 404s for visitors), website_ids[], websites[] ({id,name}, only when the relation is loaded), websites_count, created_at, updated_at.

Error codes

  • 401 — missing/invalid token; 403 — missing scope or inactive flag; 404 — unknown or foreign id; 422 — validation (non-unique slug, bad color, foreign/unowned website_id).

Examples — API

Create a status page

curl -s -X POST https://app.nodea.io/api/v1/status-pages \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Service status",
    "slug": "service-status",
    "accent_color": "#50CD89",
    "is_public": true,
    "selectedWebsiteIds": ["9b1f...uuid", "7c22...uuid"]
  }'

Publish an existing page (PATCH)

curl -s -X PATCH https://app.nodea.io/api/v1/status-pages/{id} \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Service status", "slug": "service-status", "accent_color": "#50CD89", "is_public": true }'

MCP — tools

Same auth/scope model as API v1 and the features:websites flag. Resolution by id is owner-scoped (no policy); a missing and a foreign id raise the same error. Unlike the web (which silently drops foreign ids), the MCP tools reject any website_id the user does not own, so a forged list can never expose another tenant's website publicly.

ToolScopeTypeDescription
status-pages-listmonitoring:readread-onlyList of status pages (newest first); page, per_page (max 50).
status-page-getmonitoring:readread-onlyDetail with the ordered list of websites. Param: id.
status-page-createmonitoring:writewritetitle, slug, description, accent_color (default #50CD89), logo_url, is_public (default false), website_ids[] (owned only).
status-page-updatemonitoring:writeidempotentPartial update; website_ids[] replaces the full list (owned only).
status-page-deletemonitoring:deletedestructivePermanent delete; requires confirm: true.

Output (status-page-*): id, slug, title, description, accent_color, logo_url, is_public, public_url, websites_count (when counted), websites[] ({id,name,url,display_order,visible}, when loaded), created_at, updated_at.

Example — MCP (JSON-RPC)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "status-page-create",
    "arguments": {
      "title": "Service status",
      "slug": "service-status",
      "is_public": true,
      "website_ids": ["9b1f...uuid", "7c22...uuid"]
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "status-page-delete",
    "arguments": { "id": "5d40...uuid", "confirm": true }
  }
}