Monitors

Create monitor

Creates a new uptime monitor. type must be http, tcp, or ping; dns and ssl are defined in the wire format but rejected with 400 (issue #209 — see "Invalid request" below). Max 100 monitors per account. Duplicate URLs within the same account are rejected.

recipient_ids (issue #329) selects which recipients the new monitor is created with — see MonitorCreateInput. Omitted or null attaches the account's default recipients (recipients.is_default), unchanged from pre-#329 behavior. An explicit array (including []) attaches exactly that set instead: validated like PUT /monitors/{id}/recipients (positive ids, no duplicates — 400 before any write) and ownership-checked against the calling account (400 on a foreign or nonexistent id, before the monitor-limit and duplicate-URL checks, so a bad id is reported even for an account already at its cap).

recipients (issue #338) is recipient_ids's alternative form, letting each attached pair set its own down_delay_s at create time — see MonitorRecipients. Mutually exclusive with recipient_ids: specifying both is 400. recipients: null is 400 (unlike recipient_ids: null, which keeps its "attach defaults" meaning).

The dispatcher synchronously runs an immediate test check against the configured URL before returning, so the create response carries state.status of up or down instead of unknown. The check uses the same probe path as POST /api/v1/monitors/{id}/test. If the probe is unavailable, the kill switch is engaged, or the URL fails the dispatch-time SSRF guard (DNS rebind between validation and dispatch), the create still succeeds and state.status is unknown — the next scheduled tick will populate it.

Per-account scan heuristic: a sliding window (10 minutes by default; SCAN_WINDOW) tracks the (host, port) tuples of each newly-created monitor — and, identically, every PUT /api/v1/monitors/{monitorID} that changes url or type (issue #335; see that endpoint's description) — and branches on two signatures:

  • Port-fan (≥6 distinct ports, by default — SCAN_PORTFAN_THRESHOLD — on ≤2 hosts, by default — SCAN_PORTFAN_HOSTS): scanner-shaped → account suspended (403), all monitors purged, security alert fired. A tcp monitor's port — already checked against the TCP allowlist below — counts toward a separate, wider ceiling, SCAN_PORTFAN_TCP_THRESHOLD (default 12, issue #331 — unset tracks the greater of 12 and the effective SCAN_PORTFAN_THRESHOLD); http/ping targets always trip at the stricter SCAN_PORTFAN_THRESHOLD (6), even mixed with tcp targets on the same host.
  • Host-fan (≥8 distinct registrable domains, by default — SCAN_HOSTFAN_THRESHOLD — eTLD+1; IP literals count individually; subdomains of one site fold into a single domain): ambiguous — also matches an agent provisioning a real multi-service stack → rejected with 429 + Retry-After (seconds until the oldest entry leaves the window). Account is NOT suspended; retry after Retry-After seconds. The response always carries the actual limit and window enforced (X-RateLimit-Limit/X-RateLimit-Window headers, limit/ window_seconds body fields) — these defaults are examples, not hard-coded values a client should assume.

Subject to two independent per-account rate limits. A request consumes one token from each:

  • Global cap: 200 requests/minute on any authed /api/v1/* route (envelope: account rate limit exceeded; retry in <N>s).
  • Creation cap: 20 POST /api/v1/monitors per minute (envelope: monitor creation rate limit exceeded; retry in <N>s). Either rejection responds 429 with an integer Retry-After header.
post/api/v1/monitors

Request

  • Base URL: https://api.uptimemonitoring.com
  • URL: https://api.uptimemonitoring.com/api/v1/monitors
  • Auth: HTTP bearer

Request body

namestring
urlstring

Target URL. Format depends on type:

  • http — full http(s) URL.
  • tcp — host:port or tcp://host:port, port restricted to a fixed allowlist (issue #331): 21, 22, 25, 80, 110, 143, 443, 465, 587, 993, 995, 2222, 3000, 3306, 5000, 5432, 6379, 8000, 8080, 8443, 8888, or 27017. Every create, and every update whose body includes url or type, stores the canonical tcp://host:port form (lowercase, no path) regardless of which shape was sent — example.com:443, tcp://example.com:443, and tcp://EXAMPLE.com:443/ all store as tcp://example.com:443. An update that changes neither url nor type leaves the stored URL as it is. A bracketed IPv6 literal is required ([2001:db8::1]:443); an unbracketed one is rejected as ambiguous.
  • ping — a bare hostname or IP literal (e.g. api.example.com, 1.1.1.1) — no scheme, path, query, userinfo, or port.
  • dns / ssl — reserved formats (a bare hostname for dns) for a type that is defined but not yet serviced by the check engine; see the type field.
type'http' | 'tcp' | 'ping'

http, tcp, and ping are accepted on create or update. dns and ssl are defined in the wire format but rejected with 400 (issue #209): type must be one of: http, tcp, ping, dns, ssl (currently available: http, ping, tcp) for an unrecognized value, or type '<type>' is not available yet; the check engine currently supports: http, ping, tcp for one of those two.

interval_secinteger

Check interval in seconds. Default 60.

expected_statusstring

HTTP status range. Default: 200-399. Accepted but silently ignored for type: tcp or type: ping (neither has an HTTP response to evaluate it against).

body_matchstring

Optional string to match in the response body. HTTP only — rejected with 400 (body_match is not applicable to tcp monitors) when type is tcp. Accepted but silently ignored (a harmless no-op; wopher's ping check never reads it) when type is ping.

enabledboolean

Default true.

method'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'

HTTP request method (issue #364). Default GET. HTTP only — sending this field at all (including its default value) on a tcp or ping create/update is 400 (method is only applicable to http monitors). On update, omitting it leaves the stored method unchanged; an explicit null is 400 (method must not be null) — but only when null is the value the request body actually decodes to: exact-duplicate JSON keys (same spelling) resolve via encoding/json's last-value-wins before this check ever runs, so {"method":null,"method":"POST"} is accepted as POST; only a differently-cased duplicate (e.g. Method alongside method) is itself rejected, as more than one key for the same field. The same caveat applies to body/timeout_sec below, which share this rejection rule.

bodystring

Request body (issue #364). Default "" — a bodiless POST is allowed. Must be valid UTF-8 with no null bytes. HTTP only, same rejection rule as method. Cleared by sending "" on update.

timeout_secinteger

Request timeout in seconds (issue #364). Default 30. HTTP only, same rejection rule as method.

recipient_idsinteger[] nullable

Selects which recipients to attach at create time. Three cases:

  • Omitted or null — attach the account's default recipients (recipients.is_default). Identical to pre-#329 behavior.
  • [] — attach none.
  • A non-empty array — attach exactly those ids. Validated like PUT /monitors/{id}/recipients: every id must be positive and the array must not contain duplicates (400 before any write), and every id must be owned by the calling account (400, checked before the monitor-limit and duplicate-URL checks).

Mutually exclusive with recipients — both present is 400.

Example request

{
  "maintenance": {
    "start_time": "23:00",
    "end_time": "03:00",
    "timezone": "Europe/Lisbon"
  }
}

Response

Monitor created. state.status carries up or down from the immediate test check. unknown indicates a best-effort skip (probe unavailable, kill switch active, or SSRF rebind detected at dispatch time); the next scheduled tick populates it.

Soft-cap notice: when the post-insert per-account monitor count is in the 51..100 window, the response carries an additional notice object (code: soft_cap_reached). Creates at or below 50 produce a response with no notice field at all (omitempty). The 101st create returns 409 (see "Monitor limit reached").

last_check_skippedstring

Populated only on POST /monitors when the create-time initial check did not produce a usable result — either short-circuited before any probe ran, or the probe started but did not finish within the create-time budget, leaving the first check pending. The state row's last_check_at remains null in either case; clients distinguish "no data yet" from "skipped on purpose" via this field.

Vocabulary:

  • domain_rate_limited — the per-domain dispatch cap (DomainLimiter) consumed the slot; the next scheduled tick will retry once the bucket refills.
  • initial_check_timeout — the create-time probe did not return within the server's bounded wait; the monitor is registered normally and the next scheduled tick resolves it.
recipient_idsinteger[]

Attached recipient ids (issue #212). Always present, [] for none. On POST /monitors, reflects any account default recipients attached inside the create transaction — though a monitor born down does not notify them at creation; see Recipient.default.

Example response

{
  "monitor": {
    "name": "Production API",
    "url": "https://api.example.com/health",
    "interval_sec": 60,
    "expected_status": "200",
    "maintenance": {
      "start_time": "23:00",
      "end_time": "03:00",
      "timezone": "Europe/Lisbon"
    }
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.