Documentation

HyperMonitors is API-first: everything you can do in the dashboard, you can do over a JSON REST API or expose to an AI agent via the Model Context Protocol. Create a free account to mint an API key.

MCP server

Let AI agents (Claude Code, Cursor, VS Code, …) inspect and manage your monitors over the Model Context Protocol. Enable it in Settings → MCP and connect with an API key. Read-only keys can use the read tools; creating, updating, or pausing monitors needs a read-write key.

Server URL (Streamable HTTP)
https://monitor.internetgroup.com/v1/mcp
Client configuration (Claude Code, Cursor, VS Code, …)
{
  "mcpServers": {
    "hypermonitor": {
      "type": "http",
      "url": "https://monitor.internetgroup.com/v1/mcp",
      "headers": {
        "Authorization": "Bearer hm_live_YOUR_KEY"
      }
    }
  }
}

Or with Claude Code: claude mcp add --transport http hypermonitor https://monitor.internetgroup.com/v1/mcp--header "Authorization: Bearer hm_live_…"

Available tools
list_monitorsget_monitorget_monitor_uptimeget_monitor_checkslist_incidentslist_alert_channelsget_usagecreate_monitorupdate_monitorpause_monitorresume_monitorset_monitor_channels

Deleting monitors is intentionally not exposed over MCP.

Getting started

All endpoints live under the base URL and speak JSON. Authenticate by passing your key as a bearer token:

curl -H "Authorization: Bearer hm_live_…" \
  https://monitor.internetgroup.com/v1/monitors
  • Key scopes. read-only keys may only make GET requests — anything else returns 403. read-write keys can use every method. Routes marked owner · read-write additionally require your org role to be owner, and dashboard only routes only work from the web app — keys can't mint or revoke keys.
  • Organizations. Requests act on your default organization. To target another org you belong to, send its id in an x-org-id header.
  • Conventions. List responses are shaped { items: [...] }; timestamps are ISO 8601 UTC; ids are UUIDs. Only /v1/notifications paginates (via cursor / nextCursor).
  • OpenAPI. A machine-readable spec is served at https://monitor.internetgroup.com/docs/json.

Monitors

GET/v1/monitorsany key

List every monitor in the organization, newest first.

Returns { items: Monitor[], nextCursor: null }
POST/v1/monitorsread-write key

Create a monitor. The body is discriminated on type — see the field reference below.

Body
  • typehttp | port | ping | heartbeat | dns | ssl (required)
  • namestring, 1–100 chars (required)
  • intervalSecondsint 30–86400, must be ≥ your plan's minimum (required)
  • timeoutMsint 1000–60000, default 30000
  • confirmationThresholdconsecutive failures before an incident opens, int 1–10, default 2
  • plus the type-specific fields listed in the monitor type reference
Returns 201 Monitor

Plan gates: tier_type_not_allowed if the type isn't on your plan, tier_interval_too_low if the interval is below the plan minimum, tier_limit_exceeded when the monitor limit is reached.

New monitors are automatically routed to every enabled alert channel (opt-out) — narrow it with PUT /v1/monitors/:id/channels.

GET/v1/monitors/:idany key

Fetch one monitor.

Returns Monitor
PATCH/v1/monitors/:idread-write key

Partially update a monitor. Accepts any create field for the monitor's type except type itself (immutable). The merged result is re-validated in full.

Returns Monitor
DELETE/v1/monitors/:idread-write key

Delete a monitor and its history.

Returns { deleted: boolean }
POST/v1/monitors/:id/pauseread-write key

Pause checking. Status becomes paused.

Returns Monitor
POST/v1/monitors/:id/resumeread-write key

Resume a paused monitor. 409 conflict if it isn't paused.

Returns Monitor
GET/v1/monitors/:id/checksany key

Raw check results, newest first.

Query
  • hoursint 1–17520, default 24
  • limitint 1–1000, default 500
Returns { items: [{ id, monitorId, checkedAt, ok, responseTimeMs, statusCode, error }] }
GET/v1/monitors/:id/checks/seriesany key

Checks aggregated into time buckets — ideal for charts and uptime bars.

Query
  • hoursint 1–17520, default 24
  • bucketMinutesint 1–1440, default 5
Returns { items: [{ bucketStart, avgResponseTimeMs, upCount, downCount }] }
GET/v1/monitors/:id/uptimeany key

Uptime percentage over fixed windows. Always returns all four windows.

Returns { windows: [{ window: "1d"|"7d"|"30d"|"365d", uptimePct: number|null, totalChecks }] }
GET/v1/monitors/:id/incidentsany key

Incidents for this monitor, newest first (up to 200).

Returns { items: [{ id, monitorId, kind, startedAt, resolvedAt, cause }] }
GET/v1/monitors/:id/channelsany key

The alert channels currently assigned to this monitor.

Returns { channelIds: uuid[] }
PUT/v1/monitors/:id/channelsread-write key

Replace the monitor's alert-channel assignment wholesale.

Body
  • channelIdsuuid[], max 100 — every id must be a channel in your org
Returns { channelIds: uuid[] }
Monitor type reference

Fields accepted by POST /v1/monitors in addition to the common ones, per type. Types marked “paid plans” aren't available on Free.

http
  • urlhttp(s) URL (required)
  • expectedStatusCodesint[] (100–599, max 50) — default: any 2xx/3xx is up
  • keywordType"exists" | "absent" — pair with keywordValue
  • keywordValuestring 1–500 — pair with keywordType
  • followRedirectsboolean, default true
  • customHeadersobject of header name → value (max 20) sent with every check, e.g. { "Authorization": "Bearer …" } — request-framing headers (host, content-length, connection, …) can't be set
  • sslCheckEnabledboolean, default true — capture HTTPS cert stats + expiry reminders
  • sslAlertDaysint 1–90, default 14
port
  • hosthostname or IPv4 (required)
  • portint 1–65535 (required)
ping
  • hosthostname or IPv4 (required)
heartbeatpaid plans
  • heartbeatGraceSecondsint 0–86400, default 300 — how long a ping may be overdue before the monitor goes down
dnspaid plans
  • hosthostname (required)
  • dnsRecordTypeA | AAAA | CNAME | MX | TXT | NS (required)
  • dnsExpectedValuestring 1–255 — resolved records must include it
sslpaid plans
  • hosthostname (required)
  • portint 1–65535, default 443
  • sslAlertDaysint 1–90, default 14
  • sslWatchChangesboolean, default false — alert when the certificate fingerprint changes

Incidents

GET/v1/incidentsany key

Incidents across all monitors in the organization, newest first.

Query
  • status"open" | "resolved" — open means resolvedAt is null (optional)
  • limitint 1–200, default 50
Returns { items: [{ id, monitorId, monitorName, kind: "down"|"ssl_expiring"|"ssl_changed", startedAt, resolvedAt, cause }] }

Alert channels

GET/v1/alert-channelsany key

List the organization's alert channels.

Returns { items: [{ id, type, name, enabled, verified, config, createdAt, … }] }
POST/v1/alert-channelsread-write key

Create an alert channel. It is automatically assigned to every existing monitor (opt-out). tier_limit_exceeded when the plan's channel limit is reached.

Body
  • typein_app | telegram | email | slack | discord | sms (required)
  • namestring, 1–100 chars (required)
  • configemail → { address }; slack/discord → { webhookUrl } (https); sms → { phone } in international format (e.g. +41791234567 — stored digits-only). in_app and telegram need none — telegram starts unverified until linked
Returns 201 AlertChannel
PATCH/v1/alert-channels/:idread-write key

Rename or enable/disable a channel.

Body
  • namestring, 1–100 chars (optional)
  • enabledboolean (optional)
Returns AlertChannel
DELETE/v1/alert-channels/:idread-write key

Delete a channel.

Returns { deleted: boolean }
POST/v1/alert-channels/:id/testread-write key

Send a test alert through the channel. 409 if the channel is disabled or Telegram isn't linked yet.

Returns { delivered: boolean, message: string|null }
POST/v1/alert-channels/telegram/link-coderead-write key

Mint a one-time code that binds a Telegram chat to a channel. Send /start <code> to the bot, or open the returned deep link. Codes expire after 10 minutes.

Body
  • channelIduuid of a telegram channel (required)
Returns { code, deepLink, expiresAt }

Notifications

GET/v1/notificationsany key

Your in-app notifications (per user, not per org). The only cursor-paginated endpoint.

Query
  • unreadOnlyboolean, default false
  • limitint 1–200, default 50
  • cursorid from a previous page's nextCursor (optional)
Returns { items: [{ id, incidentId, title, body, readAt, createdAt }], nextCursor: string|null }
POST/v1/notifications/readread-write key

Mark notifications as read.

Body
  • idsnumber[], 1–500 entries (required)
Returns { updated: number }

Account & usage

GET/v1/meany key

The authenticated user and their active organization's tier.

Returns { id, email, tier, telegramLinked, createdAt }
PATCH/v1/medashboard only

Update account settings: the per-account MCP toggle and/or the active organization. Only available with a browser session. 404 if you are not a member of the given organization.

Body
  • mcpEnabledboolean (optional)
  • activeOrganizationIduuid of an organization you belong to (optional) — switches the active org
Returns MeResponse (tier reflects the new active organization)
GET/v1/usageany key

Current usage against plan limits.

Returns { tier, monitors: {used, limit}, alertChannels: {used, limit}, minIntervalSeconds, logRetentionDays, allowedMonitorTypes }
GET/v1/analyticsany key

Org-wide analytics for a trailing window, compared with the window before it: time-weighted uptime, down-incident count/downtime/avg/longest/MTBF, response-time stats with >1s peaks, and every monitor sorted by lowest uptime.

Query
  • days7 or 30, default 7
Returns { windowDays, since, until, uptimePct, prevUptimePct, incidents, prevIncidents, downtimeMs, avgIncidentMs, longestIncidentMs, mtbfMs, avgResponseMs, prevAvgResponseMs, minResponseMs, maxResponseMs, peaksAbove1s, monitors: [{ id, name, uptimePct, incidents, avgResponseMs }] }

MCP server

A Model Context Protocol server for AI agents (Claude Code, Cursor, …). Enable it per account in Settings → MCP, then point your client at the endpoint with an API key in the Authorization header.

POST/v1/mcpany key

MCP Streamable HTTP endpoint (stateless JSON-RPC 2.0). Tools: list_monitors, get_monitor, get_monitor_uptime, get_monitor_checks, list_incidents, list_alert_channels, get_usage, create_monitor, update_monitor, pause_monitor, resume_monitor, set_monitor_channels. Read tools work with read-only keys; write tools need a read-write key. 403 until the account enables MCP in Settings.

Returns JSON-RPC 2.0 responses

Organization & team

GET/v1/organizationsany key

Every organization the authenticated user belongs to (drives the org switcher).

Returns { items: [{ id, name, tier, role: "owner"|"member" }] }
GET/v1/organizationany key

The active organization.

Returns { id, name, tier, createdAt }
PATCH/v1/organizationowner · read-write

Rename the organization.

Body
  • namestring, 1–100 chars (required)
Returns Organization
GET/v1/organization/membersany key

List members.

Returns { items: [{ userId, email, role: "owner"|"member", joinedAt }] }
DELETE/v1/organization/members/:idowner · read-write

Remove a member. 409 when trying to remove yourself.

Returns { removed: boolean }
GET/v1/organization/invitesany key

List pending invites.

Returns { items: [{ id, email, role, expiresAt, createdAt }] }
POST/v1/organization/invitesowner · read-write

Invite a teammate. Invites expire after 7 days. 409 if they're already a member.

Body
  • emailemail, max 255 (required)
  • role"owner" | "member", default "member"
Returns 201 { id, token, email, role, acceptUrl, expiresAt }
DELETE/v1/organization/invites/:idowner · read-write

Revoke a pending invite.

Returns { revoked: boolean }
POST/v1/organization/invites/acceptread-write key

Accept an invite as the calling user. 404 if the token is invalid or expired.

Body
  • tokeninvite token, min 8 chars (required)
Returns { organizationId: uuid }

Status pages

GET/v1/status-pagesany key

List the organization's status pages.

Returns { items: StatusPage[] }
GET/v1/status-pages/:idany key

Fetch one status page.

Returns { id, slug, title, branding: { logoUrl, accentColor, headerText }, isPublic, hasPassword, customDomain, monitorIds, createdAt }
GET/v1/status-pages/by-slug/:slug/previewany key

Team preview: the same payload as the public status endpoint, for members of the owning org. Works for hidden pages and bypasses the password gate. 403 when the page belongs to another organization.

Returns PublicStatusPage
POST/v1/status-pagesread-write key

Create a status page. Slugs are global — 409 if taken; tier_limit_exceeded at the plan's page limit.

Body
  • slug3–40 chars, lowercase letters/digits/hyphens (required)
  • titlestring, 1–100 chars (required)
Returns 201 StatusPage
PATCH/v1/status-pages/:idread-write key

Update settings, branding, monitors, visibility, custom domain, or password.

Body
  • titlestring, 1–100 chars
  • slugnew slug (409 if taken)
  • brandingpartial { logoUrl, accentColor, headerText }
  • isPublicboolean
  • customDomainhostname (max 253) or null to clear
  • monitorIdsuuid[], max 200 — all must be org monitors
  • passwordstring 4–200 to set, null to remove, omit to keep
Returns StatusPage
DELETE/v1/status-pages/:idread-write key

Delete a status page.

Returns { deleted: boolean }

API keys

GET/v1/api-keysany key

List your active (non-revoked) keys. The secret is never returned.

Returns { items: [{ id, name, access, prefix, lastUsedAt, createdAt }] }
POST/v1/api-keysdashboard only

Mint a key. Only available with a browser session — keys can't create keys. The secret is returned once; up to 25 active keys per user.

Body
  • namestring, 1–100 chars (required)
  • access"read_only" | "read_write", default "read_write"
Returns 201 ApiKey & { secret }
DELETE/v1/api-keys/:iddashboard only

Revoke a key. Only available with a browser session.

Returns { revoked: boolean }

Billing

GET/v1/billing/statusany key

Whether billing is configured and the org has a Stripe customer.

Returns { tier, billingEnabled, hasCustomer }
POST/v1/billing/checkoutowner · read-write

Create a Stripe Checkout session for a paid tier. 409 if billing isn't configured.

Body
  • tiera paid tier (not free) (required)
  • cycle"monthly" | "annual", default "monthly"
Returns { url: string }
POST/v1/billing/portalowner · read-write

Open the Stripe customer portal. 409 if the org has no Stripe customer yet.

Returns { url: string }

Public endpoints

No Authorization header. Each is rate-limited to 120 requests per minute.

GET/v1/heartbeat/:tokenpublic

Heartbeat ping for cron-style monitors — also accepts POST. Call it from your job on every run; the monitor goes down when pings stop for longer than the grace period.

Returns { ok: true }
GET/v1/public/status/:slugpublic

The JSON behind a public status page, including daily uptime per monitor over the page's configured history window (7/30/90/365 days, default 90).

Query
  • passwordrequired only for password-protected pages
Returns { title, branding, overall, historyDays, monitors: [{ id, name, status, uptime90d, dailyUptime[historyDays] }], incidents, updatedAt } — or { passwordRequired: true, title }
GET/v1/public/resolve-domainpublic

Resolve a status-page custom domain to its slug.

Query
  • hosthostname, max 253 chars (required)
Returns { slug: string|null }
GET/healthzpublic

Liveness probe (note: no /v1 prefix).

Returns { status, service, uptimeSeconds }
GET/readyzpublic

Readiness probe — 503 until the database and scheduler are up (no /v1 prefix).

Returns { status, checks }

Errors

Every error is JSON with a stable code:

{ "error": { "code": "conflict", "message": "…", "details": … } }
CodeStatusMeaning
validation_error400Request body or query failed validation — details carries the issues
unauthorized401Missing, invalid, or revoked credentials
forbidden403Read-only key on a write, non-member org, missing owner role, or a dashboard-only route
not_found404Resource (or route) doesn't exist in your org
conflict409State conflict — slug taken, monitor not paused, channel disabled, billing unconfigured…
tier_limit_exceeded403A plan count limit was reached (monitors, channels, status pages, 25 API keys)
tier_interval_too_low403Check interval below your plan's minimum
tier_type_not_allowed403Monitor type not available on your plan
rate_limited429Too many requests (public endpoints: 120/min)
internal500Unexpected server error