Monitoring your AI agent can set up.
One API key gives Claude Code, Cursor, ChatGPT, scripts and CI the same powers you have in the dashboard: create monitors, route alerts to SMS, Slack or PagerDuty, and publish status pages. The MCP server is hosted, the REST API is described in OpenAPI, and every call is scoped to your organization.
# 1. an alert channel
curl -X POST https://app.updog.watch/api/v1/notifications \
-H "Authorization: Bearer $UPDOG_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"Ops Slack","type":"slack","config":{"webhookUrl":"https://hooks.slack.com/…"}}'
# → {"ok":true,"notification":{"id":7,…}}
# 2. a monitor that uses it
curl -X POST https://app.updog.watch/api/v1/monitors \
-H "Authorization: Bearer $UPDOG_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"api.acme.dev","type":"http","url":"https://api.acme.dev/health","interval":60,"maxretries":2,"notificationIds":[7]}'
# → {"ok":true,"monitor":{"id":415,"status":"pending","dashboardUrl":"…"}}
# 3. is it up?
curl https://app.updog.watch/api/v1/monitors/415 -H "Authorization: Bearer $UPDOG_API_KEY"
# → "status":"up","lastCheck":{"message":"200 - OK","ping":212}
Authentication
Two ways in, same permissions:
- OAuth 2.1 for MCP clients that support it (Claude.ai, Claude Desktop, Claude Code, ChatGPT, Cursor): just add the server URL. The client discovers the authorization server, you approve it once in the dashboard, and tokens refresh themselves. Nothing to paste.
- API keys for scripts, CI and clients that take a static header. Create one under Settings → API Keys. Keys look like
uk12_8f3a…; the secret is shown once and stored hashed.
Authorization: Bearer uk12_YOUR_KEY
# also accepted:
X-API-Key: uk12_YOUR_KEY
Scope and roles
A key or OAuth grant acts inside one organization, with the role its owner currently holds there. OAuth grants additionally carry a scope: read or read write. Revoke a key or a connected app in Settings, or remove the member; either cuts access immediately.
| Role | Can |
|---|---|
viewer | Read everything: account, monitors, heartbeats, channels, status pages. |
editor | Everything above, plus create, update, pause, resume and delete monitors, channels and status pages. |
admin, owner | Same as editor via the API. Billing, members and plan changes stay in the dashboard. |
Envelope, limits
- Success:
{"ok": true, …}. Failure:{"ok": false, "error": {"code", "message"}}with a matching HTTP status. Codes are listed under Errors. - Rate limit: 120 requests per minute per key (
429 RATE_LIMITED). - Plan limits apply exactly as in the dashboard and come back as
402with the limit in the body, so an agent can explain what to upgrade. - Timestamps are UTC
YYYY-MM-DD HH:mm:ss; intervals are seconds;pingis milliseconds; uptime is a percentage.
MCP server
UpDog hosts a Model Context Protocol server at https://app.updog.watch/mcp (Streamable HTTP, stateless, JSON responses). It authenticates with the same bearer key and exposes the REST API as tools, so an agent can go from "monitor this" to a running monitor with alerts in one conversation. The server also sends the agent short instructions on the right order of operations, so you do not have to prompt it.
Connect a client
With OAuth (no key): give the client the URL https://app.updog.watch/mcp and nothing else. It will open UpDog in your browser, you sign in, pick an organization and click Allow. Works in Claude.ai and Claude Desktop (Settings → Connectors → Add custom connector), ChatGPT (Settings → Connectors, developer mode), Cursor (add the URL with no headers), and Claude Code:
claude mcp add --transport http updog https://app.updog.watch/mcp
# then inside Claude Code: /mcp → updog → Authenticate
With an API key: for clients and scripts that send a fixed header.
claude mcp add --transport http updog https://app.updog.watch/mcp \
--header "Authorization: Bearer uk12_YOUR_KEY"
export UPDOG_API_KEY=uk12_YOUR_KEY
claude plugin marketplace add corey-Robinson1337/uptime-kuma-commercial
claude plugin install updog@updog
Add to Cursor then paste your key into the Authorization header it opens
// ~/.cursor/mcp.json
{
"mcpServers": {
"updog": {
"url": "https://app.updog.watch/mcp",
"headers": { "Authorization": "Bearer uk12_YOUR_KEY" }
}
}
}
{
"mcpServers": {
"updog": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://app.updog.watch/mcp",
"--header", "Authorization: Bearer ${UPDOG_API_KEY}"],
"env": { "UPDOG_API_KEY": "uk12_YOUR_KEY" }
}
}
}
codex mcp add updog --url https://app.updog.watch/mcp \
--header "Authorization: Bearer uk12_YOUR_KEY"
{
"type": "mcp",
"server_label": "updog",
"server_url": "https://app.updog.watch/mcp",
"headers": { "Authorization": "Bearer uk12_YOUR_KEY" },
"require_approval": "never"
}
Import https://app.updog.watch/api/v1/openapi.json as the schema, auth type API Key, header Authorization, value Bearer uk12_YOUR_KEY.
Tool reference
Parameters mirror the REST fields below; every tool returns the REST response as structured JSON plus a text copy. Errors come back as tool errors with the API's CODE: message.
| Tool | Parameters | Notes |
|---|---|---|
get_account | none | Plan, trial, limits, usage. Read-only. Call first. |
list_monitor_types | none | Types with required/optional fields. Read-only. |
list_monitors | status? all|up|down|pending|paused|maintenance, search? | Live status, last check, uptime per monitor. Read-only. |
get_monitor | id | Read-only. |
get_monitor_heartbeats | id, limit? (1–200, default 20), important? | important: true = only state changes, i.e. the incident history. Read-only. |
create_monitor | name, type?, url?, hostname?, port?, interval?, retryInterval?, maxretries?, method?, acceptedStatusCodes?, headers?, body?, keyword?, invertKeyword?, jsonPath?, jsonPathOperator?, expectedValue?, dnsResolveType?, dnsResolveServer?, ignoreTls?, expiryNotification?, upsideDown?, description?, parentId?, notificationIds?, active? | Starts checking immediately. Push monitors return pushUrl. Omitting notificationIds attaches the organization's default channels. |
update_monitor | id + any field above | Partial update; restarts the check. active: false pauses. |
pause_monitor / resume_monitor | id | History is kept. |
delete_monitor | id | Destructive; agents are told to do this only on explicit request. |
list_notifications | none | Channels plus the shortcut types below. Read-only. |
create_notification | name, type, config, isDefault?, applyExisting? | See Notification channels for config per type. |
delete_notification | id | Detaches from every monitor. Destructive. |
list_status_pages | none | Read-only. |
create_status_page | title, slug?, description?, monitorIds?, groupName? | Returns the public url. |
delete_status_page | id (number or slug) | Destructive. |
OAuth 2.1 details
For client authors. Discovery and registration are standard, so most MCP SDKs need no configuration.
| Protected resource metadata | https://app.updog.watch/.well-known/oauth-protected-resource/mcp |
| Authorization server metadata | https://app.updog.watch/.well-known/oauth-authorization-server |
| Dynamic client registration | POST https://app.updog.watch/oauth/register (RFC 7591, public clients with token_endpoint_auth_method: none supported) |
| Authorize | GET https://app.updog.watch/oauth/authorize, response_type code, PKCE S256 required, optional resource (RFC 8707) |
| Token | POST https://app.updog.watch/oauth/token, grants authorization_code and refresh_token; form or JSON body. Access tokens last 1 hour, refresh tokens 30 days and rotate on use. |
| Revoke | POST https://app.updog.watch/oauth/revoke (RFC 7009) |
| Scopes | read, write |
| Redirect URIs | https:// URLs, http://localhost / 127.0.0.1, and native app schemes |
REST reference
Base URL https://app.updog.watch/api/v1. The machine-readable version is openapi.json (OpenAPI 3.1); this page is rendered from it, so they cannot disagree.
Account
Account, plan, limits and usage
Use this first: limits.monitors - usage.monitors is how many monitors can still be created, limits.minInterval is the fastest allowed check.
Request
curl https://app.updog.watch/api/v1/me \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"organization": {
"id": 12,
"name": "Acme"
},
"user": {
"id": 3,
"email": "ops@acme.dev"
},
"role": "owner",
"apiKeyId": 9,
"plan": {
"key": "watchdog",
"label": "WatchDog",
"basePlan": "watchdog",
"trial": {
"active": false,
"endsAt": null,
"daysLeft": 0,
"planKey": "bigdog",
"used": false
}
},
"limits": {
"monitors": 50,
"statusPages": 5,
"users": 5,
"minInterval": 60,
"sms": true
},
"usage": {
"monitors": 12,
"statusPages": 1,
"users": 3
},
"baseUrl": "https://app.updog.watch"
}
Errors: 401 Missing or invalid key
Monitors
Supported monitor types
Each type with its required and optional fields.
Request
curl https://app.updog.watch/api/v1/monitor-types \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"types": [
{
"type": "http",
"label": "HTTP(S)",
"description": "Request a URL and expect an accepted status code (default 200-299).",
"required": [
"url"
],
"optional": [
"method",
"acceptedStatusCodes",
"headers",
"body",
"ignoreTls",
"expiryNotification",
"maxredirects"
]
},
{
"type": "keyword",
"label": "HTTP(S) keyword",
"description": "Request a URL and require (or forbid, with invertKeyword) a keyword in the response body.",
"required": [
"url",
"keyword"
],
"optional": [
"invertKeyword",
"method",
"acceptedStatusCodes",
"headers",
"body",
"ignoreTls"
]
}
]
}
List monitors
All monitors in the organization with live status, last check and uptime. status filters on the computed status; search matches name, URL and hostname.
| Name | In | Type | Description |
|---|---|---|---|
status | query | string | Only monitors in this state. One of: all, up, down, pending, paused, maintenance. |
search | query | string | Case-insensitive substring of name, URL or hostname. |
Request
curl https://app.updog.watch/api/v1/monitors?status=down \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"count": 1,
"monitors": [
{
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": true,
"interval": 60,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "up",
"lastCheck": {
"status": "up",
"time": "2026-10-28 21:14:07",
"message": "200 - OK",
"ping": 212
},
"uptime24h": 100,
"uptime30d": 99.98,
"avgPing24h": 207
}
]
}
Create a monitor
Creates the monitor and runs the first check within seconds. Push monitors return pushUrl, the URL your job must call. If notificationIds is omitted the organization's default channels are attached.
Body fields (29)
| Field | Type | Default | Description |
|---|---|---|---|
name | string | Display name. Required on create. | |
type | string | http | Monitor type. See GET /monitor-types for what each needs. One of: http, keyword, json-query, port, ping, dns, push, group. |
url | url | Absolute http(s) URL. Required for http, keyword, json-query. | |
hostname | string | Host name or IP. Required for port, ping, dns. | |
port | integer | TCP port. Required for port. | |
interval | integer | 60 | Seconds between checks. Floor is the plan's minInterval (Puppy 300, WatchDog 60, BigDog 30). |
retryInterval | integer | Seconds between retries after a failed check. Defaults to interval. | |
maxretries | integer | 0 | Failed checks tolerated before the monitor is marked down and alerts fire. 0 alerts on the first failure. Alias: maxRetries. |
resendInterval | integer | 0 | Re-send the down alert every N checks while still down (0 = once). |
timeout | integer | Request timeout in seconds. Default 80% of interval (ping: 10). | |
method | string | GET | One of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. |
acceptedStatusCodes | string[] | ['200-299'] | Status codes or ranges counted as up, e.g. ["200-299", "301"]. |
headers | string | JSON object of request headers, as a string, e.g. "{\"Authorization\": \"Bearer …\"}". | |
body | string | Request body for POST/PUT/PATCH (sent as JSON). | |
keyword | string | Text that must appear in the response body. Required for keyword. | |
invertKeyword | boolean | False | Alert when the keyword IS present. |
jsonPath | string | JSONata expression evaluated against the JSON response, e.g. $.status. Required for json-query. | |
jsonPathOperator | string | == | One of: ==, !=, >, >=, <, <=, contains, not contains, starts with. |
expectedValue | string | Value the expression result is compared with. Required for json-query. | |
dnsResolveType | string | A | One of: A, AAAA, CNAME, MX, NS, TXT, SOA, SRV, CAA, PTR. |
dnsResolveServer | string | 1.1.1.1 | Resolver to query. |
ignoreTls | boolean | False | Skip TLS certificate validation. |
expiryNotification | boolean | False | Also alert before the TLS certificate expires. |
upsideDown | boolean | False | Invert the result: a successful check counts as down. |
maxredirects | integer | 10 | |
description | string | ||
parentId | integer | null | Group monitor to nest under (null to un-nest). | |
notificationIds | integer[] | Alert channels to attach (replaces the current set). On create, defaults to the organization's default channels. | |
active | boolean | True | false creates the monitor paused (or pauses it on update). |
Request
curl -X POST https://app.updog.watch/api/v1/monitors \
-H "Authorization: Bearer $UPDOG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "api.acme.dev",
"type": "http",
"url": "https://api.acme.dev/health",
"interval": 60,
"maxretries": 2,
"notificationIds": [
7
]
}'
Response example
{
"ok": true,
"monitor": {
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": true,
"interval": 60,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "pending",
"lastCheck": null,
"uptime24h": 0,
"uptime30d": 0,
"avgPing24h": null
}
}
Errors: 402 Plan limit: PLAN_LIMIT_MONITORS, PLAN_MIN_INTERVAL, PLAN_MIN_RETRY_INTERVAL · 422 VALIDATION: missing or invalid fields
Get a monitor
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
Request
curl https://app.updog.watch/api/v1/monitors/415 \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"monitor": {
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": true,
"interval": 60,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "up",
"lastCheck": {
"status": "up",
"time": "2026-10-28 21:14:07",
"message": "200 - OK",
"ping": 212
},
"uptime24h": 100,
"uptime30d": 99.98,
"avgPing24h": 207
}
}
Errors: 404 NOT_FOUND
Update a monitor
Partial update: send only the fields to change. The check restarts with the new settings. active: false pauses, active: true resumes.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
Body fields (29)
| Field | Type | Default | Description |
|---|---|---|---|
name | string | Display name. Required on create. | |
type | string | http | Monitor type. See GET /monitor-types for what each needs. One of: http, keyword, json-query, port, ping, dns, push, group. |
url | url | Absolute http(s) URL. Required for http, keyword, json-query. | |
hostname | string | Host name or IP. Required for port, ping, dns. | |
port | integer | TCP port. Required for port. | |
interval | integer | 60 | Seconds between checks. Floor is the plan's minInterval (Puppy 300, WatchDog 60, BigDog 30). |
retryInterval | integer | Seconds between retries after a failed check. Defaults to interval. | |
maxretries | integer | 0 | Failed checks tolerated before the monitor is marked down and alerts fire. 0 alerts on the first failure. Alias: maxRetries. |
resendInterval | integer | 0 | Re-send the down alert every N checks while still down (0 = once). |
timeout | integer | Request timeout in seconds. Default 80% of interval (ping: 10). | |
method | string | GET | One of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. |
acceptedStatusCodes | string[] | ['200-299'] | Status codes or ranges counted as up, e.g. ["200-299", "301"]. |
headers | string | JSON object of request headers, as a string, e.g. "{\"Authorization\": \"Bearer …\"}". | |
body | string | Request body for POST/PUT/PATCH (sent as JSON). | |
keyword | string | Text that must appear in the response body. Required for keyword. | |
invertKeyword | boolean | False | Alert when the keyword IS present. |
jsonPath | string | JSONata expression evaluated against the JSON response, e.g. $.status. Required for json-query. | |
jsonPathOperator | string | == | One of: ==, !=, >, >=, <, <=, contains, not contains, starts with. |
expectedValue | string | Value the expression result is compared with. Required for json-query. | |
dnsResolveType | string | A | One of: A, AAAA, CNAME, MX, NS, TXT, SOA, SRV, CAA, PTR. |
dnsResolveServer | string | 1.1.1.1 | Resolver to query. |
ignoreTls | boolean | False | Skip TLS certificate validation. |
expiryNotification | boolean | False | Also alert before the TLS certificate expires. |
upsideDown | boolean | False | Invert the result: a successful check counts as down. |
maxredirects | integer | 10 | |
description | string | ||
parentId | integer | null | Group monitor to nest under (null to un-nest). | |
notificationIds | integer[] | Alert channels to attach (replaces the current set). On create, defaults to the organization's default channels. | |
active | boolean | True | false creates the monitor paused (or pauses it on update). |
Request
curl -X PATCH https://app.updog.watch/api/v1/monitors/415 \
-H "Authorization: Bearer $UPDOG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"interval": 30,
"notificationIds": [
7,
8
]
}'
Response example
{
"ok": true,
"monitor": {
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": true,
"interval": 30,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7,
8
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "up",
"lastCheck": {
"status": "up",
"time": "2026-10-28 21:14:07",
"message": "200 - OK",
"ping": 212
},
"uptime24h": 100,
"uptime30d": 99.98,
"avgPing24h": 207
}
}
Errors: 402 Plan limit · 404 NOT_FOUND · 422 VALIDATION
Delete a monitor
Permanent. Deletes the monitor and all its history.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
Request
curl -X DELETE https://app.updog.watch/api/v1/monitors/415 \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"deleted": 415
}
Errors: 404 NOT_FOUND
Pause a monitor
Stops checks and alerts; history is kept. Use around deploys.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
Request
curl -X POST https://app.updog.watch/api/v1/monitors/415/pause \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"monitor": {
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": false,
"interval": 60,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "paused",
"lastCheck": {
"status": "up",
"time": "2026-10-28 21:14:07",
"message": "200 - OK",
"ping": 212
},
"uptime24h": 100,
"uptime30d": 99.98,
"avgPing24h": 207
}
}
Resume a monitor
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
Request
curl -X POST https://app.updog.watch/api/v1/monitors/415/resume \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"monitor": {
"id": 415,
"name": "api.acme.dev",
"type": "http",
"description": null,
"active": true,
"interval": 60,
"retryInterval": 60,
"maxretries": 2,
"parentId": null,
"notificationIds": [
7
],
"tags": [],
"dashboardUrl": "https://app.updog.watch/dashboard/415",
"url": "https://api.acme.dev/health",
"method": "GET",
"acceptedStatusCodes": [
"200-299"
],
"status": "up",
"lastCheck": {
"status": "up",
"time": "2026-10-28 21:14:07",
"message": "200 - OK",
"ping": 212
},
"uptime24h": 100,
"uptime30d": 99.98,
"avgPing24h": 207
}
}
Recent check results
Newest first. important=1 returns only state changes (went down, came back), which is the incident history.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Monitor ID |
limit | query | integer | |
important | query | string | 1 = only state changes. One of: 0, 1. |
Request
curl https://app.updog.watch/api/v1/monitors/415/heartbeats?important=1&limit=10 \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"monitorId": 415,
"heartbeats": [
{
"status": "up",
"time": "2026-10-28 02:18:40",
"message": "200 - OK",
"ping": 198,
"important": true,
"duration": 60
},
{
"status": "down",
"time": "2026-10-28 02:14:07",
"message": "Request failed with status code 503",
"ping": null,
"important": true,
"duration": 60
}
]
}
Notifications
List alert channels
Also returns shortcuts: the simplified channel types POST accepts and the config each needs.
Request
curl https://app.updog.watch/api/v1/notifications \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"notifications": [
{
"id": 7,
"name": "Ops Slack",
"type": "slack",
"isDefault": true,
"active": true
}
],
"shortcuts": {
"email": "Email from UpDog's mail server (no SMTP setup). config: { to }",
"sms": "SMS from UpDog (paid plans). config: { phone } in E.164, e.g. +15551234567",
"slack": "Slack incoming webhook. config: { webhookUrl, channel? }",
"discord": "Discord webhook. config: { webhookUrl }",
"teams": "Microsoft Teams incoming webhook. config: { webhookUrl }",
"webhook": "Generic HTTP webhook, JSON POST. config: { url }",
"telegram": "Telegram bot. config: { botToken, chatId }",
"pagerduty": "PagerDuty Events API v2. config: { integrationKey }",
"ntfy": "ntfy topic. config: { topic, serverUrl? }"
}
}
Create an alert channel
Use a shortcut type with a small config (see the Notification channels reference), or any raw UpDog provider type with its native fields. SMS needs a paid plan.
Body fields (5)
| Field | Type | Default | Description |
|---|---|---|---|
namerequired | string | ||
typerequired | string | Shortcut (email, sms, slack, discord, teams, webhook, telegram, pagerduty, ntfy) or a raw provider type. | |
config | object | Type-specific fields. Shortcuts: email {to}, sms {phone}, slack {webhookUrl, channel?}, discord {webhookUrl}, teams {webhookUrl}, webhook {url}, telegram {botToken, chatId}, pagerduty {integrationKey}, ntfy {topic, serverUrl?}. | |
isDefault | boolean | False | Attach to every new monitor by default. |
applyExisting | boolean | False | Also attach to all existing monitors now. |
Request
curl -X POST https://app.updog.watch/api/v1/notifications \
-H "Authorization: Bearer $UPDOG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ops Slack",
"type": "slack",
"config": {
"webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX"
},
"isDefault": true
}'
Response example
{
"ok": true,
"notification": {
"id": 7,
"name": "Ops Slack",
"type": "slack",
"isDefault": true,
"active": true
}
}
Errors: 402 PLAN_FEATURE_REQUIRED (SMS on free plan) · 422 VALIDATION
Delete an alert channel
Detaches it from every monitor first.
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | integer | Notification ID |
Request
curl -X DELETE https://app.updog.watch/api/v1/notifications/7 \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"deleted": 7
}
Errors: 404 NOT_FOUND
Status pages
List status pages
Request
curl https://app.updog.watch/api/v1/status-pages \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"statusPages": [
{
"id": 3,
"slug": "acme-status",
"title": "Acme Status",
"description": null,
"published": true,
"url": "https://app.updog.watch/status/acme-status",
"groups": [
{
"id": 5,
"name": "Services",
"monitorIds": [
415,
416
]
}
]
}
]
}
Create a status page
Publishes immediately at url. Monitors are shown in one group named groupName. Logos, custom domains and more groups are edited in the dashboard.
Body fields (5)
| Field | Type | Default | Description |
|---|---|---|---|
titlerequired | string | ||
slug | string | Defaults to a slug derived from the title. Must be unique across UpDog. | |
description | string | ||
monitorIds | integer[] | ||
groupName | string | Services |
Request
curl -X POST https://app.updog.watch/api/v1/status-pages \
-H "Authorization: Bearer $UPDOG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Acme Status",
"monitorIds": [
415,
416
]
}'
Response example
{
"ok": true,
"statusPage": {
"id": 3,
"slug": "acme-status",
"title": "Acme Status",
"description": null,
"published": true,
"url": "https://app.updog.watch/status/acme-status",
"groups": [
{
"id": 5,
"name": "Services",
"monitorIds": [
415,
416
]
}
]
}
}
Errors: 402 PLAN_LIMIT_STATUS_PAGES · 409 SLUG_TAKEN · 422 VALIDATION
Delete a status page
| Name | In | Type | Description |
|---|---|---|---|
idOrSlugrequired | path | string | Numeric ID or slug |
Request
curl -X DELETE https://app.updog.watch/api/v1/status-pages/acme-status \
-H "Authorization: Bearer $UPDOG_API_KEY"
Response example
{
"ok": true,
"deleted": 3,
"slug": "acme-status"
}
Errors: 404 NOT_FOUND
Monitor types
The API exposes the types an agent can set up without extra infrastructure. The dashboard offers more (MySQL, Postgres, Redis, MQTT, Docker, real browser, …).
| type | What it checks | Required | Optional |
|---|---|---|---|
httpHTTP(S) | Request a URL and expect an accepted status code (default 200-299). | url | method acceptedStatusCodes headers body ignoreTls expiryNotification maxredirects |
keywordHTTP(S) keyword | Request a URL and require (or forbid, with invertKeyword) a keyword in the response body. | url keyword | invertKeyword method acceptedStatusCodes headers body ignoreTls |
json-queryHTTP(S) JSON query | Request a JSON URL, evaluate a JSONata/JSONPath expression and compare it with expectedValue. | url jsonPath expectedValue | jsonPathOperator method headers body |
portTCP port | Open a TCP connection to hostname:port. | hostname port | – |
pingPing | ICMP ping a hostname or IP. | hostname | – |
dnsDNS | Resolve a hostname and optionally require a specific record value (use keyword/expectedValue via conditions in the UI). | hostname | dnsResolveType dnsResolveServer |
pushPush / heartbeat (cron job) | Passive monitor: your job calls the returned pushUrl on success; UpDog alerts when it goes quiet for longer than the interval. | none | – |
groupGroup | A folder that rolls up other monitors (set parentId on children). Groups do not count towards the monitor limit. | none | – |
Common to all: interval, retryInterval, maxretries, resendInterval, timeout, description, parentId, notificationIds, active, upsideDown.
Notification channels
Shortcut types take a small config. Anything else UpDog supports (90+ providers) can be created by passing the provider's raw type and native fields; the dashboard's notification dialog shows the field names.
| type | config | Notes |
|---|---|---|
email | to | Email from UpDog's mail server (no SMTP setup). config: { to } |
sms | phone | SMS from UpDog (paid plans). config: { phone } in E.164, e.g. +15551234567 |
slack | webhookUrl | Slack incoming webhook. config: { webhookUrl, channel? } |
discord | webhookUrl | Discord webhook. config: { webhookUrl } |
teams | webhookUrl | Microsoft Teams incoming webhook. config: { webhookUrl } |
webhook | url | Generic HTTP webhook, JSON POST. config: { url } |
telegram | botToken chatId | Telegram bot. config: { botToken, chatId } |
pagerduty | integrationKey | PagerDuty Events API v2. config: { integrationKey } |
ntfy | topic | ntfy topic. config: { topic, serverUrl? } |
# SMS to the on-call phone (paid plans), applied to every monitor now and in future
curl -X POST https://app.updog.watch/api/v1/notifications \
-H "Authorization: Bearer $UPDOG_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"On-call SMS","type":"sms","config":{"phone":"+15551234567"},"isDefault":true,"applyExisting":true}'
Push monitors (cron jobs, backups, queues)
A push monitor is passive: UpDog waits for your job to call its pushUrl. If no call arrives within interval seconds the monitor goes down and alerts fire. Create one with type: "push" and an interval a little longer than the job's schedule.
# crontab: nightly backup at 02:00, monitor interval 90000 s (25 h)
0 2 * * * /usr/local/bin/backup.sh && curl -fsS "$PUSH_URL" > /dev/null
# report a failure explicitly, with a message and a duration in ms
curl -fsS "$PUSH_URL&status=down&msg=dump+failed&ping=0"
# GitHub Actions step at the end of a scheduled workflow
- name: Heartbeat
if: success()
run: curl -fsS "${{ secrets.UPDOG_PUSH_URL }}" > /dev/null
Query parameters: status (up default, or down), msg (shown in the dashboard and alerts), ping (number, e.g. job duration in ms). Both GET and POST work.
Webhook payload
A webhook channel POSTs JSON on every state change (and on resend). Treat heartbeat.status as the source of truth: 0 down, 1 up, 2 pending, 3 maintenance.
POST {your url}
Content-Type: application/json
{
"heartbeat": {
"monitorID": 415, "status": 0, "time": "2026-10-28 02:14:07",
"msg": "Request failed with status code 503", "ping": null,
"important": true, "duration": 60, "retries": 2
},
"monitor": {
"id": 415, "name": "api.acme.dev", "type": "http",
"url": "https://api.acme.dev/health", "interval": 60, "tags": []
},
"msg": "[api.acme.dev] [🔴 Down] Request failed with status code 503"
}
Need a different shape? Create the channel in the dashboard with content type custom and a template, or point the webhook at a small function that reshapes it.
Errors and plan limits
{
"ok": false,
"error": {
"code": "PLAN_MIN_INTERVAL",
"message": "Plan minimum interval is 300s on Puppy. Upgrade at /pricing to check faster.",
"minInterval": 300,
"plan": "Puppy"
}
}
| HTTP | code | Meaning |
|---|---|---|
| 400 | INVALID_JSON | Request body is not valid JSON. |
| 401 | INVALID_TOKEN | Malformed, unknown or wrong key. |
| 401 | KEY_DISABLED | Key was disabled in Settings → API Keys. |
| 401 | KEY_EXPIRED | Key passed its expiry date. |
| 401 | UNAUTHENTICATED | No Authorization header. |
| 402 | PLAN_FEATURE_REQUIRED | Feature (e.g. SMS) is not included in the plan. |
| 402 | PLAN_LIMIT_MONITORS | Organization is at its plan's monitor limit. |
| 402 | PLAN_LIMIT_STATUS_PAGES | Organization is at its plan's status page limit. |
| 402 | PLAN_MIN_INTERVAL | interval is below the plan's minimum (minInterval in the error). |
| 402 | PLAN_MIN_RETRY_INTERVAL | retryInterval is below the plan's minimum. |
| 403 | FORBIDDEN | Key's role is too low for this action (writes need editor or higher). |
| 403 | NOT_A_MEMBER | The key's creator is no longer a member of the organization. |
| 403 | NO_ORGANIZATION | Key is not attached to an organization. |
| 404 | NOT_FOUND | No such resource in this organization, or no such route. |
| 409 | SLUG_TAKEN | Status page slug already in use. |
| 422 | VALIDATION | Invalid or missing fields; the message says which. |
| 429 | RATE_LIMITED | More than 120 requests in a minute from this key. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
Plan limits today: Puppy (free) 10 monitors, 5-minute checks, 1 status page, no SMS. WatchDog 50 monitors, 1-minute checks, 5 status pages, SMS. BigDog 200 monitors, 30-second checks, 20 status pages, SMS. GET /me returns the live numbers for your organization.
Examples
Monitor every URL in a file
# urls.txt: one URL per line. Attaches channel 7 to each.
while read -r url; do
curl -s -X POST https://app.updog.watch/api/v1/monitors \
-H "Authorization: Bearer $UPDOG_API_KEY" -H "Content-Type: application/json" \
-d "{\"name\":\"$url\",\"type\":\"http\",\"url\":\"$url\",\"maxretries\":1,\"notificationIds\":[7]}" | jq -r '.monitor.id // .error.message'
done < urls.txt
Pause during a deploy (GitHub Actions)
- name: Pause monitor
run: curl -fsS -X POST https://app.updog.watch/api/v1/monitors/415/pause -H "Authorization: Bearer ${{ secrets.UPDOG_API_KEY }}"
- name: Deploy
run: ./deploy.sh
- name: Resume monitor
if: always()
run: curl -fsS -X POST https://app.updog.watch/api/v1/monitors/415/resume -H "Authorization: Bearer ${{ secrets.UPDOG_API_KEY }}"
Python
import os, requests
API = "https://app.updog.watch/api/v1"
H = {"Authorization": f"Bearer {os.environ['UPDOG_API_KEY']}"}
down = requests.get(f"{API}/monitors", params={"status": "down"}, headers=H).json()["monitors"]
for m in down:
beats = requests.get(f"{API}/monitors/{m['id']}/heartbeats", params={"important": 1, "limit": 1}, headers=H).json()["heartbeats"]
print(m["name"], "down since", beats[0]["time"], beats[0]["message"])
FAQ
Does it work on the free plan?
OAuth or API key?
Will my agent delete things by accident?
Is there a local package, SDK or CLI?
mcp-remote. The REST API is plain JSON with an OpenAPI description, so any generator (openapi-generator, Kiota, Stainless) can produce a typed client in your language.Where do I report a problem?
error.code and the time; API calls are in your organization's audit log.Give your agent a watchdog
Free for 10 monitors, 14 days of every paid feature, no card. Create a key in Settings → API Keys and connect in one line.
Start monitoring free