---
title: "Replace metric guards for a check"
method: PUT
path: "/api/v1/checks/{id}/guards"
tags: ["Checks"]
---

# Replace metric guards for a check

`PUT /api/v1/checks/{id}/guards`

## Request

- Base URL: `https://app.lastping.dev`
- URL: `https://app.lastping.dev/api/v1/checks/{id}/guards`
- Auth: API key in header `X-Api-Key`

Replaces the complete set of metric guards for the check
(**replace-the-set** semantics) — the request body is the new set in
its entirety; guards absent from the body are deleted.

Every guard is validated before any write
(the same validation everywhere): a missing `name`, a `path` containing
`[`, `*` or `$`, an `aggregation` outside `sum`/`max`/`avg`, a
`window_s` of zero or below, a `window_s` above 604800, or a
non-finite `ceiling` all fail the request with `400` and **nothing is
written**. The `400` message names the offending guard.

Two caps apply, and both are measured cost rather than taste:

- **`window_s` at most 604800 seconds (7 days).** A guard re-reads
  every ping body in its window on every ping, so the work grows
  with the window. Seven days is also comfortably inside the 90-day
  ping retention, past which a guard would aggregate over pings no
  longer kept and quietly under-report.
- **At most 5 guards per check**, against 20 for assertions. An
  assertion is a string match over a set already in hand; a guard is
  its own lookup, because each carries its own window — so N guards
  multiply the per-ping cost by N.

Returns the resulting set (same shape as `GET`) on success.

## Request body

- object
  - `guards` MetricGuard[], required: The complete new set of guards (at most 5). An empty array clears all guards.
    - `id` string, uuid: Server-assigned. Present on GET/PUT responses; ignored if sent in a PUT request body.
    - `name` string, required: Human label for the guard. It is written to the incident's detail when the guard trips, and is the only thing that distinguishes a tripped guard from a ping-rate runaway in the alert.
    - `path` string, required: A dotted path into the ping body decoded as JSON, e.g. `"cost.usd"`. Identical in meaning to an `Assertion`'s `path` and read by the same code: it must not contain `[`, `*` or `$`, since this is a fixed lookup rather than a query. A value that is a numeric string counts as a number, on the same rule assertions use.
    - `window_s` integer, required: Trailing window in seconds. Capped at 604800 (7 days), and the cap is cost rather than policy: a guard re-aggregates every ping body in its window on every ping, so the work grows with the window. A window longer than the 90-day ping retention would also aggregate over pings that are no longer kept and quietly report a number lower than the truth.
    - `ceiling` number, double, required: The value the aggregate must not exceed. Zero and negative values are both legitimate.
    - `aggregation` 'sum' | 'max' | 'avg', required: How the numbers found in the window are rolled up: `sum` totals them, `max` takes the largest, `avg` averages them over the pings that actually reported a number at `path`.

## Response `200`

Resulting metric guards after applying the replace-the-set operation.

- MetricGuardsResponse: The complete set of metric guards configured for a check.
  - `guards` MetricGuard[], required: Always a JSON array, empty when the check has no guards configured.
    - `id` string, uuid: Server-assigned. Present on GET/PUT responses; ignored if sent in a PUT request body.
    - `name` string, required: Human label for the guard. It is written to the incident's detail when the guard trips, and is the only thing that distinguishes a tripped guard from a ping-rate runaway in the alert.
    - `path` string, required: A dotted path into the ping body decoded as JSON, e.g. `"cost.usd"`. Identical in meaning to an `Assertion`'s `path` and read by the same code: it must not contain `[`, `*` or `$`, since this is a fixed lookup rather than a query. A value that is a numeric string counts as a number, on the same rule assertions use.
    - `window_s` integer, required: Trailing window in seconds. Capped at 604800 (7 days), and the cap is cost rather than policy: a guard re-aggregates every ping body in its window on every ping, so the work grows with the window. A window longer than the 90-day ping retention would also aggregate over pings that are no longer kept and quietly report a number lower than the truth.
    - `ceiling` number, double, required: The value the aggregate must not exceed. Zero and negative values are both legitimate.
    - `aggregation` 'sum' | 'max' | 'avg', required: How the numbers found in the window are rolled up: `sum` totals them, `max` takes the largest, `avg` averages them over the pings that actually reported a number at `path`.

## Other responses

- `400`: Invalid request — one of: - More than 5 guards in the request. - A guard fails validation: missing `name`, missing `path`, a `path` containing `[`, `*` or `$`, an `aggregation` outside `sum`/`max`/`avg`, `window_s` at or below zero, `window_s` above 604800, or a non-finite `ceiling`. - Malformed JSON body. Nothing is written when any entry fails validation.
- `401`: Missing or invalid API key.
- `404`: Check not found in this project.
- `429`: Too many requests. The per-principal fixed-window rate limit has been exceeded. Retry after the number of seconds indicated by the `Retry-After` response header. The problem body's `code` is `RATE_LIMITED`. It names this limiter specifically, so a client can tell it apart from the application-level throttles that also answer 429 with their own codes and their own, much longer, `Retry-After` values (`TEST_PING_RATE_LIMITED`, `TOO_MANY_ATTEMPTS`). Back off globally on `RATE_LIMITED`; back off on the one operation for the others. The principal is the API key for a key-authenticated request and the USER for a browser session, so every tab one person has open shares one budget.

---

[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)
