Upwatch REST API

Manage monitors, incidents, maintenance windows, status pages and alert integrations programmatically. JSON in, JSON out.

Base URL/api/v1
FormatJSON request and response bodies (Content-Type: application/json)
IDsNumeric, e.g. monitor 800000001
DatesISO 8601 UTC, e.g. 2026-11-01T14:30:00.000Z
CORSEnabled — the API can be called from browsers.

Authentication

Create keys in the dashboard under Settings → API, then send the key in the Authorization header:

Key typePrefixAccess
Mainuw_main_…Full read & write access to every endpoint.
Read-onlyuw_read_…GET requests only. Ideal for dashboards (Grafana, Home Assistant).
Monitor-specificuw_mon<id>_…GET on one monitor only: /monitors, /monitors/{id}, its stats and logs.
App tokenuwa_…Issued to the upWatch mobile app at sign-in (see Mobile app API). Works on every plan: all GET endpoints plus POST /monitors/{id}/pause|start|check|reset and POST /incidents/{id}/comments. Anything else returns 403-005.

Missing or invalid keys get 401; a read-only or monitor key used for writing gets 403.

Rate limits

600 requests per minute per key. Every response carries:

X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetUnix time (seconds) when the window resets
Retry-AfterSeconds to wait — only on 429 responses

Pagination

List endpoints accept limit (1–200, default 50) and cursor. They return:

{
  "data": [ … ],
  "nextLink": "https://host/api/v1/monitors?limit=50&cursor=50"   // null on the last page
}

Follow nextLink until it is null.

Errors

Errors use standard HTTP status codes and a consistent body:

{
  "code": "404-001",
  "message": "Monitor not found",
  "statusCode": 404,
  "error": "Not Found"
}
StatusMeaning
400Validation failed — message says which field is wrong
401Missing / invalid API key
403Key not allowed to do this
404Resource or endpoint not found
405Method not allowed on this path
429Rate limit exceeded
500Server error

Types & statuses

Monitor types

typeChecksKey fields
HTTPWebsite / endpoint responds with an "up" status codeurl, httpMethodType, successHttpResponseCodes, customHttpHeaders, postValueData, httpUsername/httpPassword, checkSSLErrors, followRedirections
KEYWORDPage contains / does not contain text+ keywordValue, keywordType, keywordCaseType
APIAssertions on JSON body, headers, status or text+ assertions
PINGICMP echourl (host / IP)
PORTTCP port accepts connectionsurl (host), port
UDPUDP service answersurl (host), port, udp
DNSDNS record resolves (to an expected value)url (hostname), dnsRecordType, dnsExpectedValue, dnsServer
SSLCertificate valid and not expiring soonurl (host), port, sslExpirationDays
DOMAINDomain registration not expiring soon (WHOIS)url (domain), domainExpirationDays
HEARTBEATYour job calls a URL regularlyinterval, gracePeriod — the ping URL is returned in url

Monitor statuses

STARTEDCreated or resumed, waiting for the first result
UPLast check passed
LOOKS_DOWNCheck failed but not yet confirmed (see confirmationChecks) — no alert yet
DOWNConfirmed down — incident open, alerts sent
PAUSEDMonitoring paused
MAINTENANCEInside a maintenance window — not checked, no alerts

Other values

keywordTypeALERT_EXISTS (alert when found) · ALERT_NOT_EXISTS (alert when missing)
keywordCaseTypeCaseSensitive · CaseInsensitive
httpMethodTypeGET HEAD POST PUT PATCH DELETE OPTIONS
postValueTypeRAW_JSON · KEY_VALUE (form-encoded)
assertion sourcebody_json (selector like $.data[0].ok) · header (selector = header name) · status_code · body_text
assertion comparisonequals not_equals contains not_contains greater_than less_than exists not_exists matches_regex
maintenance intervalonce daily weekly monthly; days = 1–7 (Mon–Sun) for weekly, 1–31 or -1 (last day) for monthly
status page sortcustom az za status_down status_up
integration typeEmail Telegram Slack Discord MSTeams GoogleChat Mattermost Pushover PushBullet Pagerduty Zapier Webhook
incident statusOPEN · RESOLVED

Heartbeat URLs

A HEARTBEAT monitor's url is its ping URL. Call it (any method, no API key) from your job on every run. If no call arrives within interval + gracePeriod, the monitor goes DOWN.

curl -fsS https://host/api/heartbeat/<token>                       # Linux cron
Invoke-WebRequest https://host/api/heartbeat/<token>              # Windows
/tool fetch url="https://host/api/heartbeat/<token>" keep-result=no  # MikroTik

What each check stores

Typedetails fields
HTTP / KEYWORD / APIrequest, ip, status, statusText, httpVersion, timing {dns, connect, tls, firstByte, total}, headers, sizeBytes, contentType, redirects [{url, status, ms}], finalUrl, tls {protocol, cipher}, certificate {subject, issuer, validFrom, validTo, daysLeft, altNames, serial, fingerprint256}, keyword {found, position, context}, assertions {results [{…, actual, passed}]}, bodySnippet (on failure), error
PINGhost, ip, ttl, roundTripMs, output
PORThost, port, ip, connectMs, banner
UDPhost, port, sentBytes, response {from, bytes, text, hex}
DNShost, recordType, resolver, resolverServers, records [{value, ttl}], expected, missing
SSLhost, port, ip, protocol, cipher, trusted, trustError, certificate {…}, chain [{subject, validTo}]
DOMAINdomain, whoisServer, registrar, created, updated, expires, daysLeft, nameServers, statuses, raw
HEARTBEATexpectedEverySeconds, graceSeconds, lastBeat, secondsSinceLastBeat, lastBeatFrom {ip, method, userAgent, message}

Heartbeats can carry a message: …/api/heartbeat/<token>?msg=backup+ok+12GB or POST a text / JSON {"message": "…"} body. It is stored with each check.

Webhook payload

Webhook and Zapier integrations receive a JSON POST for each event (down, up, ssl, storm, test):

{
  "event": "down",
  "time": "2026-11-01T14:30:00.000Z",
  "monitor": { "id": 800000001, "name": "Main website", "type": "http", "target": "https://example.com", "status": "down" },
  "reason": "HTTP 503 Service Unavailable",
  "durationSeconds": null,          // set on "up": how long it was down
  "incidentId": 42,
  "subject": "🔴 Monitor is DOWN: Main website",
  "message": "🔴 Monitor is DOWN: Main website\nTarget: …"
}

Mobile app API

Used by the upWatch Android app. Base URL /api/mobile/v1, JSON in and out, same error format as above. Every endpoint except /info, /auth/login and /auth/pair needs Authorization: Bearer uwa_…. App tokens belong to one user and workspace, can be signed out from the web under Account → Mobile app, and keep working whatever plan the workspace is on.

EndpointDescription
GET /infoPublic. { appName, logoUrl, version, apiVersion, signupUrl, webUrl, pushProvider: "builtin" | "fcm", push: { stream: true, unifiedPush: true, fcm } }. Use it to validate a server URL.
POST /auth/login{ email, password, deviceName } → { token, tokenId, user, workspace, workspaces }. Limited to 10 attempts per 5 minutes per IP.
POST /auth/pair{ code, deviceName }, the code from web Account → Connect Mobile App (format XXXX-XXXX, 10 minutes, single use; the QR encodes upwatch://pair?server=…&code=…). Same response as login.
POST /auth/logoutRevokes this token and unregisters its devices. 204.
GET /meUser, workspace (plan, status, trial/period end), limits, push provider and this sign-in's devices.
GET /devicesDevices registered with this token.
POST /devices{ installId, unifiedPushEndpoint?, fcmToken?, platform: "android", name, appVersion, locale, timezone, prefs? } → 201 { deviceId, transports, … }. installId is a random id the app generates once and keeps; re-registering with it updates the same device. unifiedPushEndpoint (optional) is the URL from the phone's UnifiedPush distributor; loopback and link-local addresses are refused. fcmToken (optional) is only used if the server has Firebase configured. The first device creates the workspace's upWatch app integration, which applies to every monitor.
PATCH /devices/{id}{ prefs, fcmToken, name, appVersion, locale, timezone }. prefs = { events: { down, up, ssl, domain, comments }, critical, quietHours: { start: "22:00", end: "07:00" } | null, mutedMonitors: { "<monitorId>": untilEpochMs | null } }; partial updates merge; a mute set to false removes it, null = forever.
DELETE /devices/{id}Unregister. 204.
POST /devices/{id}/testSends a test push to that device.
GET /stream?deviceId=Built-in live delivery, no Google needed. A Server-Sent Events stream. It sends event: hello on connect, then event: alert with id: <cursor> and the push payload as data, plus a : ping comment every 25 s. Reconnect with the Last-Event-ID header (or ?since=) to replay what was missed. With deviceId, every alert carries notify ("true"/"false", after this phone's preferences) and critical; alerts with notify = "false" still belong in the inbox. Auth: Authorization: Bearer, or ?token= for clients that can't set headers. Up to 3 streams per sign-in.
GET /events?since=&limit=Alert feed (same objects as the push data), oldest first. { data, cursor, hasMore }; pass the returned cursor as since next time. Events are recorded even when a push is muted or push is not configured; kept 30 days / 1,000 per workspace.
POST /incidents/{id}/ack{ note? }: adds "✅ Acknowledged by <name> from upWatch" to the incident. 201.

For monitors, stats, checks, incidents and maintenance the app calls the regular /api/v1 endpoints with its app token.

Push payload

The same payload is delivered over every transport the phone has:

  1. Live stream (GET /stream): built in, needs no setup, and works on phones without Google services.
  2. UnifiedPush: JSON POST to the phone's distributor endpoint (for example a self-hosted ntfy server), with Urgency: high for down alerts. A 404/410 response removes the endpoint.
  3. Firebase (optional): data-only FCM HTTP v1 with android.priority = HIGH, used only when an admin uploads a service account. UNREGISTERED tokens are removed.

A phone holding a live stream gets the alert there and isn't pushed twice. All values are strings. The server applies each device's preferences: event types, muted monitors, and quiet hours in the device's timezone. During quiet hours only down/storm get through, and only with critical enabled.

{
  "v": "1",
  "type": "down",                 // down | up | ssl | domain | comment | storm | test
  "eventId": "evt_1287",
  "workspaceId": "1001", "workspaceName": "Alphabridge Technologies",
  "monitorId": "42", "monitorName": "Mariah Camp RADIUS", "monitorType": "PORT", "target": "10.0.0.5:1812",
  "incidentId": "317",
  "reason": "Connection refused",
  "duration": "754",              // up: seconds it was down
  "days": "9",                    // ssl / domain: days left
  "count": "6",                   // storm: monitors down
  "time": "2026-11-02T01:40:12.000Z",
  "url": "https://status.example.com/app#/incident/…",
  "title": "🔴 Monitor is DOWN: Mariah Camp RADIUS",
  "body": "🔴 Monitor is DOWN: …",
  "critical": "false"             // true when this device opted in to critical down alerts
}

Upwatch API v1 · self-hosted