Checks

Create or upsert a check

Creates a new check and returns 201.

Upsert: if slug is non-empty and a check with that slug already exists in the project, the existing check's config is updated and 200 is returned instead of 201.

Create-only (If-None-Match: *): send the request header If-None-Match: * to opt out of the upsert behaviour above (RFC 9110 §13.1.2 — apply only if the resource does not already exist). If a check with the given slug already exists, the request is rejected with 412 Precondition Failed and the existing check is left completely unmodified, instead of being silently adopted/updated. This is intended for IaC clients (e.g. a Terraform provider) where a slug collision must surface as an error rather than take over a monitor the client did not create. The header is opt-in: omitting it preserves the upsert behaviour above for existing callers (dashboard, MCP tools). Any other If-None-Match value (e.g. an ETag) is ignored.

post/api/v1/checks

Request

  • Base URL: https://app.lastping.dev
  • URL: https://app.lastping.dev/api/v1/checks
  • Auth: API key in header X-Api-Key

Headers

If-None-Matchstring

Set to * for create-only semantics: reject with 412 instead of upserting when slug already exists. Any other value is ignored.

Request body

namestring required

Human-readable name for the check: at most 200 characters (not bytes) and no control or invisible characters (a tab, newline, zero-width character or non-ASCII space is rejected with 400 INVALID_NAME; an ordinary space is fine). Bytes that are not valid UTF-8 are replaced with U+FFFD when the request is decoded, and accepted.

slugstring

Optional stable identifier. If provided and a check with this slug already exists in the project, the request performs an upsert (config update) and returns 200 instead of 201.

Input is trimmed and lowercased before validation and before the uniqueness lookup, so " Nightly-Backup " and "nightly-backup" are the same slug. Must then match ^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$ (3-50 chars, lowercase alphanumeric and hyphens, starting and ending alphanumeric); a 400 is returned otherwise. A UUID-shaped slug is rejected — the Terraform provider resolves an import ID by trying to read it as a UUID first, so a UUID-shaped slug could never be imported by slug and could collide with another monitor's real id.

An empty slug means "no slug" — it is exempt from the uniqueness check, is not validated, and multiple unslugged monitors are allowed in the same project.

monitor_type'heartbeat' | 'ci' | 'http'

Monitor mode. Defaults to heartbeat when omitted. Any other value is rejected with 400 (UNKNOWN_MONITOR_TYPE). Use http to have LastPing actively probe a URL instead of waiting for an inbound ping.

ci is a label, not a binding. A CI monitor is stored as a heartbeat row with a CI binding attached, so it is ci_provider that makes a monitor a CI monitor — monitor_type: ci on its own creates an ordinary heartbeat, and its ci_workflow/ci_branch are then rejected with 400 (FIELD_NOT_IN_SHAPE).

schedule_kind'simple' | 'cron' | 'on_demand'

simple uses a fixed period; cron uses a cron expression; on_demand means the check has no cadence at all — it is driven entirely by run events (start/success/fail/…), never arms an absence deadline, and silence between runs never alerts. Run-scoped detection (max_runtime_s, step_timeout_s) still applies once a run starts, and expect_every_s — the silence floor — is the only way to detect absence between runs. Required for heartbeat and ci monitor types.

Rejected with 400 (FIELD_NOT_IN_SHAPE) when monitor_type is http, together with period_s, cron_expr and tz: an http monitor's schedule is derived from probe_interval_s, so all four were previously accepted and discarded.

period_sinteger

Expected ping interval in seconds. Required when schedule_kind is simple.

Must be absent when schedule_kind is on_demand — supplying it alongside on_demand is rejected with 400 (ON_DEMAND_SCHEDULE_CONFLICT).

cron_exprstring

Cron expression (5-field). Required when schedule_kind is cron.

Must be absent when schedule_kind is on_demand — supplying it alongside on_demand is rejected with 400 (ON_DEMAND_SCHEDULE_CONFLICT).

tzstring

IANA timezone for cron evaluation. Defaults to UTC.

grace_sinteger

Grace period in seconds after a ping is due before the check is considered late. This is the silence tolerance and the first-run (monitor_from) deadline. It is also the fallback run-duration budget when max_runtime_s is not set.

max_runtime_sinteger nullable

Run-duration budget in seconds, measured from an unmatched /start ping. It replaces grace_s for the overrun deadline only — the silence rule (alert_after = due_at + grace_s) and the first-run seed (alert_after = monitor_from + grace_s) keep using grace_s. This is what makes "alert if silent for more than 10 minutes, but allow a 4-hour run" expressible.

Time a run spends blocked (waiting on a person) does not count toward this budget: the run's clocks pause at the blocked ping, and when the next ping or step ends the wait, the waited time is added to the run's budget and the step_timeout_s clock starts again from that moment. Each single wait is bounded by blocked_timeout_s, but the total a run can add this way has no cap: a run that keeps alternating waits shorter than blocked_timeout_s with progress never overruns. On a simple or cron monitor the added time never postpones a missed scheduled run: when the next run's silence deadline comes sooner, that deadline still pages.

Null or omitted falls back to grace_s, so a monitor that never sets it behaves exactly as before this field existed.

Rejected with 400 (MAX_RUNTIME_NOT_SUPPORTED) when monitor_type is http. An HTTP probe has no start/success pair — the prober mints a fresh run id for every probe — so no start is ever outstanding and the overrun rule can never fire. Use probe_timeout_s to bound a single probe.

step_timeout_sinteger nullable

Progress budget in seconds: how long an armed run may go without posting a /step before a stalled incident opens. The anchor is the LATER of the run's /start and its most recent /step, so a run that wedges before its first step is caught too.

Null or omitted disables stall detection entirely, which is how every monitor behaved before this field existed.

Stall detection needs your job to call /step: the Claude Code hook and the reporting prompt do not send steps, so leave step_timeout_s unset for them.

A step resets the stall clock ONLY — it never extends max_runtime_s, so an agent that reports progress forever still overruns.

Must be strictly less than the effective run budget, COALESCE(max_runtime_s, grace_s), or the request is rejected with 400 (STEP_TIMEOUT_EXCEEDS_BUDGET). The stall rule fires only inside that budget — past it the run is an overrun and overrun is the truer cause — so a step timeout at or above the budget leaves an empty window and the rule could never fire.

Rejected with 400 (STEP_TIMEOUT_NOT_SUPPORTED) when monitor_type is http. An HTTP probe never arms a run, so there is nothing for a step to report progress within. Use probe_timeout_s to bound a single probe.

expect_every_sinteger nullable

Silence floor: open a silence incident if no ping of any kind — success, start, fail, step — has arrived within this many seconds, regardless of the schedule. It is anchored on the monitor's last activity (last_ping_at), never on the schedule: it is a floor on silence, not a cadence.

Null or omitted turns it off, which is exactly how every monitor behaved before this field existed.

This is the only absence rule an on_demand monitor can have. That schedule kind arms no deadlines between runs by design, so without expect_every_s an on_demand monitor reads up indefinitely however long its agent stays dark.

It never fires mid-run: while a /start is outstanding the floor stands down entirely and the run clock owns detection (max_runtime_s, step_timeout_s). A blocked ping also suppresses it, bounded by blocked_timeout_s.

On simple/cron monitors it is a backstop: it joins the schedule's own deadline as whichever is sooner, so it can tighten detection under a long cadence but can never loosen it.

Accepted on every monitor_type and every schedule_kind — unlike max_runtime_s/step_timeout_s it has no run-scoped precondition that would make it a provable no-op on an HTTP monitor.

notify_min_run_sinteger nullable

Notification duration floor: a run shorter than this many seconds does not produce an INFO-CLASS notification (success, started, every-run, note). This is what stops a trivial, fast agent run — "what's 2+2" — from paging a destination routed to success exactly like a real, hours-long job would.

It never suppresses down, fail, recovery or blocked — those are alert-class and are never affected by this field, however short the run. A run that failed in two seconds is exactly what a user needs to hear about.

It never suppresses started (a run's duration does not exist yet the moment it begins), and it never suppresses an event whose duration could not be measured at all — an unknown duration always means "notify", never "suppress".

Null or omitted turns it off, which is exactly how every monitor behaved before this field existed.

Rejected with 400 (NOTIFY_MIN_RUN_NOT_SUPPORTED) when monitor_type is http. The floor only ever suppresses a notification once a run's duration is measured, and that measurement comes exclusively from a matched /start + /success pair — which an HTTP probe never produces (the prober mints a fresh run id per probe).

blocked_timeout_sinteger nullable

How long (in seconds) a check may sit blocked — reported via a blocked ping, meaning the run is waiting on a human — before a blocked incident opens.

Null or omitted does NOT mean "wait forever". It falls back to the default, which is 24 hours, after which the blocked incident opens regardless. There is no way to disable the blocked timeout entirely.

Accepted on every monitor_type, including http — unlike max_runtime_s/step_timeout_s it has no run-scoped precondition that would make it a provable no-op on an HTTP monitor.

failure_thresholdinteger

Number of consecutive explicit failures required before an incident opens — retry-before-alert. Applies to the fail cause ONLY: silence, overrun, never_started, runaway, stalled and blocked are time- or rate-based, so a consecutive-failure count is meaningless for them and never gates them. A run that ends on the model provider's API error (upstream) neither counts toward it nor resets it: those page on their own count of runs in a row, which is 3 or this threshold, whichever is higher.

Below the threshold nothing alerts and the status does not change, but the ping is still recorded, an outstanding run is still ended, and deadlines are still recomputed — the failed run did happen, only the alert is deferred. Any success resets the counter to zero; a cancel does not.

Defaults to 1 (open on the first failure) when omitted, which is the pre-existing behaviour. Applies to http monitors too: a failed probe is a real fail signal.

monitor_fromstring date-time nullable

RFC 3339 timestamp. Deadlines are not computed before this time, useful for checks that should start alerting only from a future date.

probe_urlstring uri

Absolute http or https URL for LastPing to probe. Required when monitor_type is http.

probe_method'GET' | 'HEAD' | 'POST'

HTTP method for the probe request. Defaults to GET when omitted. Valid values: GET, HEAD, POST.

probe_expected_bodystring

Substring that must appear in the response body for the probe to be considered successful. Leave empty to skip body matching.

probe_interval_sinteger

How often (in seconds) LastPing should probe the URL. Must be between 30 and 86400. Required when monitor_type is http.

probe_expected_statusinteger

HTTP status code the probe must receive to count as success. No default is applied on create: an omitted (or 0) value means "any 2xx response counts as success". Set it explicitly to require one exact status. On PATCH, an omitted field retains the stored value.

probe_timeout_sinteger

Probe HTTP request timeout in seconds. Must be between 1 and 30. Defaults to 10 when omitted.

probe_follow_redirectsboolean

Whether the probe should follow HTTP redirects. Defaults to false when omitted on create. On PATCH, an omitted field retains the stored value.

ci_provider'github' | 'gitlab' | 'jenkins'

CI provider to bind on create. When supplied (and valid), LastPing generates a webhook secret and binds it. The secret is returned ONCE in the 201 response (ci_secret) and is NEVER returned again — use the POST /api/v1/checks/{id}/ci/regenerate endpoint to rotate it.

Rejected with 400 (FIELD_NOT_IN_SHAPE) when monitor_type is http. An http monitor is never bound to CI; the request used to be accepted, create no binding, and report success.

Fixed at creation. On the slug upsert of a monitor that already has a binding, the stored provider is accepted as a no-op (the provider and secret stay) and a different one is rejected with 400 (CI_PROVIDER_IMMUTABLE); on one created without a binding it is rejected with 400 (FIELD_NOT_IN_SHAPE), because a binding cannot be added to an existing monitor.

ci_workflowstring

Optional CI workflow name to filter inbound webhooks by (provider-specific).

Requires ci_provider, and this is enforced: with no CI binding the filter is stored nowhere and matches nothing, so the request is rejected with 400 (FIELD_NOT_IN_SHAPE). It was previously accepted and discarded — a Terraform configuration setting ci_workflow without ci_provider applied clean and filtered nothing.

On the slug upsert (when slug names a check that already exists, this POST updates it and returns 200) the CI binding is the stored one: a CI-bound monitor accepts ci_workflow and ci_branch without ci_provider being repeated, and a monitor created without a binding still refuses them. A filter is written only when it is sent with a non-empty value; omitted or "" keeps the stored value, so an upsert can set a filter but never clear one. Clear a filter with PATCH /api/v1/checks/{id} and null.

ci_branchstring

Optional branch name to filter inbound webhooks by.

Requires ci_provider, and this is enforced — see ci_workflow. Rejected with 400 (FIELD_NOT_IN_SHAPE) when the monitor has no CI binding, and follows the same slug-upsert rule: set when sent, kept when omitted or empty, cleared only by PATCH.

runaway_ceilinginteger nullable

Maximum pings allowed in a rolling 1-hour window. Exceeding it opens a runaway-cause incident, which detects a looping agent or job pinging abnormally fast. Null or omitted disables the check.

tagsstring[]

Optional labels for namespace scoping and auditing. Max 20 tags; each tag max 50 characters. Duplicate tags are silently deduplicated. The agent: prefix is a convention for AI-provisioned monitors.

agent_idstring

Optionally attaches this monitor to an agent at creation time, by either the agent's id or its slug. Omitted or empty means "no attachment" — it never creates an agent implicitly.

An unknown or foreign agent reference is rejected with 400 (UNKNOWN_AGENT) — this is what keeps Terraform (or any IaC client) authoritative over the agents it owns: naming an agent that does not exist can never conjure one into being. Create the agent first with POST /api/v1/agents.

source_kindstring

Discovery source identity, part 1 of 2. The scanner that found this thing: crontab, github-actions, k8s-cronjob, systemd-timer. Lowercase, dash-separated — it is compared byte-for-byte, so GitHub Actions and github-actions would be two different identities for one scanner.

Together with source_ref and the project, this is the reconcile key that makes a discovery scan safe to re-run: the second run diffs against what already exists instead of creating every monitor a second time.

Omit both fields for a hand-made monitor. Monitors with no source are never reconciled, so discovery can never adopt, modify or delete something a human created.

source_refstring

Discovery source identity, part 2 of 2. A stable path within source_kind — .github/workflows/nightly.yml#build, /etc/cron.d/backup#0 3 * * *. Must be valid UTF-8 with no control characters.

Must be sent together with source_kind — exactly one of the two is rejected with 400 (SOURCE_INCOMPLETE). Half an identity reconciles against nothing.

A pair this project already monitors is rejected with 409 (SOURCE_ALREADY_MONITORED). That rejection is the feature: it is what stops a re-run of discovery from duplicating every monitor.

On a slug upsert these two fields are read-only. POST /api/v1/checks with a slug that already exists never rewrites a monitor's source — that would let a slug collision silently adopt a hand-made monitor into discovery — and returns 409 (SOURCE_IMMUTABLE_ON_UPSERT) if the body offers a different pair. Change a source deliberately with PATCH /api/v1/checks/{id}.

trace_content'redacted' | 'dropped'

What this monitor's traces keep of prompt, command and tool content. dropped (the default) removes the content attributes (prompts, completions, commands, tool arguments, results and definitions, retrieved documents, working directories) of every recognised vocabulary entirely; lengths such as user_prompt_length, token counts and cost are kept. Error text (error, error.message, exception.message, exception.stacktrace and the span's status message) is kept at dropped, redacted and then cut to 512 characters, and a cut marks the span or log event truncated. redacted opts in to storing that content, with every secret-shaped value replaced by [REDACTED:<class>] at ingest, before anything is queued or stored: API keys (LastPing, Anthropic, OpenAI, GitHub, GitLab, npm, AWS access key ids, Slack, Stripe, Google, Hugging Face, Perplexity, Fireworks, Supabase, New Relic, Airtable, Terraform Cloud, Firebase), HashiCorp Vault tokens, age secret keys, Discord bot tokens, Slack, Discord and Teams webhook URLs, Authorization header values, JWTs, private key blocks, password=/token=/api_key= style assignments (subscripted ones such as os.environ["API_KEY"] = ... and Dockerfile ENV/ARG lines included), SQL PASSWORD '...' and IDENTIFIED BY clauses, the Redis AUTH command, .netrc passwords, command-line password flags and the argument after them (any secret-named --flag, -p after a login subcommand, redis-cli -a), cookies, credentials in URLs, the value of any attribute or JSON field whose name is secret-named (password, token, api_key, secret, the same word in several other languages, an Authorization or X-Api-Key header and the like; Idempotency-Key and request, correlation and trace ids are not secrets), including JSON carried inside another JSON string such as tool-call arguments, and long high-entropy tokens. A bytes attribute that is valid UTF-8 is scanned and stored as its text. A span that had a value redacted carries lastping.redacted_values. A request whose redaction work exceeds a fixed budget stores [REDACTED:over_budget] for the values it could not scan, never the values themselves. In both modes every remaining value is redacted the same way. There is no setting that stores content unredacted. A change reaches ingest within 60 seconds: ingest caches each monitor's setting for up to a minute, so an export sent just after the change may still be handled the old way.

Omitted means dropped on create. On a slug upsert an omitted value leaves the stored setting alone, so an upsert can never undo a choice made earlier; any other value is rejected with 400 (INVALID_TRACE_CONTENT).

Example request

{
  "name": "Daily backup job",
  "slug": "daily-backup",
  "monitor_type": "heartbeat",
  "schedule_kind": "simple",
  "period_s": 86400,
  "cron_expr": "0 3 * * *",
  "tz": "Europe/London",
  "grace_s": 600,
  "max_runtime_s": 14400,
  "step_timeout_s": 300,
  "expect_every_s": 900,
  "notify_min_run_s": 300,
  "blocked_timeout_s": 86400,
  "failure_threshold": 3,
  "monitor_from": "2026-01-01T00:00:00Z",
  "probe_url": "https://example.com/healthz",
  "probe_method": "GET",
  "probe_expected_body": "\"status\":\"ok\"",
  "probe_interval_s": 60,
  "probe_expected_status": 200,
  "probe_timeout_s": 10,
  "ci_provider": "github",
  "ci_workflow": "ci.yml",
  "ci_branch": "main",
  "runaway_ceiling": 20,
  "tags": [
    "agent:claude",
    "env:prod"
  ],
  "agent_id": "nightly-etl-bot",
  "source_kind": "github-actions",
  "source_ref": ".github/workflows/nightly.yml#build",
  "trace_content": "dropped"
}

Response

Existing check (matched by slug) updated; config replaced.

idstring uuid required

Unique check identifier.

namestring required

Human-readable name.

slugstring required

Stable project-scoped identifier (may be empty string if not set).

schedule_kind'simple' | 'cron' | 'on_demand' required

on_demand means the check has no cadence: it never arms an absence deadline, so silence between runs never alerts. Run-scoped detection (max_runtime_s, step_timeout_s) still applies once a run starts, and expect_every_s — the silence floor — is the only way to detect absence between runs.

period_sinteger required

Period in seconds (only meaningful for schedule_kind=simple).

cron_exprstring

Cron expression (only present when schedule_kind=cron).

tzstring required

IANA timezone.

grace_sinteger required

Grace period in seconds — the silence tolerance and the first-run deadline, and the overrun budget when max_runtime_s is unset.

max_runtime_sinteger nullable

Run-duration budget in seconds for the overrun deadline only. Omitted from the response when unset, in which case the overrun rule falls back to grace_s. Never set on http monitors.

step_timeout_sinteger nullable

Progress budget in seconds for the stall rule. Omitted from the response when unset, in which case stall detection is disabled. Never set on http monitors.

expect_every_sinteger nullable

Silence floor in seconds. Omitted from the response when unset, in which case absence is detected only by the schedule — and, for an on_demand monitor, not at all between runs.

notify_min_run_sinteger nullable

Notification duration floor in seconds. Omitted from the response when unset, in which case every info-class notification (success, started, every-run, note) fires regardless of how short the run was. Never set on http monitors. Never suppresses down, fail, recovery or blocked.

blocked_timeout_sinteger nullable

How long (in seconds) the check may sit blocked before a blocked incident opens. Omitted from the response when unset — that does NOT mean the check waits forever, it means the 24-hour default applies.

failure_thresholdinteger required

Consecutive explicit failures required before an incident opens (fail cause only). Always present; 1 means "open on the first failure", the default.

monitor_fromstring date-time nullable

RFC 3339 timestamp from which deadlines are computed.

status'new' | 'up' | 'late' | 'down' required

Current check status derived from ping history.

last_ping_atstring date-time nullable

RFC 3339 timestamp of the most recent ping received.

due_atstring date-time nullable

RFC 3339 timestamp when the next ping is expected.

alert_afterstring date-time nullable

RFC 3339 timestamp after which an alert is sent if no ping arrives.

pausedboolean required

When true the check does not alert regardless of ping status.

maintenance_untilstring date-time nullable

RFC 3339 timestamp until which the check is snoozed. Alerts are suppressed while this is in the future; null or absent when the check is not snoozed.

created_atstring date-time required

RFC 3339 UTC timestamp when the check was created.

ping_urlstring uri required

The URL your service should HTTP-GET to record a successful ping.

monitor_type'heartbeat' | 'ci' | 'http'

Monitor mode. Omitted on responses when the value is the default heartbeat.

probe_urlstring uri

URL being probed (HTTP monitors only; omitted otherwise).

probe_method'GET' | 'HEAD' | 'POST'

HTTP method used for the probe (HTTP monitors only; omitted otherwise).

probe_expected_bodystring

Expected response body substring (HTTP monitors only; omitted otherwise).

probe_interval_sinteger

Probe interval in seconds (HTTP monitors only; omitted otherwise).

probe_expected_statusinteger

Expected HTTP status code (HTTP monitors only; omitted otherwise).

probe_timeout_sinteger

Probe timeout in seconds (HTTP monitors only; omitted otherwise).

probe_follow_redirectsboolean

Whether the probe follows redirects (HTTP monitors only; omitted otherwise).

next_probe_atstring date-time nullable

RFC 3339 UTC timestamp when the next probe is scheduled (HTTP monitors only; omitted otherwise).

ci_provider'github' | 'gitlab' | 'jenkins'

CI provider bound to this check (omitted when no CI binding is set).

ci_configuredboolean

True when a CI binding is set on this check (omitted when false).

ci_webhook_urlstring uri

Webhook URL for this CI check (omitted when no CI binding is set). POST this URL from your CI provider with the ci_secret as the HMAC secret.

ci_secretstring

WRITE-ONCE — returned ONLY in the 201 create response and the POST /api/v1/checks/{id}/ci/regenerate response. NEVER returned on GET or list. Copy it immediately; it cannot be retrieved again (use regenerate to rotate).

ci_workflowstring

Optional workflow/job name filter narrowing which CI runs this check watches (omitted when unset, which watches every workflow).

ci_branchstring

Optional branch filter narrowing which CI runs this check watches (omitted when unset, which watches every branch).

runaway_ceilinginteger nullable

Maximum pings allowed in a rolling 1-hour window. Exceeding it opens a runaway-cause incident, which detects a looping agent or job pinging abnormally fast. Omitted from the response when disabled for this monitor.

tagsstring[] required

Labels attached to this monitor. Always returned as an array (never null); empty array when no tags are set.

agent_idstring uuid nullable

Id of the agent that owns this monitor. Omitted/null when the monitor is unattached — including right after its owning agent was deleted: DELETE /api/v1/agents/{id} unowns the monitors it owned, it does not delete them.

source_kindstring

The scanner that discovered this monitor (crontab, github-actions, k8s-cronjob, systemd-timer). Omitted when a human created the monitor, which is the state of every monitor that predates discovery — and such a monitor is never reconciled, so a scan can neither adopt nor delete it.

source_refstring

The stable path within source_kind that identifies exactly what was found. Together with source_kind and the project it is the discovery reconcile key.

source_kind and source_ref are always either both present or both omitted: a half-set identity is never stored.

trace_content'redacted' | 'dropped' required

What this monitor's traces keep of prompt, command and tool content. dropped (the default) removes the content attributes (prompts, completions, commands, tool arguments, results and definitions, retrieved documents, working directories) of every recognised vocabulary entirely; lengths such as user_prompt_length, token counts and cost are kept. Error text (error, error.message, exception.message, exception.stacktrace and the span's status message) is kept at dropped, redacted and then cut to 512 characters, and a cut marks the span or log event truncated. redacted opts in to storing that content, with every secret-shaped value replaced by [REDACTED:<class>] at ingest, before anything is queued or stored: API keys (LastPing, Anthropic, OpenAI, GitHub, GitLab, npm, AWS access key ids, Slack, Stripe, Google, Hugging Face, Perplexity, Fireworks, Supabase, New Relic, Airtable, Terraform Cloud, Firebase), HashiCorp Vault tokens, age secret keys, Discord bot tokens, Slack, Discord and Teams webhook URLs, Authorization header values, JWTs, private key blocks, password=/token=/api_key= style assignments (subscripted ones such as os.environ["API_KEY"] = ... and Dockerfile ENV/ARG lines included), SQL PASSWORD '...' and IDENTIFIED BY clauses, the Redis AUTH command, .netrc passwords, command-line password flags and the argument after them (any secret-named --flag, -p after a login subcommand, redis-cli -a), cookies, credentials in URLs, the value of any attribute or JSON field whose name is secret-named (password, token, api_key, secret, the same word in several other languages, an Authorization or X-Api-Key header and the like; Idempotency-Key and request, correlation and trace ids are not secrets), including JSON carried inside another JSON string such as tool-call arguments, and long high-entropy tokens. A bytes attribute that is valid UTF-8 is scanned and stored as its text. A span that had a value redacted carries lastping.redacted_values. A request whose redaction work exceeds a fixed budget stores [REDACTED:over_budget] for the values it could not scan, never the values themselves. In both modes every remaining value is redacted the same way. There is no setting that stores content unredacted. A change reaches ingest within 60 seconds: ingest caches each monitor's setting for up to a minute, so an export sent just after the change may still be handled the old way.

Always present.

kind'cron' | 'ci' | 'agent' | 'http' required

The console's four-way grouping of this monitor, derived server-side so every surface groups it the same way. agent wins over the rest: an agent's heartbeat is the agent's. A CI monitor is one with a ci_provider, not one with monitor_type: "ci" — nothing in the product writes that value.

uptime_90dnumber double nullable required

Uptime over the last 90 days, or since the monitor was created if that is more recent, as a percentage with two decimals. For an HTTP monitor this is the share of probes that succeeded; for every other type it is derived from incident downtime over the window. Null when an HTTP monitor has no probe history yet, and null for any other monitor that has never been pinged. A monitor created three days ago reports on those three days, not on 90 days of assumed success, so the window varies per monitor and the console says so beside the figure.

latency_msinteger nullable required

The most recent probe's latency. HTTP monitors only; null for every other type and for an HTTP monitor with no probe yet.

routes_countinteger required

How many distinct alert destinations would hear about this monitor FAILING: the destinations routed on down, fail, blocked or every-run. Distinct destinations, not route rows, so one destination routed on both down and fail is one place the alerts go. every-run counts because it fires for every completed run, a failed one included. A route on recovery, success, started or note is not counted: routes are resolved per event with no fallback, and none of those four ever fires for a failure, so a monitor routed only on them has nowhere for its bad news to go. 0 means failures would be silent, which the console flags as "Not routed" and Home counts in overview.alerts.monitors_unrouted.

recent_outcomesstring[] required

This monitor's last 20 run outcomes, newest first, for the console's run strip. Always an array, empty when the monitor has not completed a run yet. Only terminal outcomes appear: a start says a run began and is not one.

hook_versioninteger nullable required

Version of the Claude Code hook script last seen on a start ping; null when no hook that reports a version has sent a start. Older than hook_version_current means re-run set-up.

hook_version_currentinteger required

The hook script version set-up installs today. Compare it with hook_version.

hook_seen_atstring date-time nullable required

When hook_version was last recorded, from a start ping. Refreshed at most once an hour while the version is unchanged. Null when no hook that reports a version has sent a start.

claude_code_seen_atstring date-time nullable required

Latest time this monitor received a Claude Code run (a hook start that reported a version, or a trace whose source is Claude Code). With hook_version null and a recent value here, the machine runs an older hook or none.

Example response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Daily backup job",
  "slug": "daily-backup",
  "schedule_kind": "simple",
  "period_s": 86400,
  "cron_expr": "0 3 * * *",
  "tz": "UTC",
  "grace_s": 600,
  "max_runtime_s": 14400,
  "step_timeout_s": 300,
  "expect_every_s": 900,
  "notify_min_run_s": 300,
  "blocked_timeout_s": 86400,
  "failure_threshold": 3,
  "monitor_from": "2026-01-01T00:00:00Z",
  "status": "up",
  "last_ping_at": "2026-07-10T03:00:05Z",
  "due_at": "2026-07-11T03:00:00Z",
  "alert_after": "2026-07-11T03:10:00Z",
  "maintenance_until": "2026-07-11T09:00:00Z",
  "created_at": "2026-07-01T12:00:00Z",
  "ping_url": "https://ingest.lastping.dev/550e8400-e29b-41d4-a716-446655440000",
  "monitor_type": "http",
  "probe_url": "https://example.com/healthz",
  "probe_method": "GET",
  "probe_expected_body": "\"status\":\"ok\"",
  "probe_interval_s": 60,
  "probe_expected_status": 200,
  "probe_timeout_s": 10,
  "next_probe_at": "2026-07-13T03:01:00Z",
  "ci_provider": "github",
  "ci_configured": true,
  "ci_webhook_url": "https://ingest.lastping.dev/ci/github/550e8400-e29b-41d4-a716-446655440000",
  "ci_secret": "a3f8c2d1e4b7a9f0c3d2e1b4a7f8c0d3e2b1a4f7",
  "ci_workflow": "ci.yml",
  "ci_branch": "main",
  "ci_ignored": {
    "count": 12,
    "last_at": "2026-10-08T14:02:11Z",
    "last_workflow": "CI",
    "last_branch": "feature/login",
    "last_matched_at": "2026-10-01T09:00:00Z",
    "filters_changed_at": "2026-09-20T12:00:00Z",
    "same_workflow_count": 3,
    "same_workflow_last_at": "2026-10-08T11:40:00Z",
    "same_workflow_last_branch": "release/2.4",
    "new_workflow": "Deploy prod",
    "new_workflow_first_at": "2026-10-06T09:12:00Z",
    "new_workflow_count": 2
  },
  "runaway_ceiling": 20,
  "tags": [
    "agent:claude",
    "env:prod"
  ],
  "agent_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "source_kind": "github-actions",
  "source_ref": ".github/workflows/nightly.yml#build",
  "trace_content": "dropped",
  "kind": "cron",
  "uptime_90d": 99.67,
  "latency_ms": 142,
  "routes_count": 2,
  "recent_outcomes": [
    "succeeded",
    "succeeded",
    "failed"
  ],
  "hook_version": 2,
  "hook_version_current": 2
}

Changes

No recorded changes to this operation across all 2 revisions of this API.