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
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/status-pages | monitoring:read | List of the user's status pages (with a website count), paginated. |
| GET | /api/v1/status-pages/{statusPage} | monitoring:read | Detail with the list of attached websites. |
| POST | /api/v1/status-pages | monitoring:write | Create a status page. Returns 201. |
| PUT/PATCH | /api/v1/status-pages/{statusPage} | monitoring:write | Update (partial) + website sync when selectedWebsiteIds is provided. |
| DELETE | /api/v1/status-pages/{statusPage} | monitoring:delete | Permanent 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.
| Tool | Scope | Type | Description |
|---|---|---|---|
| status-pages-list | monitoring:read | read-only | List of status pages (newest first); page, per_page (max 50). |
| status-page-get | monitoring:read | read-only | Detail with the ordered list of websites. Param: id. |
| status-page-create | monitoring:write | write | title, slug, description, accent_color (default #50CD89), logo_url, is_public (default false), website_ids[] (owned only). |
| status-page-update | monitoring:write | idempotent | Partial update; website_ids[] replaces the full list (owned only). |
| status-page-delete | monitoring:delete | destructive | Permanent 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 }
}
}