Websites — API v1 and MCP | Nodea Documentation
The Websites module covers full management of monitored websites: listing, create, update, delete and operational actions (run now, pause, resume, mute, clone), plus test-run history, uptime statistics and maintenance windows. This document describes the REST API v1 layer (/api/v1) and the module's MCP tools.
Authentication and scopes
API v1 authenticates with a Passport personal access token via the Authorization: Bearer <token> header. Every route is guarded by a scope matching the operation tier:
monitoring:read— listing and detail (index, show, runs, uptime, maintenance-windows).monitoring:write— create, update and non-destructive actions (run, pause, resume, mute, clone, maintenance-window create/delete).monitoring:delete— permanently destroying the website.
The whole module is additionally gated by the Pennant flag features:websites — disabling it hides the module from both the web UI and the API. Rate limit: throttle:api-v1 = 240/min per token user.
Ownership and visibility
Row-level access is enforced by WebsitePolicy: view = owner or accepted FOREIGN share, manage = owner only. An id that exists but is not visible to the token yields 403 (policy); an unknown id yields 404 (route model binding). The run action is the exception: allowed on view (owner AND accepted share may trigger a re-check); the other state actions (pause/resume/mute/clone) require manage. Test parameters are masked — basic-auth passwords, credential-bearing headers and raw POST bodies never come back in cleartext (****+last-4 scheme), so even an accepted share never sees the owner's credentials.
API v1 — endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/websites | monitoring:read | List of websites (owned + accepted shares), paginated. |
| GET | /api/v1/websites/{website} | monitoring:read | Website detail with tests; meta.can_manage. |
| GET | /api/v1/websites/{website}/runs | monitoring:read | Test-run history (TestRun), newest first. |
| GET | /api/v1/websites/{website}/uptime | monitoring:read | Monthly uptime aggregates from website_uptimes. |
| GET | /api/v1/websites/{website}/maintenance-windows | monitoring:read | List of the website's maintenance windows. |
| POST | /api/v1/websites | monitoring:write | Create a website with its test set. Returns 201. |
| PUT/PATCH | /api/v1/websites/{website} | monitoring:write | Update name/URL, upsert tests, sync notification groups and excluded run servers. |
| POST | /api/v1/websites/{website}/run | monitoring:write | Manually dispatch probes (HEALTH_CHECK/SSL/WHOIS). 202/422/503. |
| POST | /api/v1/websites/{website}/pause | monitoring:write | Idempotent pause (sets paused_at). |
| POST | /api/v1/websites/{website}/resume | monitoring:write | Idempotent resume (clears paused_at). |
| POST | /api/v1/websites/{website}/mute | monitoring:write | Toggle paused_at null↔now (silences notifications). |
| POST | /api/v1/websites/{website}/clone | monitoring:write | Clone the website for the token user. Returns 201. |
| POST | /api/v1/websites/{website}/maintenance-windows | monitoring:write | Create a maintenance window. Returns 201. |
| DELETE | /api/v1/websites/{website}/maintenance-windows/{maintenanceWindow} | monitoring:write | Delete a maintenance window (scoped binding). Returns 204. |
| DELETE | /api/v1/websites/{website} | monitoring:delete | Permanently delete the website (cascade). Returns 204. |
GET /websites — query parameters
search(string, optional, max 255) — matched against name and URL.status(enum:paused|healthy|unhealthy|skipped, optional).sort(enum:name|url|created_at, defaultcreated_at).direction(asc|desc).per_page(int 1–100, default 20),page(int ≥1).
Response: a WebsiteResource collection — fields: id, name, url, status (paused→unhealthy→skipped→healthy), paused_at, ownership (owner|shared), tests[], created_at, updated_at. Each test: id, type (enum backing value), type_name, interval, state, state_name, parameters (masked), last_run.
POST/PATCH — body parameters
url(string, required,SafePublicUrlrule).name(string, required, max 255).intervals(object keyed by TestType backing value; value: seconds, 0–259200).parameters(per-type object; optional — omitting a type keeps its stored config).notificationGroupsId[]— must belong to the token user (otherwise422).excludedRunServersId[]— existingrun_servers.id.
Test types (backing value): 1=HEALTH_CHECK, 2=SSL, 4=WHOIS, 16=TCP, 32=DNS, 64=PING, 128=SMTP, 256=POP3, 512=IMAP, 1024=UDP.
Error codes
401— missing/invalid token.403— missing required scope or policy (existing, foreign resource without access).404— unknown id.422— validation (e.g. no runnable test onrun, codeno_runnable_test).503— no healthy run server available to execute probes (codeno_run_server_available).
Examples — API
List websites filtered by status
curl -s https://app.nodea.io/api/v1/websites?status=unhealthy&per_page=10 \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Create a website with a 60s health check
curl -s -X POST https://app.nodea.io/api/v1/websites \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Shop",
"url": "https://shop.example.com",
"intervals": { "1": 60 },
"parameters": { "1": { "timeout": 10, "status_codes": "200" } }
}'
Manually dispatch probes
curl -s -X POST https://app.nodea.io/api/v1/websites/{id}/run \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "test_id": 123 }'
MCP — tools
MCP tools share the auth and scope model with API v1 (Bearer + scope checked in ensureScope) and the features:websites flag (a tool on an inactive flag disappears from tools/list). Resource ids are UUIDs passed as strings. A missing and a foreign id raise the same error (no cross-tenant enumeration).
| Tool | Scope | Type | Description |
|---|---|---|---|
| websites-list | monitoring:read | read-only | List websites; filters search, status (paused/healthy/unhealthy), page, per_page (max 50). |
| website-get | monitoring:read | read-only | Website detail with tests. Param: id. |
| website-create | monitoring:write | write | Create a website; name, url, tests[] (type/interval/parameters, min 1). |
| website-update | monitoring:write | idempotent | Update name/url + upsert tests[]. Owner only. |
| website-delete | monitoring:delete | destructive | Permanent delete; requires confirm: true. |
| website-run-now | monitoring:write | write | Dispatch probes; optional test_type. Returns dispatched/skipped. |
| website-set-paused | monitoring:write | idempotent | Pause/resume via paused: true|false (not a toggle). |
| website-runs | monitoring:read | read-only | Run history; test_type, page, per_page (max 50). |
| website-uptime | monitoring:read | read-only | Monthly uptime; months (default 12, max 60). |
| website-maintenance-windows | monitoring:read | read-only | List maintenance windows with an active_now flag. |
| website-maintenance-window-create | monitoring:write | write | Create a window; starts_at/ends_at (wall-clock in timezone), recurrence_type, weekdays. |
| website-maintenance-window-delete | monitoring:write | destructive (annotation) | Delete a window by id. Scope stays write. |
Example — MCP (JSON-RPC)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "website-run-now",
"arguments": { "id": "9b1f...uuid", "test_type": 1 }
}
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "website-set-paused",
"arguments": { "id": "9b1f...uuid", "paused": true }
}
}