# Get from zero to first monitor in under 3 minutes > Everything the dashboard does is available over API, MCP, or GitHub Action. ## Get an API key Sign in with GitHub, create a key, and label it for your environment. ```bash Authorization: Bearer umk_live_... ``` 1. Go to [app.uptimemonitoring.com](https://app.uptimemonitoring.com) 2. Sign in with GitHub 3. Create an API key and label it (e.g. `ci-prod`, `local-dev`, `mcp`) --- ## Create your first monitor ```bash curl -X POST https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"myapp","url":"https://myapp.com/healthz","type":"http"}' ``` The hostname must resolve at creation time — typos and unreachable targets fail immediately. The response wraps the monitor record and its first state snapshot: ```json { "monitor": { "id": 1287, "name": "myapp", "url": "https://myapp.com/healthz", "type": "http", "interval_sec": 60, "expected_status": "200", "enabled": true }, "state": { "status": "up", "last_check_at": "2026-05-07T10:21:01Z", "evidence_buffer": [ { "region": "EU", "status_code": 200, "ttfb_ms": 182 } ] } } ``` Capture the ID for the rest of the Quickstart: ```bash MONITOR_ID=1287 ``` --- ## Check current status Capture the monitor ID from the create response, then poll it: ```bash curl https://api.uptimemonitoring.com/api/v1/monitors/$MONITOR_ID \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` --- ## Add a webhook Webhooks are scoped to a monitor — set or replace with `PUT`: ```bash curl -X PUT https://api.uptimemonitoring.com/api/v1/monitors/$MONITOR_ID/webhook \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://hooks.example.com/incident"}' ``` The HMAC secret is returned on the initial PUT — store it now. Replacing the URL keeps the existing secret. See [Webhooks](/docs/webhooks/) for signature verification and the account-wide `/api/v1/webhook-deliveries` log. --- ## Connect via MCP Set up UptimeMonitoring as an MCP server in your preferred tool: - **Claude** — Add the MCP server in Claude Desktop settings or Claude Code - **ChatGPT** — Configure via the ChatGPT plugin/MCP settings - **Cursor** — Add to `.cursor/mcp.json` - **OpenAI API** — Use the MCP endpoint in your tool configuration See the [MCP docs](/docs/mcp/) for detailed setup instructions per host. --- ## Use in GitHub Actions ```yaml - name: Verify deploy health uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.UPTIMEMONITORING_API_KEY }} monitor-id: ${{ vars.MONITOR_ID }} timeout: 120 ``` This step waits up to 120 seconds for the monitor to report healthy, then fails the workflow if it doesn't recover. See the [GitHub Action docs](/docs/github-action/) for advanced options. --- :::tip[Working examples] Full runnable copies of every snippet on this page — GitHub Actions workflows, MCP setup, webhook relay scripts, and language examples — live at [github.com/uptimemonitoring/examples](https://github.com/uptimemonitoring/examples). ::: --- # Authentication > GitHub OAuth, API key format, rotation, and common auth errors. ## GitHub OAuth flow UptimeMonitoring uses GitHub OAuth exclusively. No email, no password. 1. Visit [app.uptimemonitoring.com](https://app.uptimemonitoring.com) 2. Click "Sign in with GitHub" 3. Authorize the UptimeMonitoring app 4. You're in — create your first API key ## API key format All API keys use the `umk_live_` prefix followed by a random string: ```bash Authorization: Bearer umk_live_abc123def456... ``` The prefix makes keys easy to identify in logs and secret scanners. ## Multiple keys by environment Use separate keys for different environments and tools: - `ci-prod` — for GitHub Actions and deploy pipelines - `local-dev` — for development and testing - `mcp-claude` — for your MCP integration - `mcp-cursor` — for Cursor Each account supports up to 10 API keys. ## Rotation and grace window When you rotate a key: 1. A new key is issued immediately 2. The old key remains valid for a 5-minute grace window 3. After grace, the old key is permanently revoked This prevents downtime during key rotation in automated systems. ## Common auth errors The error envelope is flat: `{"error": ""}`. Match on the HTTP status; the message is the literal validator output. | Status | Message | Meaning | |--------|---------|---------| | 401 | `missing authorization` | `Authorization` header absent | | 401 | `invalid credentials` | API key not recognised or revoked | | 403 | `account suspended; contact support` | Account suspended (e.g. by the port-scan heuristic). Returned on regular `/api/v1/*` calls. | | 403 | `forbidden` | Admin-only route hit by a non-admin caller. Opaque on purpose — does not disclose whether the route exists. | Note: 401 responses currently carry `Content-Type: text/plain` even though the body is valid JSON. Parse the body as JSON regardless; this discrepancy is being fixed on the API side (see [monitive/uptimemonitoring-api](https://github.com/monitive/uptimemonitoring-api)). --- # Use UptimeMonitoring through MCP > Available MCP tools, annotations, auth model, and host-specific setup. ## What is MCP Model Context Protocol (MCP) lets AI agents interact with external tools. UptimeMonitoring's MCP server lets any compatible agent create, inspect, and manage monitors using natural language. ## Available tools | Tool | Description | Read-only | |------|-------------|-----------| | `list_monitors` | List all monitors with current status | Yes | | `get_monitor` | Get monitor details + recent evidence | Yes | | `create_monitor` | Create a new monitor | No | | `update_monitor` | Update monitor configuration | No | | `delete_monitor` | Delete a monitor | No (destructive) | | `list_incidents` | Query incidents with filtering | Yes | | `assert_monitor_healthy` | Check if monitor is healthy (for deploy gates) | Yes | | `get_account_status` | Usage against caps | Yes | ## Tool annotations | Tool | title | readOnlyHint | destructiveHint | idempotentHint | openWorldHint | |------|-------|-------------|-----------------|----------------|---------------| | `list_monitors` | "List Monitors" | `true` | `false` | `false` | `true` | | `get_monitor` | "Get Monitor" | `true` | `false` | `false` | `true` | | `list_incidents` | "List Incidents" | `true` | `false` | `false` | `true` | | `assert_monitor_healthy` | "Assert Monitor Healthy" | `true` | `false` | `false` | `true` | | `get_account_status` | "Get Account Status" | `true` | `false` | `false` | `true` | | `create_monitor` | "Create Monitor" | `false` | `true` | `false` | `true` | | `update_monitor` | "Update Monitor" | `false` | `true` | `false` | `true` | | `delete_monitor` | "Delete Monitor" | `false` | `true` | `true` | `true` | `openWorldHint: true` reflects that all tools make external API calls. `idempotentHint: true` is set only on `delete_monitor` — repeating the call after the monitor is gone produces the same result. ## Auth model The MCP endpoint accepts Bearer credentials: API keys, session tokens, and OAuth access tokens. Which credential you configure depends on the host. OAuth-based connector hosts such as Claude.ai, Claude Desktop, and ChatGPT should use the OAuth flow when prompted. Config-file and API clients such as Claude Code, Cursor, and the OpenAI API can pass an OAuth access token or a custom `Authorization` header when the host supports custom headers. OAuth 2.1 discovery endpoints are published at [`https://api.uptimemonitoring.com/.well-known/oauth-authorization-server`](https://api.uptimemonitoring.com/.well-known/oauth-authorization-server) (authorization server metadata) and [`https://api.uptimemonitoring.com/.well-known/oauth-protected-resource/mcp`](https://api.uptimemonitoring.com/.well-known/oauth-protected-resource/mcp) (protected resource metadata for the MCP endpoint). For API-key setups, use the same key as the REST API: ``` Authorization: Bearer umk_live_... ``` Create a dedicated key labeled `mcp` for API-key based agent setups. ## Example ```text User: Create a monitor for https://example.com/healthz and tell me whether it is healthy right now. Agent: I'll create the monitor and check its status. Tool: create_monitor Result: Monitor created (id: 1287, name: "example-healthz") Tool: assert_monitor_healthy Result: Healthy — EU, 200, ttfb 184ms ``` ## Discovery UptimeMonitoring's MCP server is listed in the [Official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers/com.uptimemonitoring/mcp) under `com.uptimemonitoring/mcp`. MCP-aware tools that auto-discover servers from the registry can install it without any manual configuration; the registry entry carries the canonical transport, endpoint, and auth metadata. ## Host compatibility Manually maintained pre-release — [open an issue](https://github.com/monitive/uptimemonitoring-web/issues) if your host should be listed. | Host | Transport | Auth | Status | |------|-----------|------|--------| | [Claude.ai](#claudeai) | Remote MCP connector | OAuth | ✅ | | [Claude Desktop](#claude-desktop) | Remote MCP connector | OAuth | ✅ | | [Claude Code](/docs/connect-claude-code/) | Remote MCP HTTP | OAuth / Bearer header | ✅ | | [ChatGPT](/docs/connect-chatgpt/) | Custom MCP connector | OAuth | ✅ Listed on ChatGPT app store | | [Cursor](/docs/connect-cursor/) | MCP config | OAuth / Bearer header | ✅ | | [OpenAI API](/docs/connect-openai-api/) | Responses MCP | OAuth token / custom headers | ✅ | *Last verified: 2026-05-26.* ## Listed in registries - ⏳ [GitHub MCP Registry](https://github.com/mcp) — submission pending 2026-05-19 (#26) - ⏳ [Official MCP Registry](https://registry.modelcontextprotocol.io) — `server.json` shipped 2026-05-19 (#25), external submission PR pending ## Support Questions or issues with the MCP server? Email [hello@uptimemonitoring.com](mailto:hello@uptimemonitoring.com). ## Host-specific setup ### Claude.ai In Claude.ai, open **Customize → Connectors**, add a custom connector, and enter: ```text https://api.uptimemonitoring.com/mcp ``` Complete the OAuth flow when Claude prompts you to connect your UptimeMonitoring account. ### Claude Desktop In Claude Desktop, add UptimeMonitoring through **Settings → Connectors** as a custom remote MCP connector: ```text https://api.uptimemonitoring.com/mcp ``` Complete the OAuth flow when Claude prompts you to connect your UptimeMonitoring account. Remote MCP servers in Claude Desktop should be added through Connectors, not `claude_desktop_config.json`. ### Claude Code OAuth or Bearer header via `claude mcp add`. Full install steps, verification, and common errors: [Connect Claude Code](/docs/connect-claude-code/). ### ChatGPT OAuth via the ChatGPT connector UI. Listed on the ChatGPT app store. Full install steps, verification, and common errors: [Connect ChatGPT](/docs/connect-chatgpt/). ### Cursor Bearer header or OAuth via `mcp.json`. Full install steps, verification, and common errors: [Connect Cursor](/docs/connect-cursor/). ### OpenAI API MCP tool in Responses API calls, API key or OAuth token. Full install steps, verification, and common errors: [Connect via OpenAI API](/docs/connect-openai-api/). --- # Connect Claude Code > Add the UptimeMonitoring MCP server to Claude Code with one shell command. ## Prerequisites - Claude Code installed (current stable) - An `umk_live_` API key from [app.uptimemonitoring.com](https://app.uptimemonitoring.com) — see [Authentication](/docs/authentication/) ## Connect Add the UptimeMonitoring remote HTTP MCP server with your API key as a Bearer header: ```bash claude mcp add --transport http uptimemonitoring https://api.uptimemonitoring.com/mcp \ --header "Authorization: Bearer umk_live_..." ``` Replace `umk_live_...` with your actual API key. ### OAuth alternative Omit the `--header` flag and run `/mcp` inside Claude Code — Claude Code will open your browser to authenticate via OAuth and store the access token automatically. ## Verify In Claude Code, type `/mcp` and confirm `uptimemonitoring` appears with status **connected**. Then ask: > list my monitors You should see a tool call to `list_monitors` and a list of your monitors (or an empty result if you don't have any yet). ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `401 Unauthorized` | Key is wrong, missing, or not `umk_live_`-prefixed | Confirm the key from [Authentication](/docs/authentication/#api-key-format) starts with `umk_live_` and is pasted in full | | Tools missing after `/mcp` | MCP server registered but Claude Code hasn't reloaded | Restart Claude Code fully | | Connection refused / DNS error | Wrong host in the server URL | The endpoint is `https://api.uptimemonitoring.com/mcp`, not the marketing host | ## See also - [MCP reference and host compatibility matrix](/docs/mcp/) - [Connect ChatGPT](/docs/connect-chatgpt/) - [Connect Cursor](/docs/connect-cursor/) - [Connect via OpenAI API](/docs/connect-openai-api/) - [Connect via REST API](/docs/connect-rest-api/) --- # Connect ChatGPT > Add the UptimeMonitoring MCP server as a custom connector in ChatGPT. ## Prerequisites - A ChatGPT plan that allows adding custom MCP connectors - An UptimeMonitoring account — sign in at [app.uptimemonitoring.com](https://app.uptimemonitoring.com) ## Connect UptimeMonitoring is listed on the ChatGPT app store — search for it in ChatGPT's connector picker, or install manually: 1. In ChatGPT, open **Settings → Connectors**. 2. Add a custom connector and enter the remote MCP URL: ```text https://api.uptimemonitoring.com/mcp ``` 3. Complete the OAuth flow when ChatGPT prompts you to connect your UptimeMonitoring account. ChatGPT uses OAuth exclusively for custom connectors — you cannot pass a custom `Authorization` header via the connector UI. If you need API-key access from a backend agent, use the [OpenAI API path](/docs/connect-openai-api/) instead. If you need full MCP tool access while testing prerelease tool changes, enable **Developer Mode** in ChatGPT before adding the connector. ## Verify In a new ChatGPT chat with the connector enabled, ask: > list my monitors You should see the connector invoked and a list returned. ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | OAuth window never opens | Popup blocker | Allow popups for chatgpt.com and retry | | Connector visible but tools missing | Developer Mode not enabled | Enable Developer Mode in ChatGPT settings, then reconnect | | Wrong account authenticated | Different UptimeMonitoring account in the OAuth tab | Sign out of UptimeMonitoring in the browser tab, sign in with the correct account, then redo the OAuth flow | ## See also - [MCP reference and host compatibility matrix](/docs/mcp/) - [Connect Claude Code](/docs/connect-claude-code/) - [Connect Cursor](/docs/connect-cursor/) - [Connect via OpenAI API](/docs/connect-openai-api/) - [Connect via REST API](/docs/connect-rest-api/) --- # Connect Cursor > Add the UptimeMonitoring MCP server to Cursor via mcp.json with a Bearer header. ## Prerequisites - Cursor (current stable release) - An `umk_live_` API key from [app.uptimemonitoring.com](https://app.uptimemonitoring.com) — see [Authentication](/docs/authentication/) ## Connect Create or open your Cursor MCP config file and add the UptimeMonitoring server block: - **Global (all projects):** `~/.cursor/mcp.json` - **Project-local:** `./.cursor/mcp.json` in your project root ```json { "mcpServers": { "uptimemonitoring": { "url": "https://api.uptimemonitoring.com/mcp", "headers": { "Authorization": "Bearer umk_live_..." } } } } ``` Replace `umk_live_...` with your actual API key. Do not commit a project-local config with a live key — add `.cursor/mcp.json` to `.gitignore` before saving. ### OAuth alternative Cursor supports OAuth for remote MCP servers that advertise OAuth metadata. If the server triggers an OAuth prompt, complete the browser flow rather than adding a manual header. ## Verify Fully restart Cursor (quit and relaunch — killing only the window is not enough). Open the MCP panel and confirm `uptimemonitoring` appears as connected. Then ask: > list my monitors ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `401 Unauthorized` | Missing or incorrect key | Confirm the key starts with `umk_live_` and is pasted in full without extra whitespace | | Server not appearing | Config file in wrong location | Cursor reads only `~/.cursor/mcp.json` or `./.cursor/mcp.json` | | Tools not appearing after config change | Cursor not fully restarted | Quit Cursor fully (including background helper) and relaunch | For end-to-end verification with test monitor creation and deletion, see the [detailed Cursor guide](/docs/cursor/). ## See also - [MCP reference and host compatibility matrix](/docs/mcp/) - [Connect Claude Code](/docs/connect-claude-code/) - [Connect ChatGPT](/docs/connect-chatgpt/) - [Connect via OpenAI API](/docs/connect-openai-api/) - [Connect via REST API](/docs/connect-rest-api/) --- # Connect via OpenAI API > Use the UptimeMonitoring MCP server in your OpenAI Responses API requests. ## Prerequisites - An OpenAI API account with Responses API access - An `umk_live_` API key from [app.uptimemonitoring.com](https://app.uptimemonitoring.com) — see [Authentication](/docs/authentication/) ## Connect Use the `mcp` tool type in a Responses API request. This example restricts `allowed_tools` to the read-only `list_monitors` for safe verification — remove the restriction to enable write tools in production: ```bash curl https://api.openai.com/v1/responses \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "tools": [ { "type": "mcp", "server_label": "uptimemonitoring", "server_url": "https://api.uptimemonitoring.com/mcp", "headers": { "Authorization": "Bearer umk_live_..." }, "allowed_tools": ["list_monitors"] } ], "input": "List my UptimeMonitoring monitors." }' ``` Replace `umk_live_...` with your UptimeMonitoring API key. Keep the two keys separate: `OPENAI_API_KEY` (`sk-...`) is your OpenAI credential; `umk_live_...` is your UptimeMonitoring credential. ### Auth modes | Use case | Method | |----------|--------| | Backend-to-backend / server agent | Pass `Authorization: Bearer umk_live_...` in the `headers` field (shown above) | | User-facing agent with OAuth | Pass the user's OAuth access token via the `authorization` field of the MCP tool config instead of `headers.Authorization` | To enable write tools (`create_monitor`, `update_monitor`, `delete_monitor`) in production, remove `allowed_tools` and handle the `mcp_approval_request` events in the response stream, or set `require_approval: "never"` only after confirming user intent in your application layer. ## Verify Run the curl call above. The response should include a tool call to `list_monitors` and a follow-up assistant message summarising your monitors. ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `401` from UptimeMonitoring | Wrong key in `headers.Authorization` | Confirm the key starts with `umk_live_` — do not use your OpenAI key here | | MCP tool ignored by model | Older model version without `mcp` tool support | Switch to `gpt-4o` or a later model | | Server not found | URL typo | The endpoint is `https://api.uptimemonitoring.com/mcp`, not `mcp.uptimemonitoring.com` | ## See also - [MCP reference and host compatibility matrix](/docs/mcp/) - [Connect Claude Code](/docs/connect-claude-code/) - [Connect ChatGPT](/docs/connect-chatgpt/) - [Connect Cursor](/docs/connect-cursor/) - [Connect via REST API](/docs/connect-rest-api/) --- # Connect via REST API > Make your first UptimeMonitoring REST API call with curl and an API key. ## Prerequisites - An `umk_live_` API key from [app.uptimemonitoring.com](https://app.uptimemonitoring.com) — see [Authentication](/docs/authentication/) - `curl` (any recent version) ## Make your first call List your monitors: ```bash curl https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer umk_live_..." ``` Replace `umk_live_...` with your actual API key. ## Create a monitor ```bash curl -X POST https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer umk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "my-api", "url": "https://example.com/healthz", "type": "http", "expected_status": "200" }' ``` See [Monitors](/docs/monitors/) for the full field reference and response shape. ## Verify A successful list call returns `200 OK` with a JSON array. A successful create call returns `200 OK` with a `{monitor, state}` envelope. If you see `401`, check the common errors below. ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `401 Unauthorized` | Key prefix wrong or key revoked | Confirm the key starts with `umk_live_` — see [Authentication](/docs/authentication/#api-key-format) | | `429 Too Many Requests` | Rate limit exceeded | See [Rate Limits](/docs/rate-limits/) | | `400` on create | Invalid request body | See [Errors](/docs/errors/) — the envelope is `{"error":""}`, not RFC 7807 | ## Want this through an agent? Connect UptimeMonitoring to your AI agent instead of calling the REST API directly: - [Connect Claude Code](/docs/connect-claude-code/) - [Connect ChatGPT](/docs/connect-chatgpt/) - [Connect Cursor](/docs/connect-cursor/) - [Connect via OpenAI API](/docs/connect-openai-api/) ## See also - [MCP reference and host compatibility matrix](/docs/mcp/) - [Monitors](/docs/monitors/) — full REST API reference - [Authentication](/docs/authentication/) — key rotation and scopes --- # Connect Cursor > Manual MCP install for Cursor — drop a config snippet into mcp.json, restart, and run three commands to verify. ## Prerequisites - Cursor ≥ current GA release - An `umk_live_*` API key from [app.uptimemonitoring.com](https://app.uptimemonitoring.com) ## Step 1 — Create an API key Log in to [app.uptimemonitoring.com](https://app.uptimemonitoring.com) and create an API key labeled `cursor`. Keep it ready for the next step. Full key management details are in [Authentication](/docs/authentication/). ## Step 2 — Add the server to `mcp.json` Create or open `.cursor/mcp.json` in your project root (project-local config). For a global install that works across all projects, use `~/.cursor/mcp.json` instead. Add the UptimeMonitoring server block: ```json { "mcpServers": { "uptimemonitoring": { "url": "https://api.uptimemonitoring.com/mcp", "headers": { "Authorization": "Bearer umk_live_..." } } } } ``` Replace `umk_live_...` with your actual API key. Do not commit project-local `.cursor/mcp.json` with a live key — add `.cursor/mcp.json` to your `.gitignore` before saving. ## Step 3 — Restart Cursor and verify Fully restart Cursor (quit and relaunch — killing only the window is not enough). Open the agent panel and confirm that `uptimemonitoring` appears in the MCP tools list. You should see all 8 tools from the [Available tools](/docs/mcp/#available-tools) table. ## Verify end-to-end Run these three prompts in the Cursor agent panel to confirm the integration works: **1. List your monitors** > List my monitors. Expected tool call: `list_monitors` — returns your current monitors with status. **2. Create a test monitor** > Create a monitor for `https://example.com/healthz` named `cursor-test`. Expected tool call: `create_monitor` — returns a monitor object with an `id`. Note the `id` for the next step. If a monitor for that URL already exists, the API returns `409`; delete or rename the existing one first. **3. Delete the test monitor** > Delete the `cursor-test` monitor. Expected tool call: `delete_monitor` — Cursor surfaces a confirmation prompt because `delete_monitor` carries `destructiveHint: true`. Confirm when asked. The monitor and its incident history are permanently removed. ## Common errors | Symptom | Cause | Fix | |---------|-------|-----| | `401 Unauthorized` | Wrong or missing key | Confirm the key starts with `umk_live_` and is pasted in full without extra whitespace | | Tools not appearing in agent panel | Cursor did not reload | Quit Cursor fully (including the background helper) and relaunch | | Destructive tool blocked | Cursor prompts before running tools with `destructiveHint: true` | This is expected — confirm the action in the prompt | | `409 Conflict` on create | Monitor for that URL already exists | List monitors, delete the duplicate by ID, then retry | ## OAuth path (coming soon) Cursor Marketplace OAuth is on the roadmap. When it ships, OAuth becomes the recommended path — no `mcp.json` edit needed. Until then, the Bearer-header setup above is the documented and supported install. Track progress in the [host-onboarding umbrella issue](https://github.com/monitive/uptimemonitoring-web/issues/23). ## Next steps - [MCP reference](/docs/mcp/) — full tool list, annotations, and host compatibility matrix - [Authentication](/docs/authentication/) — key rotation and scopes - [API Reference](/api-reference/) — REST endpoints if you need to script outside an agent --- # Monitors > Create, update, delete, and query monitors via the REST API. ## Create a monitor ```bash curl -X POST https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "api-prod", "url": "https://example.com/", "type": "http", "expected_status": "200" }' ``` The target hostname must resolve at creation time — the API runs an immediate test check, and unresolvable hosts are rejected up front. The response wraps the monitor and its first state snapshot: ```json { "monitor": { "id": 1287, "name": "api-prod", "url": "https://example.com/", "type": "http", "interval_sec": 60, "expected_status": "200", "enabled": true, "created_at": "2026-05-07T10:21:00Z" }, "state": { "status": "up", "last_check_at": "2026-05-07T10:21:01Z", "evidence_buffer": [ { "region": "EU", "status_code": 200, "ttfb_ms": 182 } ] } } ``` > **Notes for integrators:** > - Unknown request fields are silently ignored — the API is forward-compatible. > - Timestamps in responses (e.g. `last_check_at`) use RFC 3339 with nanosecond precision. If your client library caps at milliseconds, truncate before parsing. ### Required fields | Field | Type | Description | |-------|------|-------------| | `name` | string | Monitor name (max 255 chars) | | `url` | string | Target URL (max 2048 chars) | | `type` | string | `http`, `tcp`, `ping`, `dns`, `ssl` | All string fields must be valid UTF-8 with no null bytes; URL fields must additionally be free of ASCII control characters (`U+0000`–`U+001F` and `U+007F`). Violations return `400` before the SSRF guard runs. The immediate test check populates `state.status` to `up` or `down` only for HTTP and SSL monitors. TCP, ping, and DNS monitors return `state.status: "unknown"` from the create response — the next scheduled tick fills it in. HTTP creates can also return `unknown` if the probe is unavailable, the kill switch is engaged, or the URL fails the dispatch-time SSRF guard (DNS rebind). ### Optional fields | Field | Type | Default | Description | |-------|------|---------|-------------| | `expected_status` | string | `"200"` | Single HTTP status code as a string (`"100"`–`"599"`). Range or list values (e.g. `"200-399"`, `"200,201"`) are rejected — see Errors. | | `body_match` | string | — | Response body must contain this string (max 1024 chars). The field may be absent from responses when unset; treat absent and `null` identically. | | `interval_sec` | integer | `60` | Check interval in seconds (minimum 60) | | `enabled` | boolean | `true` | Whether the monitor is actively checking. Set `false` to pause without deleting. | ## Monitor types ### HTTP / HTTPS Checks status code, optional body match, full timing breakdown (DNS, connect, TLS, TTFB, download). ### TCP Connects to a service port. Restricted to common service ports for abuse prevention. ### Ping (ICMP) Basic reachability check. ### DNS Verifies record presence and values. ### SSL Monitors certificate expiry. ## Check behavior - **60-second minimum interval** (30-second reserved for future paid tier) - **Phase-spread scheduling** — checks are spread across the interval window, not clustered at the top of the minute - **Cross-region confirmation** — any failure triggers re-checks from 2 additional regions. State flips only when 2 of 3 agree. - **Exponential backoff** — sustained-down monitors back off from 1 min to 5 min after 10 minutes of continuous downtime - **Flap detection** — monitors transitioning more than 10 times per hour are flagged as flapping ## List monitors ```bash curl https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` The list endpoint returns every monitor on the account in a single response. There is no filtering or pagination — `?limit`, `?offset`, `?status`, and `?type` are accepted but silently ignored. Apply filters client-side. The list response is a **bare JSON array** of monitor records with the current `status` and `last_check_at` flattened into each entry (no `{monitor, state}` wrapping). For the full state — including `evidence_buffer` — use `GET /api/v1/monitors/{id}`. Response shape (abridged): ```json [ { "id": 1287, "name": "api-prod", "url": "https://example.com/", "type": "http", "interval_sec": 60, "expected_status": "200", "enabled": true, "created_at": "2026-05-07T10:21:00Z", "status": "up", "last_check_at": "2026-05-07T10:21:01Z" } ] ``` ## Get a monitor ```bash curl https://api.uptimemonitoring.com/api/v1/monitors/1287 \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` Response (single envelope): ```json { "monitor": { "id": 1287, "name": "api-prod", "url": "https://example.com/", "type": "http", "interval_sec": 60, "expected_status": "200", "enabled": true, "created_at": "2026-05-07T10:21:00Z" }, "state": { "status": "up", "last_check_at": "2026-05-07T10:25:43Z", "evidence_buffer": [ { "region": "EU", "timestamp": "2026-05-07T10:25:43Z", "status_code": 200, "ttfb_ms": 174 }, { "region": "US", "timestamp": "2026-05-07T10:24:42Z", "status_code": 200, "ttfb_ms": 198 } ] } } ``` A non-numeric path parameter (e.g. `/monitors/abc`) returns `400 invalid monitor ID`. A numeric-but-missing ID returns `404 monitor not found`. ## Update a monitor ```bash curl -X PUT https://api.uptimemonitoring.com/api/v1/monitors/1287 \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "api-prod-v2"}' ``` `PUT` accepts a partial body — fields you omit keep their current values. `PATCH` is not supported and returns `405`. ## Delete a monitor ```bash curl -X DELETE https://api.uptimemonitoring.com/api/v1/monitors/1287 \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` --- # Webhooks > Monitor-scoped webhooks, payload format, signature verification, retry policy, and the account-wide delivery log. ## Set or replace the webhook on a monitor Webhooks are scoped to a single monitor — each monitor exposes one webhook slot. Use `PUT /api/v1/monitors/{monitorID}/webhook` to set it or replace its URL: ```bash curl -X PUT https://api.uptimemonitoring.com/api/v1/monitors/1287/webhook \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://hooks.example.com/incident"}' ``` Response: ```json { "id": 42, "monitor_id": 1287, "url": "https://hooks.example.com/incident", "secret": "whs_live_…", "created_at": "2026-05-19T12:00:00Z" } ``` The response includes the webhook record and the plaintext HMAC `secret`. The same shape is returned on subsequent `GET` reads — the API does NOT redact the secret on read, so the endpoint must be treated as a credential surface (anyone who can authenticate to the account can recover the signing key). Replacing the URL on an existing webhook keeps the existing secret. To rotate the secret, `DELETE` the webhook then `PUT` it again. ## Inspect or delete a webhook ```bash # Read the current webhook for a monitor (returns the plaintext secret too) curl https://api.uptimemonitoring.com/api/v1/monitors/1287/webhook \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" # Remove the webhook (monitor stays; just no notifications) curl -X DELETE https://api.uptimemonitoring.com/api/v1/monitors/1287/webhook \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` ## Payload format ```json { "event": "monitor.down", "monitor_id": 30, "monitor_name": "myapp-healthz", "monitor_url": "https://example.com/health", "account_id": 1, "occurred_at": "2026-04-12T14:23:11Z", "reason": "http_5xx", "delivery_id": 3, "attempt": 1 } ``` Events: `monitor.down`, `monitor.up`, `monitor.flapping`. | Field | Type | Description | |-------|------|-------------| | `event` | string | One of `monitor.down`, `monitor.up`, `monitor.flapping`. | | `monitor_id` | integer | ID of the monitor that changed state. | | `monitor_name` | string | Display name of the monitor. | | `monitor_url` | string \| omitted | The URL being monitored. Omitted if the monitor has no URL. | | `account_id` | integer | Account that owns the monitor. | | `occurred_at` | RFC3339 string | When the state transition was detected. | | `reason` | string \| omitted | Error class for `monitor.down` (e.g. `http_5xx`, `timeout`), flap reason for `monitor.flapping`, empty/omitted for `monitor.up`. | | `delivery_id` | integer | Stable per logical state transition. Use this field to deduplicate retried deliveries — the same transition always carries the same `delivery_id`. | | `attempt` | integer (1–3) | Which delivery attempt this is (1 = first try). | ## Signature verification Every webhook delivery includes an `X-UptimeMonitoring-Signature` header containing an HMAC-SHA256 hex digest. To verify it, compute the HMAC over the **raw request bytes** (not a re-serialized parse), then compare with a **constant-time** function. Using a regular string comparison (`===` / `!==`) leaks timing information that lets an attacker recover the expected signature byte-by-byte. :::caution Always verify against the raw body bytes, not a re-serialized parse of the body. Always use a constant-time comparison — `===` or `!==` is not safe here. ::: ```js const crypto = require('crypto'); const express = require('express'); const app = express(); // Register this route BEFORE any global express.json() or body-parser middleware. // Earlier middleware consumes the stream first; express.raw() must run here, not after. app.post( '/webhooks/uptimemonitoring', express.raw({ type: 'application/json' }), (req, res) => { if (!Buffer.isBuffer(req.body)) { return res.status(400).send('Expected raw body — check middleware order'); } const sig = req.header('X-UptimeMonitoring-Signature') || ''; if (!/^[a-f0-9]{64}$/i.test(sig)) { return res.status(401).send('Invalid signature'); } const expected = crypto .createHmac('sha256', process.env.UPTIMEMONITORING_WEBHOOK_SECRET) .update(req.body) // req.body is a Buffer here .digest('hex'); const a = Buffer.from(sig, 'hex'); const b = Buffer.from(expected, 'hex'); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body.toString('utf8')); // …handle event.event === 'monitor.down' / 'monitor.up' / 'monitor.flapping'… res.status(204).end(); }, ); app.listen(3000); ``` ```python import hmac import hashlib import os from flask import Flask, request, abort app = Flask(__name__) SECRET = os.environ["UPTIMEMONITORING_WEBHOOK_SECRET"].encode() @app.post("/webhooks/uptimemonitoring") def receive(): raw = request.get_data() # bytes, before any parsing sig = request.headers.get("X-UptimeMonitoring-Signature", "") expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, expected): abort(401) # event = json.loads(raw) return "", 204 if __name__ == "__main__": app.run(port=3000) ``` Background: [OWASP — Observable Timing Discrepancy (CWE-208)](https://cwe.mitre.org/data/definitions/208.html). ## Retry policy Failed deliveries (non-2xx response or timeout) are retried with exponential backoff: | Attempt | Delay | |---------|-------| | 1 | Immediate | | 2 | 1 minute | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | After 5 failed attempts, the delivery is marked as failed. ## Delivery log (account-wide) The delivery log is exposed at the account scope, not per-webhook. Filter with `monitor_id` if you only want one monitor's deliveries: ```bash curl "https://api.uptimemonitoring.com/api/v1/webhook-deliveries?monitor_id=1287&limit=20" \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` | Query | Type | Description | |-------|------|-------------| | `monitor_id` | integer | Restrict to deliveries for one monitor. | | `since` | RFC3339 datetime | Only deliveries dispatched at or after this time. | | `status` | string | Filter by delivery status: `pending`, `retrying`, `delivered`, or `failed`. | | `limit` | integer (1–100) | Page size. Default `50`. | | `offset` | integer | Skip the first N results. | Response shape: ```json { "deliveries": [ { "id": 9001, "webhook_id": 42, "monitor_id": 1287, "account_id": 14, "event_type": "monitor.down", "status": "delivered", "attempt_count": 1, "last_response_code": 200, "last_attempt_at": "2026-05-07T09:14:05Z", "next_attempt_at": "2026-05-07T09:14:05Z", "created_at": "2026-05-07T09:14:02Z" } ], "total": 1, "limit": 50, "offset": 0 } ``` Each delivery record includes: `id`, `webhook_id`, `monitor_id`, `event_type`, `status` (`pending|retrying|delivered|failed`), `attempt_count`, `last_response_code`, `last_attempt_at`, `next_attempt_at`, and `created_at`. There is no per-webhook delivery endpoint — `GET /api/v1/webhooks/{id}/deliveries` does not exist. --- # Browser push notifications > Why push exists, how to enable it, events, and browser limitations. ## Why push exists UptimeMonitoring has no email system by design. Browser push notifications fill the human alerting gap — one-click opt-in, no PII, and instant delivery. ## How to enable 1. Log in to [app.uptimemonitoring.com](https://app.uptimemonitoring.com) 2. Click the bell icon or "Enable notifications" prompt 3. Allow notifications in your browser That's it. No email, no phone number, no configuration. ## What events trigger notifications - **Monitor down** — when a monitor's state flips to down (after cross-region confirmation) - **Monitor up** — when a previously-down monitor recovers ## Subscription management Manage your push subscriptions from the dashboard. You can: - View active subscriptions (browser + OS) - Remove individual subscriptions - Test notifications ## Known browser limitations - **Safari on iOS** — requires adding the site to your home screen first - **Incognito / private mode** — push subscriptions don't persist - **Multiple devices** — each browser/device requires a separate opt-in - **Browser must be running** — notifications queue until the browser is open (behavior varies by OS) --- # Alert bridge: self-hosted Cloudflare Worker > Deploy the open-source alerts-bridge to your own Cloudflare Worker to receive Monitive webhook events as Pushover, ntfy, Slack, Discord, or Telegram notifications. ## What it is The alerts-bridge is an open-source Cloudflare Worker that translates Monitive webhook events into push notifications. Deploy it once to your own Cloudflare account and every monitor's down/up event reaches your phone, Slack workspace, or Discord server — via Pushover, ntfy, Slack, Discord, or Telegram. You own the Worker. Monitive sends the webhook; the bridge forwards it to your provider. No third-party relay, no vendor lock-in. ## Prerequisites - A [Cloudflare account](https://dash.cloudflare.com/sign-up) (free tier is enough) - Node 20 or newer - A UptimeMonitoring.com account with at least one monitor - A provider account — this walkthrough uses [Pushover](https://pushover.net/) ## Get the code ```bash git clone https://github.com/uptimemonitoring/alerts-bridge cd alerts-bridge npm install ``` ## Deploy Log in to Cloudflare, then deploy: ```bash npx wrangler login npm run deploy ``` Wrangler prints the Worker URL when the deploy succeeds: ``` Published alerts-bridge (0.00 sec) https://alerts-bridge..workers.dev ``` Note that URL — you register it with your monitor next. ## Wire to Monitive 1. Open the monitor in [app.uptimemonitoring.com](https://app.uptimemonitoring.com) → the **Webhook** section. 2. Set the webhook URL to the `*.workers.dev` URL from the deploy step and save. 3. Reveal and copy the **Signing secret** shown there — you'll paste it as `MONITOR_WEBHOOK_SECRETS` in the next step. (API equivalent: `PUT /api/v1/monitors//webhook -d '{"url":"…"}'` returns the `secret`; `GET` re-reads it.) The bridge verifies the `X-UptimeMonitoring-Signature` header against this secret on every request — see [Webhooks](/docs/webhooks) for the signature contract. ## Set secrets Configure the Worker secrets with `npx wrangler secret put`. Wrangler prompts you for each value; nothing is written to disk. ```bash # Which provider to use npx wrangler secret put PROVIDER # → pushover # Pushover application token (from pushover.net → Your Applications) # Tip: name the Pushover application "UptimeMonitoring" — that name is what # shows as the source on each push notification. npx wrangler secret put PUSHOVER_TOKEN # Pushover user key (from pushover.net → Your User Key) npx wrangler secret put PUSHOVER_USER # The whs_live_… signing secret you copied from the monitor's Webhook section above npx wrangler secret put MONITOR_WEBHOOK_SECRETS ``` :::caution Never commit these values to source control. `wrangler secret put` stores them as encrypted Cloudflare secrets, not in `wrangler.toml`. Secrets apply to the live Worker immediately — no redeploy needed. ::: ## Test Watch the Worker logs in one terminal: ```bash npx wrangler tail ``` Then trigger a real event: point the monitor at a URL that returns 5xx (or stop the service it watches) so Monitive fires a `down` webhook, then restore it to fire `up`. Confirm each notification arrives on your device, and watch `wrangler tail` for the incoming request and the provider's response. If your monitor's webhook settings offer a test-send button, that's the fastest way to fire a sample delivery without affecting the monitor's real state. ## Provider reference Set `PROVIDER` to one of the values in the first column and supply the corresponding secrets: | `PROVIDER` value | Required secrets | Notes | |---|---|---| | `pushover` | `PUSHOVER_TOKEN`, `PUSHOVER_USER` | Pushover app token + user key | | `ntfy` | `NTFY_TOPIC` (+ optional `NTFY_URL`, `NTFY_TOKEN`) | `NTFY_URL` defaults to `https://ntfy.sh`; `NTFY_TOKEN` only for protected topics | | `slack` | `SLACK_WEBHOOK_URL` | Slack incoming webhook URL | | `discord` | `DISCORD_WEBHOOK_URL` | Discord webhook URL | | `telegram` | `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` | Bot token + target chat ID | ## Troubleshooting **401 from the Worker** The signature verification failed. Check that the `MONITOR_WEBHOOK_SECRETS` value in the Worker matches the signing secret shown in the Monitive webhook settings exactly — trailing whitespace or a copy-paste error will cause every request to be rejected. **No push notification** Signature passed but the provider rejected the request. Double-check `PUSHOVER_TOKEN` and `PUSHOVER_USER` (or the equivalent for your provider). Run `npx wrangler tail` while triggering a test event to see the provider's error response. **413 Request Entity Too Large** The webhook payload exceeded the Worker's request size limit. This should not happen with standard Monitive payloads — if it does, open an issue in the alerts-bridge repository. --- # Incidents > Query the per-account incident log (30-day retention). Down events and flap events are recorded as incidents. A monitor that goes down opens a `down` incident, which closes by setting `resolved_at` when the monitor recovers — recovery does NOT spawn a separate row. Flap events open a `flapping` incident that resolves the same way. The API exposes a queryable log so you can build status pages, post-mortem timelines, or pipe incidents into your own alerting. Retention is **30 days** — incidents older than that are dropped from the table. For longer history, capture incidents to your own store via webhooks. ## List incidents ```bash curl https://api.uptimemonitoring.com/api/v1/incidents \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` Ordered newest first. Supports filtering and pagination via query parameters: | Parameter | Type | Description | |-----------|------|-------------| | `monitor_id` | integer | Restrict to incidents for one monitor. | | `type` | string | `down` or `flapping`. | | `started_after` | RFC3339 datetime | Only incidents started strictly after this time. | | `started_before` | RFC3339 datetime | Only incidents started strictly before this time. | | `limit` | integer (1–100) | Page size. Default `50`. Out-of-range values return `400 limit must be between 1 and 100`. | | `offset` | integer | Skip the first N results. Default `0`. | Response shape: ```json { "incidents": [ { "id": 4422, "monitor_id": 1287, "account_id": 14, "type": "down", "started_at": "2026-05-07T09:14:02Z", "resolved_at": "2026-05-07T09:18:51Z", "evidence": [ { "region": "EU", "timestamp": "2026-05-07T09:14:01Z", "status_code": 503, "ttfb_ms": 2143 }, { "region": "US", "timestamp": "2026-05-07T09:14:00Z", "status_code": 503, "ttfb_ms": 2087 } ], "created_at": "2026-05-07T09:14:02Z" } ], "total": 1, "limit": 50, "offset": 0 } ``` `resolved_at` is **absent** (the key is omitted from the JSON object) while the incident is still ongoing; it appears once the monitor recovers. `total` reports the count of incidents matching the supplied filters (`monitor_id`, `type`, `started_after`, `started_before`) — paginate against this number, not the entire 30-day window. ## Get an incident ```bash curl https://api.uptimemonitoring.com/api/v1/incidents/4422 \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" ``` The response wraps the incident under an `incident` key: ```json { "incident": { "id": 4422, "monitor_id": 1287, "account_id": 14, "type": "down", "started_at": "2026-05-07T09:14:02Z", "resolved_at": "2026-05-07T09:18:51Z", "evidence": [], "created_at": "2026-05-07T09:14:02Z" } } ``` A non-numeric ID returns `400 invalid incident ID`. A numeric-but-missing ID returns `404 incident not found`. ## Incident types | `type` | Meaning | |--------|---------| | `down` | Monitor transitioned from up to down. Resolves when it transitions back to up. | | `flapping` | Monitor crossed the flap threshold (more than 10 transitions per hour). Resolves when traffic stabilises. | The `up` type is reserved for future use and is not currently emitted. --- # Feeds > Per-account incident feed (Atom XML over the RSS-shaped URL) and shareable incident URLs. ## Per-account incident feed Every account exposes an Atom XML feed of incidents at a single URL: ``` https://api.uptimemonitoring.com/feed/{token}/rss ``` The path segment is `feed` (singular, not `/api/v1/feeds/...`). The body is Atom XML even though the path ends in `/rss` — the suffix is preserved for client compatibility, but there is no separate `/atom` route. The token IS the credential — it carries no `Bearer` header, and anyone with the URL can read the feed. Treat it like a password. Find or rotate it from the dashboard under **Feeds**. Subscribe from Slack, RSS readers, or automation tools. Most readers handle Atom transparently. > **Treat the URL like an API key.** The token appears in HTTP referer headers, web-server logs, and any system that processes the URL. Rotate it from the dashboard if it's exposed (paste into the wrong chat, leaked in a screenshot, etc.). ## Shareable incident feed URL You can generate a read-only permalink for your incident feed. This is useful for: - Sharing with team members who don't have an account - Embedding in internal status dashboards - Providing a human-readable fallback alongside webhooks Manage shareable feed URLs from the dashboard. ## Platform news feed A separate feed for platform announcements (maintenance, new features, policy changes) is available at: ``` https://api.uptimemonitoring.com/feed.xml ``` The body is Atom XML and is open (no auth). Subscribe from your feed reader of choice. --- # GitHub Action: assert healthy after deploy > Fail a deploy if health never comes back. *Available since v1.0.0 (May 2026).* ## What it does The `uptimemonitoring/assert-healthy` action polls a monitor and fails the workflow step if the monitor doesn't report healthy within a timeout window. Built for post-deploy health gates. ## Minimal workflow ```yaml - name: Verify deploy health uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.UPTIMEMONITORING_API_KEY }} monitor-id: ${{ vars.MONITOR_ID }} timeout: 120 ``` ## Inputs | Input | Required | Default | Description | |-------|----------|---------|-------------| | `api-key` | Yes | — | UptimeMonitoring API key | | `monitor-id` | Yes | — | Monitor ID to check | | `timeout` | No | `120` | Max seconds to wait for healthy | | `base-url` | No | `https://api.uptimemonitoring.com` | API base URL | ## Outputs | Output | Description | |--------|-------------| | `healthy` | `true` or `false` | | `last-status` | Last observed monitor status | | `last-region` | Region of last check | | `last-ttfb-ms` | TTFB of last check | ## Advanced usage ### Custom timeout ```yaml - name: Verify deploy health uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.UPTIMEMONITORING_API_KEY }} monitor-id: ${{ vars.MONITOR_ID }} timeout: 180 ``` :::note[If you also use Monitive Pro] [Monitive Pro](https://monitive.com) is a separate, full-featured monitoring product from the same team. UptimeMonitoring.com and Monitive Pro have different feature sets, pricing, and accounts. The `base-url` input lets the same action target either product: ```yaml - name: Verify deploy health uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.MONITIVE_API_KEY }} monitor-id: ${{ vars.MONITOR_ID }} base-url: https://app.monitive.com/api timeout: 180 ``` ::: ### Using in a matrix The block below is a `jobs..steps:` fragment — drop it into a full workflow with `name`, `on`, and `runs-on` already declared: ```yaml jobs: assert-healthy: runs-on: ubuntu-latest strategy: matrix: monitor: [1287, 1288, 1289] steps: - uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.UPTIMEMONITORING_API_KEY }} monitor-id: ${{ matrix.monitor }} ``` --- # Errors > Error envelope, observed messages, and common error responses. ## Error envelope The `/api/v1/*` surface uses a flat envelope: ```json { "error": "url is required" } ``` The `error` field is always a single human-readable string for these endpoints. There is no `code`, `message`, or nested object — clients should match on the HTTP status and (where useful) the literal `error` string. There are two documented exceptions: - The `/oauth2/*` endpoints (`/oauth2/register`, `/oauth2/token`, `/oauth2/revoke`) follow RFC 7591 / RFC 6749 and return `{"error":"", "error_description":""}` (e.g. `{"error":"invalid_client_metadata","error_description":"redirect_uris is required"}`). - The monitor-test endpoint's kill-switch 503 carries `{error, message}` — see [503 Kill-switch](#503-kill-switch-active-test-monitor-endpoint-only) below. ## Common messages A representative — not exhaustive — list of the literal `error` strings the API returns today, grouped by HTTP status. New endpoints add new strings; pin clients on the HTTP status first and only fall back to literal-string matching where the wording is part of an established contract (validator output, rate-limit hints). ### 400 Bad Request | Message | When | |---------|------| | `name is required` | Create monitor body missing `name` | | `url is required` | Create monitor body missing `url` | | `interval_sec must be at least 60` | `interval_sec` below the 60-second minimum | | `expected_status must be a single integer between 100 and 599` | `expected_status` non-integer or out of range | | `type must be one of: down, flapping` | Invalid incident `type` query value | | `limit must be between 1 and 100` | Pagination `limit` out of range | | `invalid monitor ID` | Path parameter is non-numeric on monitor endpoints (e.g. `/monitors/abc`) | | `invalid incident ID` | Path parameter is non-numeric on `/api/v1/incidents/{id}` | | `invalid key ID` | Path parameter is non-numeric on key endpoints | | `invalid request body` | Body is not valid JSON | | `label must be at least 3 characters` | Key label below the minimum length | | `label must be 100 characters or less` | Key label above the maximum length | | `missing code` | OAuth callback missing `code` query parameter | | `invalid or expired code` | OAuth callback `code` rejected by GitHub | | `url rejected: ip is in a blocked range` | Target URL resolves to a blocked IP range (SSRF guard) | ### 401 Unauthorized | Message | When | |---------|------| | `missing authorization` | No `Authorization` header | | `invalid credentials` | API key not recognised or revoked | ### 403 Forbidden | Message | When | |---------|------| | `account suspended; contact support` | Account suspended (e.g. by the port-scan heuristic). Returned on `/api/v1/*`. | | `forbidden` | Admin-only route hit by a non-admin caller. Opaque on purpose. | ### 404 Not Found | Message | When | |---------|------| | `monitor not found` | Monitor ID is numeric but does not exist on this account | | `incident not found` | Incident ID is numeric but does not exist on this account | | `key not found` | API key ID does not exist on this account | ### 409 Conflict | Message | When | |---------|------| | `cannot delete last key` | Tried to delete the only remaining API key | | `key limit reached` | Account already at the 10-key cap | ### 429 Too Many Requests Rate-limit responses carry a route-specific message in the flat envelope plus a `Retry-After` header (seconds). Examples: | Limiter | Message | |---------|---------| | Account-wide `/api/v1/*` cap | `account rate limit exceeded; retry in s` | | Monitor-creation cap | `monitor creation rate limit exceeded; retry in s` | | Per-monitor manual-test minimum interval | `too many tests for this monitor; retry in s` | | Per-domain cross-account test cap | `domain rate limit exceeded; retry in s` | Don't pin clients on a single literal string; honour `Retry-After` and treat any 429 as transient. ### 500 Internal Server Error The body is `{"error":"internal error"}`. Retry with exponential backoff. ### 503 Kill-switch active (test-monitor endpoint only) `POST /api/v1/monitors/{id}/test` is the one endpoint where the 503 body deviates from the flat envelope. When the dispatcher's global kill switch is engaged, the test endpoint returns: ```json { "error": "kill_switch_active", "message": "check dispatches temporarily disabled" } ``` with a `Retry-After` header. Both `error` and `message` are present. Other 503s (e.g. `kill switch not configured` on admin endpoints) keep the flat `{"error":"..."}` shape. ## Common scenarios ### SSRF blocked ```json { "error": "url rejected: ip 10.0.0.5 is in a blocked range" } ``` Private IPs (10.x, 172.16-31.x, 192.168.x), loopback, and link-local addresses are blocked. See [Security](/docs/security/) for details. ### Validation failure ```json { "error": "expected_status must be a single integer between 100 and 599" } ``` The body is the literal validator output — useful to surface back to the user. --- # Rate Limits > API rate limiting policy, headers, and retry behavior. ## Limits | Resource | Limit | |----------|-------| | API requests | 200 per minute per account | | Monitor creation (`POST /api/v1/monitors`) | 20 per minute per account (independent cap) | | Webhook deliveries | Not counted against your API limit | The two caps are independent — either can 429 a `POST /api/v1/monitors` request. ## Response headers Every API response includes rate limit headers: ``` X-RateLimit-Limit: 200 X-RateLimit-Remaining: 187 X-RateLimit-Reset: 1681310460 ``` ## 429 responses When you exceed the limit, you'll receive a `429 Too Many Requests` response with a `Retry-After` header. The body uses the same flat envelope as all `/api/v1/*` errors (see [/docs/errors/](/docs/errors/#429-too-many-requests)): ```json {"error": "account rate limit exceeded; retry in 1s"} ``` Monitor-creation requests that hit the independent 20/min cap return the same shape with `"monitor creation rate limit exceeded; retry in s"`. ``` Retry-After: 12 ``` Respect the `Retry-After` value. Repeated violations may result in longer cooldown periods. --- # Security and target restrictions > UptimeMonitoring is designed for public uptime checks, not network scanning. ## Private/reserved IP blocking Targets that resolve to private, reserved, or loopback IP ranges are blocked: - `10.0.0.0/8` - `172.16.0.0/12` - `192.168.0.0/16` - `127.0.0.0/8` - `169.254.0.0/16` (link-local) - `::1`, `fc00::/7`, `fe80::/10` ```json { "error": "url rejected: ip 10.0.0.5 is in a blocked range" } ``` The full error envelope is documented in [Errors](/docs/errors/). DNS resolution is cached and re-validated — SSRF via DNS rebinding is blocked. ## Disallowed target classes - Private and reserved IPs (see above) - Targets on non-standard ports for TCP monitors (restricted to common service ports) - Targets that actively opt out (see below) ## Port restrictions for TCP monitors TCP monitors are restricted to a small allowlist of common HTTP-style service ports to prevent the platform being repurposed as a port scanner. The full allowlist is: `80`, `443`, `8080`, `8443`, `3000`, `5000`, `8000`, `8888` Any other port returns `400` with an error message naming the allowed set. Database ports (`3306`, `5432`, `6379`, `27017`), message-queue ports (`5672`, `9092`), and admin-style ports (`22`, `25`, `53`, `110`, `143`, `993`, `995`) are intentionally **not** on the list. ## URL validation All monitor URLs are validated as UTF-8 strings with no null bytes (`U+0000`) and no ASCII control characters (`U+0000`–`U+001F` and `U+007F`). Violations return `400` before the SSRF guard runs. ## Rate limiting and abuse controls - 200 API requests per minute per account - Up to 50 monitors per account (up to 100 during extended free tier) - 10 API keys per account - Exponential backoff on sustained-down monitors - Flap detection collapses noisy state transitions ## Target owner opt-out Target owners can request opt-out by contacting [hello@uptimemonitoring.com](mailto:hello@uptimemonitoring.com). Opted-out domains are blocked from being monitored. --- # Examples > Working code examples for every supported language and integration. :::tip[Working examples repo] Full runnable copies of every snippet on this page live at [github.com/uptimemonitoring/examples](https://github.com/uptimemonitoring/examples) — including self-contained scripts, GitHub Actions workflows, and webhook relay recipes. ::: > The target hostname must resolve at creation time. The snippets below use `https://example.com/` because it actually resolves and returns 200 — fine for a smoke test. In real use, point at your own service (e.g. `https://api.your-domain.com/healthz`). ## curl Create a monitor from any terminal: ```bash curl -X POST https://api.uptimemonitoring.com/api/v1/monitors \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "api-prod", "url": "https://example.com", "type": "http" }' ``` ## Node.js ```js const res = await fetch("https://api.uptimemonitoring.com/api/v1/monitors", { method: "POST", headers: { "Authorization": `Bearer ${process.env.UPTIMEMONITORING_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "api-prod", url: "https://example.com", type: "http", }), }); const data = await res.json(); console.log(`Monitor ${data.monitor.id} created, status: ${data.state.status}`); ``` ## Python ```python import os import requests resp = requests.post( "https://api.uptimemonitoring.com/api/v1/monitors", headers={"Authorization": f"Bearer {os.environ['UPTIMEMONITORING_API_KEY']}"}, json={ "name": "api-prod", "url": "https://example.com", "type": "http", }, ) data = resp.json() print(f"Monitor {data['monitor']['id']} created, status: {data['state']['status']}") ``` ## GitHub Actions ### Post-deploy health gate ```yaml name: Deploy and verify on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./deploy.sh - name: Verify deploy health uses: uptimemonitoring/assert-healthy@v1 with: api-key: ${{ secrets.UPTIMEMONITORING_API_KEY }} monitor-ids: ${{ vars.MONITOR_ID }} ``` ## Claude / Claude Code ```text Add UptimeMonitoring as an MCP server Create a monitor for https://example.com/ Assert that it is healthy after deploy ``` MCP configuration: ```json { "mcpServers": { "uptimemonitoring": { "url": "https://api.uptimemonitoring.com/mcp", "headers": { "Authorization": "Bearer umk_live_..." } } } } ``` ## Cursor Add to `.cursor/mcp.json`: ```json { "mcpServers": { "uptimemonitoring": { "url": "https://api.uptimemonitoring.com/mcp", "headers": { "Authorization": "Bearer umk_live_..." } } } } ``` Then use natural language: ```text Create a monitor for https://example.com/ and tell me if it is healthy ``` ## Webhook to Slack Webhooks are monitor-scoped — set or replace with `PUT`: ```bash # Set the webhook on monitor 1287 to post to Slack curl -X PUT https://api.uptimemonitoring.com/api/v1/monitors/1287/webhook \ -H "Authorization: Bearer $UPTIMEMONITORING_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://hooks.slack.com/services/T.../B.../xxx"}' ``` The webhook payload is JSON — Slack will render it as a message attachment automatically. The HMAC secret is returned on the initial PUT; store it for [signature verification](/docs/webhooks/#signature-verification). --- # FAQ > Common questions about UptimeMonitoring.com. ## Is it really free? Yes. Free plan: up to 50 monitors. Early users can go up to 100 during the extended free tier while paid plans are being finalized. ## Do I need an email address? No. Signup uses GitHub OAuth. Email is optional only if you want future pricing updates. ## How do alerts work without email? Webhooks, browser push, RSS feeds, and MCP queries. See [Alert channels](/docs/webhooks/) and [Push notifications](/docs/push-notifications/). ## How do you avoid false positives? Infrastructure-class failures are re-checked from two additional regions. State only changes when 2 of 3 regions agree. See [Monitors](/docs/monitors/) for details on cross-region confirmation. ## Can I use this in GitHub Actions? Yes — the `uptimemonitoring/assert-healthy` action is built for deploy verification. See [GitHub Action](/docs/github-action/). ## Can I monitor private IPs or internal services? No. Private and reserved targets are blocked. See [Security](/docs/security/) for details on target restrictions.