Developer docs · API v1.0.0

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.

Quickstart, three calls
# 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.

RoleCan
viewerRead everything: account, monitors, heartbeats, channels, status pages.
editorEverything above, plus create, update, pause, resume and delete monitors, channels and status pages.
admin, ownerSame 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 402 with 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; ping is 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 Code
claude mcp add --transport http updog https://app.updog.watch/mcp \
  --header "Authorization: Bearer uk12_YOUR_KEY"
Claude Code plugin (MCP server + a skill that knows when to use it)
export UPDOG_API_KEY=uk12_YOUR_KEY
claude plugin marketplace add corey-Robinson1337/uptime-kuma-commercial
claude plugin install updog@updog
Cursor

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" }
    }
  }
}
Claude Desktop, Windsurf and other stdio-only clients (via mcp-remote)
{
  "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" }
    }
  }
}
OpenAI Codex CLI
codex mcp add updog --url https://app.updog.watch/mcp \
  --header "Authorization: Bearer uk12_YOUR_KEY"
OpenAI Agents SDK / Responses API (hosted MCP tool)
{
  "type": "mcp",
  "server_label": "updog",
  "server_url": "https://app.updog.watch/mcp",
  "headers": { "Authorization": "Bearer uk12_YOUR_KEY" },
  "require_approval": "never"
}
Custom GPT (Actions)

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.

ToolParametersNotes
get_accountnonePlan, trial, limits, usage. Read-only. Call first.
list_monitor_typesnoneTypes with required/optional fields. Read-only.
list_monitorsstatus? all|up|down|pending|paused|maintenance, search?Live status, last check, uptime per monitor. Read-only.
get_monitoridRead-only.
get_monitor_heartbeatsid, limit? (1–200, default 20), important?important: true = only state changes, i.e. the incident history. Read-only.
create_monitorname, 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_monitorid + any field abovePartial update; restarts the check. active: false pauses.
pause_monitor / resume_monitoridHistory is kept.
delete_monitoridDestructive; agents are told to do this only on explicit request.
list_notificationsnoneChannels plus the shortcut types below. Read-only.
create_notificationname, type, config, isDefault?, applyExisting?See Notification channels for config per type.
delete_notificationidDetaches from every monitor. Destructive.
list_status_pagesnoneRead-only.
create_status_pagetitle, slug?, description?, monitorIds?, groupName?Returns the public url.
delete_status_pageid (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 metadatahttps://app.updog.watch/.well-known/oauth-protected-resource/mcp
Authorization server metadatahttps://app.updog.watch/.well-known/oauth-authorization-server
Dynamic client registrationPOST https://app.updog.watch/oauth/register (RFC 7591, public clients with token_endpoint_auth_method: none supported)
AuthorizeGET https://app.updog.watch/oauth/authorize, response_type code, PKCE S256 required, optional resource (RFC 8707)
TokenPOST 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.
RevokePOST https://app.updog.watch/oauth/revoke (RFC 7009)
Scopesread, write
Redirect URIshttps:// 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

GET /api/v1/me viewer

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

GET /api/v1/monitor-types viewer

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"
      ]
    }
  ]
}
GET /api/v1/monitors viewer

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.

Parameters
NameInTypeDescription
statusquerystringOnly monitors in this state. One of: all, up, down, pending, paused, maintenance.
searchquerystringCase-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
    }
  ]
}
POST /api/v1/monitors editor

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)
FieldTypeDefaultDescription
namestringDisplay name. Required on create.
typestringhttpMonitor type. See GET /monitor-types for what each needs. One of: http, keyword, json-query, port, ping, dns, push, group.
urlurlAbsolute http(s) URL. Required for http, keyword, json-query.
hostnamestringHost name or IP. Required for port, ping, dns.
portintegerTCP port. Required for port.
intervalinteger60Seconds between checks. Floor is the plan's minInterval (Puppy 300, WatchDog 60, BigDog 30).
retryIntervalintegerSeconds between retries after a failed check. Defaults to interval.
maxretriesinteger0Failed checks tolerated before the monitor is marked down and alerts fire. 0 alerts on the first failure. Alias: maxRetries.
resendIntervalinteger0Re-send the down alert every N checks while still down (0 = once).
timeoutintegerRequest timeout in seconds. Default 80% of interval (ping: 10).
methodstringGETOne of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
acceptedStatusCodesstring[]['200-299']Status codes or ranges counted as up, e.g. ["200-299", "301"].
headersstringJSON object of request headers, as a string, e.g. "{\"Authorization\": \"Bearer …\"}".
bodystringRequest body for POST/PUT/PATCH (sent as JSON).
keywordstringText that must appear in the response body. Required for keyword.
invertKeywordbooleanFalseAlert when the keyword IS present.
jsonPathstringJSONata expression evaluated against the JSON response, e.g. $.status. Required for json-query.
jsonPathOperatorstring==One of: ==, !=, >, >=, <, <=, contains, not contains, starts with.
expectedValuestringValue the expression result is compared with. Required for json-query.
dnsResolveTypestringAOne of: A, AAAA, CNAME, MX, NS, TXT, SOA, SRV, CAA, PTR.
dnsResolveServerstring1.1.1.1Resolver to query.
ignoreTlsbooleanFalseSkip TLS certificate validation.
expiryNotificationbooleanFalseAlso alert before the TLS certificate expires.
upsideDownbooleanFalseInvert the result: a successful check counts as down.
maxredirectsinteger10
descriptionstring
parentIdinteger | nullGroup monitor to nest under (null to un-nest).
notificationIdsinteger[]Alert channels to attach (replaces the current set). On create, defaults to the organization's default channels.
activebooleanTruefalse 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 /api/v1/monitors/{id} viewer

Get a monitor

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor 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

PATCH /api/v1/monitors/{id} editor

Update a monitor

Partial update: send only the fields to change. The check restarts with the new settings. active: false pauses, active: true resumes.

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor ID
Body fields (29)
FieldTypeDefaultDescription
namestringDisplay name. Required on create.
typestringhttpMonitor type. See GET /monitor-types for what each needs. One of: http, keyword, json-query, port, ping, dns, push, group.
urlurlAbsolute http(s) URL. Required for http, keyword, json-query.
hostnamestringHost name or IP. Required for port, ping, dns.
portintegerTCP port. Required for port.
intervalinteger60Seconds between checks. Floor is the plan's minInterval (Puppy 300, WatchDog 60, BigDog 30).
retryIntervalintegerSeconds between retries after a failed check. Defaults to interval.
maxretriesinteger0Failed checks tolerated before the monitor is marked down and alerts fire. 0 alerts on the first failure. Alias: maxRetries.
resendIntervalinteger0Re-send the down alert every N checks while still down (0 = once).
timeoutintegerRequest timeout in seconds. Default 80% of interval (ping: 10).
methodstringGETOne of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
acceptedStatusCodesstring[]['200-299']Status codes or ranges counted as up, e.g. ["200-299", "301"].
headersstringJSON object of request headers, as a string, e.g. "{\"Authorization\": \"Bearer …\"}".
bodystringRequest body for POST/PUT/PATCH (sent as JSON).
keywordstringText that must appear in the response body. Required for keyword.
invertKeywordbooleanFalseAlert when the keyword IS present.
jsonPathstringJSONata expression evaluated against the JSON response, e.g. $.status. Required for json-query.
jsonPathOperatorstring==One of: ==, !=, >, >=, <, <=, contains, not contains, starts with.
expectedValuestringValue the expression result is compared with. Required for json-query.
dnsResolveTypestringAOne of: A, AAAA, CNAME, MX, NS, TXT, SOA, SRV, CAA, PTR.
dnsResolveServerstring1.1.1.1Resolver to query.
ignoreTlsbooleanFalseSkip TLS certificate validation.
expiryNotificationbooleanFalseAlso alert before the TLS certificate expires.
upsideDownbooleanFalseInvert the result: a successful check counts as down.
maxredirectsinteger10
descriptionstring
parentIdinteger | nullGroup monitor to nest under (null to un-nest).
notificationIdsinteger[]Alert channels to attach (replaces the current set). On create, defaults to the organization's default channels.
activebooleanTruefalse 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 /api/v1/monitors/{id} editor

Delete a monitor

Permanent. Deletes the monitor and all its history.

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor 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

POST /api/v1/monitors/{id}/pause editor

Pause a monitor

Stops checks and alerts; history is kept. Use around deploys.

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor 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
  }
}
POST /api/v1/monitors/{id}/resume editor

Resume a monitor

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor 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
  }
}
GET /api/v1/monitors/{id}/heartbeats viewer

Recent check results

Newest first. important=1 returns only state changes (went down, came back), which is the incident history.

Parameters
NameInTypeDescription
idrequiredpathintegerMonitor ID
limitqueryinteger
importantquerystring1 = 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

GET /api/v1/notifications viewer

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? }"
  }
}
POST /api/v1/notifications editor

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)
FieldTypeDefaultDescription
namerequiredstring
typerequiredstringShortcut (email, sms, slack, discord, teams, webhook, telegram, pagerduty, ntfy) or a raw provider type.
configobjectType-specific fields. Shortcuts: email {to}, sms {phone}, slack {webhookUrl, channel?}, discord {webhookUrl}, teams {webhookUrl}, webhook {url}, telegram {botToken, chatId}, pagerduty {integrationKey}, ntfy {topic, serverUrl?}.
isDefaultbooleanFalseAttach to every new monitor by default.
applyExistingbooleanFalseAlso 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 /api/v1/notifications/{id} editor

Delete an alert channel

Detaches it from every monitor first.

Parameters
NameInTypeDescription
idrequiredpathintegerNotification 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

GET /api/v1/status-pages viewer

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
          ]
        }
      ]
    }
  ]
}
POST /api/v1/status-pages editor

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)
FieldTypeDefaultDescription
titlerequiredstring
slugstringDefaults to a slug derived from the title. Must be unique across UpDog.
descriptionstring
monitorIdsinteger[]
groupNamestringServices
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 /api/v1/status-pages/{idOrSlug} editor

Delete a status page

Parameters
NameInTypeDescription
idOrSlugrequiredpathstringNumeric 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, …).

typeWhat it checksRequiredOptional
http
HTTP(S)
Request a URL and expect an accepted status code (default 200-299).url method acceptedStatusCodes headers body ignoreTls expiryNotification maxredirects
keyword
HTTP(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-query
HTTP(S) JSON query
Request a JSON URL, evaluate a JSONata/JSONPath expression and compare it with expectedValue.url jsonPath expectedValue jsonPathOperator method headers body
port
TCP port
Open a TCP connection to hostname:port.hostname port –
ping
Ping
ICMP ping a hostname or IP.hostname –
dns
DNS
Resolve a hostname and optionally require a specific record value (use keyword/expectedValue via conditions in the UI).hostname dnsResolveType dnsResolveServer
push
Push / 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–
group
Group
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.

typeconfigNotes
emailto Email from UpDog's mail server (no SMTP setup). config: { to }
smsphone SMS from UpDog (paid plans). config: { phone } in E.164, e.g. +15551234567
slackwebhookUrl Slack incoming webhook. config: { webhookUrl, channel? }
discordwebhookUrl Discord webhook. config: { webhookUrl }
teamswebhookUrl Microsoft Teams incoming webhook. config: { webhookUrl }
webhookurl Generic HTTP webhook, JSON POST. config: { url }
telegrambotToken chatId Telegram bot. config: { botToken, chatId }
pagerdutyintegrationKey PagerDuty Events API v2. config: { integrationKey }
ntfytopic 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"
  }
}
HTTPcodeMeaning
400INVALID_JSONRequest body is not valid JSON.
401INVALID_TOKENMalformed, unknown or wrong key.
401KEY_DISABLEDKey was disabled in Settings → API Keys.
401KEY_EXPIREDKey passed its expiry date.
401UNAUTHENTICATEDNo Authorization header.
402PLAN_FEATURE_REQUIREDFeature (e.g. SMS) is not included in the plan.
402PLAN_LIMIT_MONITORSOrganization is at its plan's monitor limit.
402PLAN_LIMIT_STATUS_PAGESOrganization is at its plan's status page limit.
402PLAN_MIN_INTERVALinterval is below the plan's minimum (minInterval in the error).
402PLAN_MIN_RETRY_INTERVALretryInterval is below the plan's minimum.
403FORBIDDENKey's role is too low for this action (writes need editor or higher).
403NOT_A_MEMBERThe key's creator is no longer a member of the organization.
403NO_ORGANIZATIONKey is not attached to an organization.
404NOT_FOUNDNo such resource in this organization, or no such route.
409SLUG_TAKENStatus page slug already in use.
422VALIDATIONInvalid or missing fields; the message says which.
429RATE_LIMITEDMore than 120 requests in a minute from this key.
500INTERNAL_ERRORUnexpected 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?
Yes. The API and MCP server are available on every plan, including Puppy. Plan limits still apply: 10 monitors and 5-minute checks on the free plan. Every new account gets 14 days of BigDog to try faster checks and SMS.
OAuth or API key?
OAuth when the client offers "connect" or "add server by URL" and runs on behalf of a person: it is one click, tokens expire and rotate, and the person can revoke it under Settings → API Keys → Connected apps. API keys for scripts, CI and anything that cannot open a browser.
Will my agent delete things by accident?
The server's instructions tell agents to delete only when explicitly asked, and the delete tools are flagged destructive so clients can ask for confirmation. For a read-only integration, create the key from a viewer account.
Is there a local package, SDK or CLI?
The MCP server is hosted, so remote-capable clients connect directly and stdio-only clients use 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?
Contact us with the request, the 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
Free forever plan · 14-day trial of everything · No credit card