API
The Norcube Monitor API the dashboard and the CLI use. Authentication and scopes, the endpoints, the RFC 9457 error shape with its codes and reasons, rate limits and pagination.
Everything the dashboard does goes through this API, and the
CLI calls it too. Endpoints live under
https://monitor.api.norcube.com/app/v1. The authoritative reference is
the live Swagger UI at /app/v1/swagger/ on that host; this page
documents the conventions.
Authentication
Authorization: Bearer <JWT>Access tokens come from Norcube Auth with monitor as the audience; see
Platform → Tokens. For automation,
exchange a CI token for an access token. A CI
token is scoped: monitor.read, monitor.check, monitor.write,
monitor.channels or monitor.statuspages (the CLI page
has the table). A request outside the token's scopes is
403 FORBIDDEN with reason INSUFFICIENT_SCOPE; the detail names the
scope that would allow it. Every request is also checked against the
caller's IAM permissions.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /monitors | List. Filters type, status (overall status, paused included), q; sort (status, name, lastChecked, uptime, created); include=history adds the last 60 results per row. |
POST | /monitors | Create (http, ssl or domain). Every check setting is required, no hidden defaults. The first check runs at once. |
POST | /monitors/bulk | {action: pause | resume | delete, monitorIds: [...]}, 1 to 100 ids. One result per id; not atomic. |
GET, PATCH, DELETE | /monitors/{id} | Settings and status (secret header values are never returned); partial update, a settings object replaces settings as a whole; delete with history. |
POST | /monitors/{id}/pause, /resume | Idempotent. Pausing closes the open incident quietly; resume checks right away unless the last check is less than an interval old. |
POST | /monitors/{id}/check | On-demand check, stored and billed like a scheduled one. |
GET | /monitors/{id}/results | Raw checks, from/to (RFC 3339, default last 24 h), cursor paging. |
GET | /monitors/{id}/stats | range=24h|7d|30d|90d: uptime, average and maximum latency, hourly or daily series. |
GET | /monitors/{id}/status-changes | Every status change, newest first, with duration, condition, HTTP status, message and the events in its span. Kept 1 year. |
GET | /monitors/{id}/availability | days (1 to 90) UTC days ending today: counts, uptime, estimated downtime, noData. |
GET | /monitors/{id}/certificate | The newest certificate seen (SSL and HTTPS monitors), with thresholds and the next threshold for SSL monitors. 404 when none was seen. |
GET, PUT | /monitors/{id}/alerts | Confirmations, reminder interval and routes. useDefaults: true returns to the organization defaults; routes: [] mutes. |
GET | /incidents | Open first (critical, then warning), then resolved. Filters status, monitorId, severity, aspect, acknowledged, q, from, to. |
GET | /incidents/summary | Open counts by severity and unacknowledged, resolved in the last 7 and 30 days, mean time to resolve over 30 days. |
GET | /incidents/{id} | Timeline and every alert sent, with status, attempts, last error and next attempt. |
POST | /incidents/{id}/acknowledge | Stops reminders. Idempotent. |
GET, POST | /alert-channels | List and create. A webhook's signing secret is returned on create only. |
GET, PATCH, DELETE | /alert-channels/{id} | Credentials are never returned; rotateSecret: true returns a new webhook secret; a new address or phone number needs verifying again. |
POST | /alert-channels/{id}/verify, /verify/confirm | Send a 6-digit code, confirm it. |
GET | /alert-channels/{id}/deliveries | The channel's delivery log, newest first, filter status=pending|sent|dead. Kept 90 days. |
POST | /alert-channels/{id}/test | Send a test now and return the provider's answer. |
GET, POST | /status-pages | List, newest first (monitorId lists the pages showing that monitor instead), and create. A new page starts unpublished; every setting is required. |
GET, PATCH, DELETE | /status-pages/{id} | A published page's new slug leaves the old one redirecting for 30 days; delete takes the page offline. |
POST | /status-pages/{id}/publish, /unpublish | Idempotent. Publishing needs a component. |
GET, PUT | /status-pages/{id}/layout | PUT replaces the layout as a whole, keeping the ids you send. |
GET, PUT, DELETE | /status-pages/{id}/logo | The logo. PUT replaces it as a whole. |
GET, PUT, DELETE | /status-pages/{id}/domain | The custom domain and the DNS records it needs. PUT sets hostname. |
GET | /status-pages/{id}/preview | The public snapshot and history, published or not. |
POST | /status-pages/{id}/preview-link | A signed link to the rendered page, valid for 15 minutes. |
GET, POST | /status-pages/{id}/incidents | Posted incidents, filter status=open|resolved, cursor paging; post one. |
GET, PATCH | /status-pages/{id}/incidents/{incidentId} | PATCH corrects the title, impact, start, components and links. The status moves by posting an update. |
POST | /status-pages/{id}/incidents/{incidentId}/updates | Its status becomes the incident's; resolved ends it. |
Status pages have their own guide. The public pages themselves are served separately; see Public data.
Conventions
JSON
Request and response fields are camelCase. Bodies are decoded strictly:
an unknown member, a member named twice, or a value of the wrong type is
a validation error, not ignored. Secrets (header values, Slack and
Discord URLs, webhook secrets) are write-only; a Slack or Discord URL is
echoed back only as a short hint.
Pagination
List endpoints use cursor pagination: limit and cursor in, a
next cursor out, absent at the end. limit is at most 100 (200 for
check results). A monitor list cursor carries its sort; a cursor used
with another sort is 400 INVALID_PAYLOAD.
Errors
Every error is an RFC 9457
problem (application/problem+json):
{
"type": "https://docs.norcube.com/errors/limit-reached",
"title": "Limit reached",
"status": 403,
"detail": "Your organization can have at most 10 monitors.",
"instance": "urn:uuid:3f2c8a8e-6c1d-4a59-9a43-2b1f0f6f9d10",
"code": "LIMIT_REACHED",
"reason": "MONITOR_LIMIT",
"data": { "limit": "10" },
"timestamp": "2026-11-04T12:00:00Z"
}Switch on code (and reason where you act on the finer cause), never
on detail, which is an English sentence for people. instance is the
request id, also returned as the X-Request-ID header.
| Status | code | Meaning |
|---|---|---|
| 400 | INVALID_PAYLOAD | The body isn't JSON, or a query parameter is malformed. |
| 400 | VALIDATION_FAILED | A field is wrong; errors lists each one (below). Reason CHANNEL_TYPE_UNAVAILABLE: the channel kind isn't offered. |
| 401 | UNAUTHORIZED | No valid token. |
| 402 | ORG_BLOCKED | The organization is blocked for unpaid invoices. On-demand checks, new monitors, test sends, codes, creating or publishing status pages, and setting a custom domain are refused. |
| 403 | FORBIDDEN | IAM refused, or reason INSUFFICIENT_SCOPE for a CI token. |
| 403 | LIMIT_REACHED | A limit, with the reason MONITOR_LIMIT, ALERT_CHANNEL_LIMIT, UNVERIFIED_CHANNEL_LIMIT, STATUS_PAGE_LIMIT or STATUS_PAGE_DOMAIN_LIMIT. |
| 404 | NOT_FOUND | No such resource, another organization's resource, a malformed id, or no such route. Reason DOMAIN_NOT_SET: the status page has no custom domain. |
| 405 | METHOD_NOT_ALLOWED | |
| 409 | ALREADY_EXISTS | |
| 409 | CONFLICT | The resource's state refuses it, with reasons such as CODE_EXPIRED, CODE_RECENTLY_SENT, ALREADY_VERIFIED, SLUG_TAKEN, DOMAIN_TAKEN, DOMAINS_DISABLED, INCIDENT_RESOLVED. |
| 409 | MANAGED_RESOURCE | The monitor belongs to another Norcube service's resource (a DomainRadar saved domain) and can't be deleted or re-targeted here. |
| 413 | PAYLOAD_TOO_LARGE | |
| 429 | TOO_MANY_REQUESTS | A rate limit; see below. |
| 500 | INTERNAL_ERROR | |
| 503 | UNAVAILABLE | A dependency is unreachable. Retry. |
New codes and reasons are added over time; treat an unknown one by its HTTP status. Error codes explains each code and what to do about it.
Field errors
A VALIDATION_FAILED problem lists every problem in errors:
{
"pointer": "/http/timeoutMs",
"rule": "range",
"params": { "min": 1000, "max": 30000 },
"detail": "must be between 1000 and 30000"
}pointer is the field's JSON Pointer (/http/headers/2/value; query
parameters are top level, /from), rule one of required, range,
maxLength, oneOf, format, public, unknown, duplicate,
type, unsupported and a few more, and params the rule's bounds or
values. Show detail, or build your own message from rule and
params; treat a rule you don't know as a generic invalid value.
Rate limits
Endpoints that cost a real request are limited per user and per
organization, per hour. A refused request is 429 TOO_MANY_REQUESTS
with Retry-After, and every limited response carries the IETF
RateLimit-Policy and RateLimit headers.
| Endpoints | Per user / h | Per organization / h |
|---|---|---|
POST /monitors, /monitors/{id}/resume, /monitors/{id}/check, /monitors/bulk | 200 | 600 |
POST /alert-channels/{id}/verify, /alert-channels/{id}/test | 20 | 60 |
POST /alert-channels/{id}/verify/confirm | 20 | 60 |
Limits allow a burst of the whole budget, then one request every
3600/n seconds. The user limit is checked first, so a user who is over
their limit never spends the organization's budget.
Limits
| Limit | Value |
|---|---|
| Monitors per organization | 10 by default (MONITOR_LIMIT) |
| Shortest check interval | 60 s by default; 30 s absolute |
| Longest check interval | 86,400 s |
| Domain monitor interval | at least 3,600 s |
| Alert channels per organization | 100 (ALERT_CHANNEL_LIMIT) |
| Channels awaiting verification | 3 (UNVERIFIED_CHANNEL_LIMIT) |
| Routes per monitor | 20 |
| Status pages per organization | 1 by default (STATUS_PAGE_LIMIT) |
| Custom domains per organization | 10, a safety cap (STATUS_PAGE_DOMAIN_LIMIT) |
| SMS per organization and day | 10 by default |
| Name, description | 120, 1,000 characters |
| Raw check results | kept about 30 days |
| Status changes, monitor events | kept 1 year |
| Alert events and deliveries | kept 90 days |
| Resolved incidents | kept 1 year |
The kind-specific bounds (URL length, headers, status rules, thresholds) are on the HTTP, SSL and domain pages.