---
title: "Reconcile a discovery scan into monitors"
method: POST
path: "/api/v1/discovery/reconcile"
tags: ["Discovery"]
---

# Reconcile a discovery scan into monitors

`POST /api/v1/discovery/reconcile`

## Request

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

Takes everything one scan of a repo or a host found and reconciles it
against what this project already monitors: new sources become
monitors, already-monitored sources are reported untouched, and
monitors whose source has vanished are reported and never deleted.

**This is what makes discovery re-runnable.** A scan you can only run
once is a setup wizard. A scan that is safe to re-run — nightly, on
every CI build, on every deploy — is drift detection: *"you added a
scheduled workflow last week and nothing is watching it."* Point it at
the same repo every night and the answer converges.

The project comes from the API key, like every other endpoint here.
There is no project id in the path.

### The three rules

- A source that is **new** becomes a monitor and is returned under
  `created`.
- A source that is **already monitored** is returned under `existing`
  and is **not modified** — not its name, not its schedule, not its
  thresholds. Whatever a user tuned by hand survives every future
  scan.
- A monitor whose source is **absent from the payload** is returned
  under `orphaned` and is **never deleted**. Reporting it is the whole
  point; deleting it would mean a scanner bug or a wrong branch could
  tear down production monitoring.

Monitors created by hand carry no source identity. They are invisible
to reconcile in both directions: a scan can neither adopt one nor
report one as orphaned.

### What a created monitor looks like

- `schedule_cron` present → a `cron` monitor on that expression, in
  the source's `tz` (default `UTC`), so LastPing arms a deadline from
  the same expression the job runs on. `tz` is **required** for
  `crontab` and `systemd-timer` sources that carry a `schedule_cron`:
  they fire in host-local time, and a UTC-assumed deadline on a host
  that is not on UTC is a false incident every day. Sending `"UTC"`
  explicitly satisfies it; omitting it is a 400.
- `schedule_cron` absent → an `on_demand` monitor. A push-triggered
  workflow has no cadence to be late against.
- `expect_every_s` (the silence floor) is seeded from the **longest**
  gap between consecutive fire times of `schedule_cron`, times two.
  The longest gap, because cron intervals are routinely irregular —
  `0 9 * * 1-5` alternates 24-hour gaps with a 72-hour one, and a
  floor set from the short gap would page every weekend. An explicit
  `suggested_expect_every_s` overrides it.
- `grace_s` is 300. A scanner has no way to know how late a job is
  allowed to be; that is a judgement about the business, not about the
  repository. PATCH it once and reconcile will never touch it again.
- No slug, and `monitor_from` is unset — so a freshly created monitor
  is not armed until its first ping, rather than paging minutes after
  every scan for jobs nobody has wired a ping into yet.

### All or nothing

The write half runs in one transaction. Either every new monitor
exists or none does — a half-created fleet would leave the caller
unable to tell which half landed.

## Request body

- ReconcileRequest: Everything one scan found. The list is complete by definition: any monitor in this project carrying a source identity that is NOT in this list is reported back as orphaned.
  - `sources` DiscoverySource[], required: The sources this scan found. Required — an omitted `sources` key is a 400 (`MISSING_SOURCES`), because it is far more likely to be a misspelled field than a genuine claim that the whole fleet is gone. An explicit empty array **is** such a claim and is accepted: it reports every discovered monitor as orphaned. Nothing is deleted either way.
    - `source_kind` string, required: The scanner that found this, as a lowercase machine identifier: `crontab`, `github-actions`, `k8s-cronjob`, `systemd-timer`. Required. Together with `source_ref` it is the key reconcile diffs against, and it is compared byte-for-byte — a kind that changes shape between scanner releases makes every monitor look new, and the next run duplicates the whole fleet.
    - `source_ref` string, required: The stable path within `source_kind` that identifies exactly what was found. Required, and **stable** is the operative word: it must name the same thing on the next scan, or reconcile cannot match it to the monitor it already created.
    - `name` string: Display name for a monitor created from this source. Defaults to `source_ref` when omitted, and truncated to 200 **characters** (not bytes, so multi-byte names are never cut mid-character). A name you send must be free of control and invisible characters (zero-width characters, non-ASCII spaces, line separators): a newline picked up from a crontab comment is rejected with 400 (`INVALID_NAME`) rather than stored. Bytes that are not valid UTF-8 are replaced with U+FFFD when the request is decoded, and accepted. The `source_ref` default is cleaned instead of rejected: non-ASCII spaces become ordinary spaces, other such characters are dropped, and a ref with nothing printable left is named `Discovered job`. Ignored for a source that is already monitored: reconcile never modifies an existing monitor, including its name.
    - `schedule_cron` string: The 5-field cron expression this job actually runs on, read out of the source (a crontab line, a workflow `schedule:` block, a CronJob spec). Evaluated in `tz`. When present the monitor is created as a `cron` monitor, so LastPing arms a deadline from the same expression the job runs on, and `expect_every_s` is seeded from the interval between fire times (see the reconcile endpoint). When absent the monitor is created `on_demand`: a workflow that triggers on push has no cadence to be late against.
    - `tz` string: The IANA timezone `schedule_cron` fires in. Deliberately declares NO `default`, even though the server defaults it to `UTC` outside the required case. A generated client materializes schema defaults, so declaring one here would fill `"UTC"` into every crontab source automatically — satisfying the conditional requirement below with exactly the reflexive answer it exists to prevent, and producing a monitor whose deadline is armed against the wrong wall clock on any host that is not UTC. That is the one failure this endpoint cannot detect from the server side: a stated `"UTC"` from an informed scanner and a stated `"UTC"` from a client filling in a default are indistinguishable on the wire. **Required when `source_kind` is `crontab` or `systemd-timer` and `schedule_cron` is present** — omitting it is a 400 (`TIMEZONE_REQUIRED`), not a defaulted UTC. Those two scanners fire in host-local time, and assuming UTC is a false incident every single day rather than a cosmetic slip: a host at UTC+4 running `0 3 * * *` pings at 23:00 UTC while a monitor storing `tz: UTC` computes its deadline as 03:00 UTC plus grace, roughly twenty hours before the job is next due. The derived silence floor is looser than the cron deadline, so it does not mask it either. The requirement is that the zone be **stated**, never that it differ from UTC: a host genuinely set to UTC sends `"UTC"` and is accepted. Optional everywhere else, defaulting to `UTC`. That default is correct for `github-actions` and `k8s-cronjob` — both evaluate cron in UTC — and a source with no `schedule_cron` becomes an `on_demand` monitor, where the zone is inert. Validated even for a source with no `schedule_cron`, so a bad zone is a 400 (`INVALID_TIMEZONE`) now rather than a 500 the first time somebody PATCHes a schedule onto the monitor.
    - `suggested_expect_every_s` integer: An explicit silence floor for a monitor created from this source, in seconds. Takes precedence over the value derived from `schedule_cron` — an explicit statement beats an inference — and is the only way to give a floor to a source with no cron at all.

## Response `200`

The reconcile diff. 200 rather than 201 even when monitors were
created: the response is a report, and a run that creates nothing
is the success case, not an empty result.

- ReconcileResult: The diff between what a scan found and what this project already monitors. All three arrays are always present and never null, including when empty.
  - `created` Check[], required: Monitors that did not exist before this call and do now. Empty on every run after the first, which is the steady state this endpoint is designed around.
    - `id` string, uuid, required: Unique check identifier.
    - `name` string, required: Human-readable name.
    - `slug` string, 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_s` integer, required: Period in seconds (only meaningful for schedule_kind=simple).
    - `cron_expr` string: Cron expression (only present when schedule_kind=cron).
    - `tz` string, required: IANA timezone.
    - `grace_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_threshold` integer, required: Consecutive explicit failures required before an incident opens (`fail` cause only). Always present; `1` means "open on the first failure", the default.
    - `monitor_from` string, 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_at` string, date-time, nullable: RFC 3339 timestamp of the most recent ping received.
    - `due_at` string, date-time, nullable: RFC 3339 timestamp when the next ping is expected.
    - `alert_after` string, date-time, nullable: RFC 3339 timestamp after which an alert is sent if no ping arrives.
    - `paused` boolean, required: When true the check does not alert regardless of ping status.
    - `maintenance_until` string, 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_at` string, date-time, required: RFC 3339 UTC timestamp when the check was created.
    - `ping_url` string, 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_url` string, 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_body` string: Expected response body substring (HTTP monitors only; omitted otherwise).
    - `probe_interval_s` integer: Probe interval in seconds (HTTP monitors only; omitted otherwise).
    - `probe_expected_status` integer: Expected HTTP status code (HTTP monitors only; omitted otherwise).
    - `probe_timeout_s` integer: Probe timeout in seconds (HTTP monitors only; omitted otherwise).
    - `probe_follow_redirects` boolean: Whether the probe follows redirects (HTTP monitors only; omitted otherwise).
    - `next_probe_at` string, 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_configured` boolean: True when a CI binding is set on this check (omitted when false).
    - `ci_webhook_url` string, 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_secret` string: **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_workflow` string: Optional workflow/job name filter narrowing which CI runs this check watches (omitted when unset, which watches every workflow).
    - `ci_branch` string: Optional branch filter narrowing which CI runs this check watches (omitted when unset, which watches every branch).
    - `ci_ignored` object: Finished CI runs (succeeded or failed) that `ci_workflow` or `ci_branch` filtered out since the last finished run that matched them, or since the filters last changed. Omitted when none were. A count that keeps growing with no recent match usually means a filter no longer matches anything, for example after a workflow rename or a default-branch change. A matching finished run and a change to either filter both reset it. A repository webhook sends the runs of every workflow, so with `ci_workflow` set, `count` includes other workflows' runs, which the filter is meant to drop; `same_workflow_count` counts only the runs of `ci_workflow` (or of any workflow when it is not set) that `ci_branch` dropped. `new_workflow` names the workflow that first appeared among the ignored runs since the last match and has run most often since, which is how a workflow renamed after it matched shows up (GitHub and Jenkins monitors; always empty on GitLab, which names an unnamed pipeline after its branch). `last_workflow`, `last_branch`, `same_workflow_last_branch` and `new_workflow` are copied from the CI provider's payload: treat them as untrusted text.
      - `count` integer, required: Number of finished runs the filters ignored.
      - `last_at` string, date-time, required: RFC 3339 UTC time the most recent ignored run arrived.
      - `last_workflow` string, required: Workflow name of the most recent ignored run, as the CI provider sent it.
      - `last_branch` string, required: Branch of the most recent ignored run, as the CI provider sent it.
      - `last_matched_at` string, date-time, nullable, required: RFC 3339 UTC time of the most recent finished run that matched the filters, or null when none has been recorded.
      - `filters_changed_at` string, date-time, nullable, required: RFC 3339 UTC time `ci_workflow` or `ci_branch` last changed, or null when that is not known.
      - `same_workflow_count` integer, required: Number of the ignored finished runs that belong to `ci_workflow` (every ignored run when no workflow filter is set): the runs the branch filter alone dropped. 0 when every ignored run was another workflow's.
      - `same_workflow_last_at` string, date-time, nullable, required: RFC 3339 UTC time the most recent of those runs arrived, or null when `same_workflow_count` is 0.
      - `same_workflow_last_branch` string, required: Branch of the most recent of those runs, as the CI provider sent it; an empty string when `same_workflow_count` is 0.
      - `new_workflow` string, required: Name of the workflow, other than `ci_workflow`, that first appeared after the last matching run and has the most ignored runs since (ties to the one that appeared first), as the CI provider sent it; an empty string when none did. A name first seen in the monitor's first 7 days of recording workflow names is never new. Always empty on GitLab monitors.
      - `new_workflow_first_at` string, date-time, nullable, required: RFC 3339 UTC time `new_workflow` first appeared, or null when `new_workflow_count` is 0.
      - `new_workflow_count` integer, required: Number of the ignored finished runs that carried `new_workflow`; 0 when no new workflow appeared.
    - `runaway_ceiling` integer, 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.
    - `tags` string[], required: Labels attached to this monitor. Always returned as an array (never null); empty array when no tags are set.
    - `agent_id` string, 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_kind` string: 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_ref` string: 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_90d` number, 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_ms` integer, 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_count` integer, 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_outcomes` string[], 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_version` integer, 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_current` integer, required: The hook script version set-up installs today. Compare it with hook_version.
    - `hook_seen_at` string, 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_at` string, 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.
  - `existing` Check[], required: Sources that were already monitored, returned exactly as stored. These rows were not written to at all. Reconcile does not refresh a name, a schedule or a threshold from the payload: a user who hand-tuned `expect_every_s` and watched a scan silently revert it would stop letting the scan run unattended, and a scan nobody runs unattended cannot detect drift.
    - `id` string, uuid, required: Unique check identifier.
    - `name` string, required: Human-readable name.
    - `slug` string, 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_s` integer, required: Period in seconds (only meaningful for schedule_kind=simple).
    - `cron_expr` string: Cron expression (only present when schedule_kind=cron).
    - `tz` string, required: IANA timezone.
    - `grace_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_threshold` integer, required: Consecutive explicit failures required before an incident opens (`fail` cause only). Always present; `1` means "open on the first failure", the default.
    - `monitor_from` string, 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_at` string, date-time, nullable: RFC 3339 timestamp of the most recent ping received.
    - `due_at` string, date-time, nullable: RFC 3339 timestamp when the next ping is expected.
    - `alert_after` string, date-time, nullable: RFC 3339 timestamp after which an alert is sent if no ping arrives.
    - `paused` boolean, required: When true the check does not alert regardless of ping status.
    - `maintenance_until` string, 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_at` string, date-time, required: RFC 3339 UTC timestamp when the check was created.
    - `ping_url` string, 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_url` string, 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_body` string: Expected response body substring (HTTP monitors only; omitted otherwise).
    - `probe_interval_s` integer: Probe interval in seconds (HTTP monitors only; omitted otherwise).
    - `probe_expected_status` integer: Expected HTTP status code (HTTP monitors only; omitted otherwise).
    - `probe_timeout_s` integer: Probe timeout in seconds (HTTP monitors only; omitted otherwise).
    - `probe_follow_redirects` boolean: Whether the probe follows redirects (HTTP monitors only; omitted otherwise).
    - `next_probe_at` string, 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_configured` boolean: True when a CI binding is set on this check (omitted when false).
    - `ci_webhook_url` string, 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_secret` string: **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_workflow` string: Optional workflow/job name filter narrowing which CI runs this check watches (omitted when unset, which watches every workflow).
    - `ci_branch` string: Optional branch filter narrowing which CI runs this check watches (omitted when unset, which watches every branch).
    - `ci_ignored` object: Finished CI runs (succeeded or failed) that `ci_workflow` or `ci_branch` filtered out since the last finished run that matched them, or since the filters last changed. Omitted when none were. A count that keeps growing with no recent match usually means a filter no longer matches anything, for example after a workflow rename or a default-branch change. A matching finished run and a change to either filter both reset it. A repository webhook sends the runs of every workflow, so with `ci_workflow` set, `count` includes other workflows' runs, which the filter is meant to drop; `same_workflow_count` counts only the runs of `ci_workflow` (or of any workflow when it is not set) that `ci_branch` dropped. `new_workflow` names the workflow that first appeared among the ignored runs since the last match and has run most often since, which is how a workflow renamed after it matched shows up (GitHub and Jenkins monitors; always empty on GitLab, which names an unnamed pipeline after its branch). `last_workflow`, `last_branch`, `same_workflow_last_branch` and `new_workflow` are copied from the CI provider's payload: treat them as untrusted text.
      - `count` integer, required: Number of finished runs the filters ignored.
      - `last_at` string, date-time, required: RFC 3339 UTC time the most recent ignored run arrived.
      - `last_workflow` string, required: Workflow name of the most recent ignored run, as the CI provider sent it.
      - `last_branch` string, required: Branch of the most recent ignored run, as the CI provider sent it.
      - `last_matched_at` string, date-time, nullable, required: RFC 3339 UTC time of the most recent finished run that matched the filters, or null when none has been recorded.
      - `filters_changed_at` string, date-time, nullable, required: RFC 3339 UTC time `ci_workflow` or `ci_branch` last changed, or null when that is not known.
      - `same_workflow_count` integer, required: Number of the ignored finished runs that belong to `ci_workflow` (every ignored run when no workflow filter is set): the runs the branch filter alone dropped. 0 when every ignored run was another workflow's.
      - `same_workflow_last_at` string, date-time, nullable, required: RFC 3339 UTC time the most recent of those runs arrived, or null when `same_workflow_count` is 0.
      - `same_workflow_last_branch` string, required: Branch of the most recent of those runs, as the CI provider sent it; an empty string when `same_workflow_count` is 0.
      - `new_workflow` string, required: Name of the workflow, other than `ci_workflow`, that first appeared after the last matching run and has the most ignored runs since (ties to the one that appeared first), as the CI provider sent it; an empty string when none did. A name first seen in the monitor's first 7 days of recording workflow names is never new. Always empty on GitLab monitors.
      - `new_workflow_first_at` string, date-time, nullable, required: RFC 3339 UTC time `new_workflow` first appeared, or null when `new_workflow_count` is 0.
      - `new_workflow_count` integer, required: Number of the ignored finished runs that carried `new_workflow`; 0 when no new workflow appeared.
    - `runaway_ceiling` integer, 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.
    - `tags` string[], required: Labels attached to this monitor. Always returned as an array (never null); empty array when no tags are set.
    - `agent_id` string, 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_kind` string: 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_ref` string: 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_90d` number, 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_ms` integer, 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_count` integer, 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_outcomes` string[], 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_version` integer, 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_current` integer, required: The hook script version set-up installs today. Compare it with hook_version.
    - `hook_seen_at` string, 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_at` string, 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.
  - `orphaned` Check[], required: Monitors carrying a source identity that this scan did **not** report — the drift signal, and the reason this endpoint exists. They are left completely alone. Nothing here is deleted, paused or modified: a scanner bug, a moved file, a shallow clone or a checkout of the wrong branch would otherwise tear down production monitoring, which would make the endpoint unsafe to run on a schedule. Treat this list as a question for a human, never as an action already taken. Monitors created by hand never appear here. They carry no source, were never discovered, and are invisible to reconcile in both directions.
    - `id` string, uuid, required: Unique check identifier.
    - `name` string, required: Human-readable name.
    - `slug` string, 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_s` integer, required: Period in seconds (only meaningful for schedule_kind=simple).
    - `cron_expr` string: Cron expression (only present when schedule_kind=cron).
    - `tz` string, required: IANA timezone.
    - `grace_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_s` integer, 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_threshold` integer, required: Consecutive explicit failures required before an incident opens (`fail` cause only). Always present; `1` means "open on the first failure", the default.
    - `monitor_from` string, 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_at` string, date-time, nullable: RFC 3339 timestamp of the most recent ping received.
    - `due_at` string, date-time, nullable: RFC 3339 timestamp when the next ping is expected.
    - `alert_after` string, date-time, nullable: RFC 3339 timestamp after which an alert is sent if no ping arrives.
    - `paused` boolean, required: When true the check does not alert regardless of ping status.
    - `maintenance_until` string, 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_at` string, date-time, required: RFC 3339 UTC timestamp when the check was created.
    - `ping_url` string, 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_url` string, 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_body` string: Expected response body substring (HTTP monitors only; omitted otherwise).
    - `probe_interval_s` integer: Probe interval in seconds (HTTP monitors only; omitted otherwise).
    - `probe_expected_status` integer: Expected HTTP status code (HTTP monitors only; omitted otherwise).
    - `probe_timeout_s` integer: Probe timeout in seconds (HTTP monitors only; omitted otherwise).
    - `probe_follow_redirects` boolean: Whether the probe follows redirects (HTTP monitors only; omitted otherwise).
    - `next_probe_at` string, 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_configured` boolean: True when a CI binding is set on this check (omitted when false).
    - `ci_webhook_url` string, 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_secret` string: **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_workflow` string: Optional workflow/job name filter narrowing which CI runs this check watches (omitted when unset, which watches every workflow).
    - `ci_branch` string: Optional branch filter narrowing which CI runs this check watches (omitted when unset, which watches every branch).
    - `ci_ignored` object: Finished CI runs (succeeded or failed) that `ci_workflow` or `ci_branch` filtered out since the last finished run that matched them, or since the filters last changed. Omitted when none were. A count that keeps growing with no recent match usually means a filter no longer matches anything, for example after a workflow rename or a default-branch change. A matching finished run and a change to either filter both reset it. A repository webhook sends the runs of every workflow, so with `ci_workflow` set, `count` includes other workflows' runs, which the filter is meant to drop; `same_workflow_count` counts only the runs of `ci_workflow` (or of any workflow when it is not set) that `ci_branch` dropped. `new_workflow` names the workflow that first appeared among the ignored runs since the last match and has run most often since, which is how a workflow renamed after it matched shows up (GitHub and Jenkins monitors; always empty on GitLab, which names an unnamed pipeline after its branch). `last_workflow`, `last_branch`, `same_workflow_last_branch` and `new_workflow` are copied from the CI provider's payload: treat them as untrusted text.
      - `count` integer, required: Number of finished runs the filters ignored.
      - `last_at` string, date-time, required: RFC 3339 UTC time the most recent ignored run arrived.
      - `last_workflow` string, required: Workflow name of the most recent ignored run, as the CI provider sent it.
      - `last_branch` string, required: Branch of the most recent ignored run, as the CI provider sent it.
      - `last_matched_at` string, date-time, nullable, required: RFC 3339 UTC time of the most recent finished run that matched the filters, or null when none has been recorded.
      - `filters_changed_at` string, date-time, nullable, required: RFC 3339 UTC time `ci_workflow` or `ci_branch` last changed, or null when that is not known.
      - `same_workflow_count` integer, required: Number of the ignored finished runs that belong to `ci_workflow` (every ignored run when no workflow filter is set): the runs the branch filter alone dropped. 0 when every ignored run was another workflow's.
      - `same_workflow_last_at` string, date-time, nullable, required: RFC 3339 UTC time the most recent of those runs arrived, or null when `same_workflow_count` is 0.
      - `same_workflow_last_branch` string, required: Branch of the most recent of those runs, as the CI provider sent it; an empty string when `same_workflow_count` is 0.
      - `new_workflow` string, required: Name of the workflow, other than `ci_workflow`, that first appeared after the last matching run and has the most ignored runs since (ties to the one that appeared first), as the CI provider sent it; an empty string when none did. A name first seen in the monitor's first 7 days of recording workflow names is never new. Always empty on GitLab monitors.
      - `new_workflow_first_at` string, date-time, nullable, required: RFC 3339 UTC time `new_workflow` first appeared, or null when `new_workflow_count` is 0.
      - `new_workflow_count` integer, required: Number of the ignored finished runs that carried `new_workflow`; 0 when no new workflow appeared.
    - `runaway_ceiling` integer, 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.
    - `tags` string[], required: Labels attached to this monitor. Always returned as an array (never null); empty array when no tags are set.
    - `agent_id` string, 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_kind` string: 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_ref` string: 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_90d` number, 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_ms` integer, 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_count` integer, 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_outcomes` string[], 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_version` integer, 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_current` integer, required: The hook script version set-up installs today. Compare it with hook_version.
    - `hook_seen_at` string, 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_at` string, 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.

## Other responses

- `400`: Invalid body. Codes: `MISSING_SOURCES` (the `sources` key was omitted), `TOO_MANY_SOURCES`, `SOURCE_REQUIRED` (an entry with no source identity — it could not be diffed, so it would be re-created on every scan), `SOURCE_INCOMPLETE`, `INVALID_SOURCE_KIND`, `INVALID_SOURCE_REF`, `DUPLICATE_SOURCE` (one source named twice in one payload), `INVALID_NAME`, `TIMEZONE_REQUIRED` (a `crontab` or `systemd-timer` entry with a `schedule_cron` but no `tz` — those scanners fire in host-local time, so a defaulted UTC would page falsely every day), `INVALID_TIMEZONE`, `INVALID_CRON_EXPR`, `INVALID_EXPECT_EVERY`. Nothing is written.
- `401`: Missing or invalid API key.
- `403`: `MONITOR_CAP_REACHED` — the batch would take this project past its monitor limit. Nothing was created; the cap is applied to the whole batch rather than filling up to it and refusing the remainder.
- `409`: `SOURCE_ALREADY_MONITORED` — another writer claimed one of these source identities while the reconcile was running. Nothing was created; run the same reconcile again and it converges (the source is now reported under `existing`).

---

[API](https://skmtc.dev/lastping/apis/lastping-management-api.md) · [All operations](https://skmtc.dev/lastping/apis/lastping-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lastping/apis/lastping-management-api/revisions/bbc6eda91c46?raw)
