# Checks

## Check Model

### Fields

- **`object`** `"check"`

- **`id`** `string`

- **`project_id`** `string`

- **`team_id`** `string`

- **`name`** `string`

- **`source_id`** `string`

   The source that observations are written to. Nothing else is stored.

- **`source_name`** `string | null`

- **`collection_slug`** `string`

   The collection within the source that observations land in. Give several checks the same slug
   to chart them together, one strip per check under a worst-of overall row.

- **`endpoint_url`** `string`

   The HTTPS URL requested on every run.

- **`method`** [`CheckMethod`](/api/checks#check-method)

- **`headers_configured`** `string[]`

   Names of the headers sent with every request. The values are stored encrypted and are never
   returned, so this shows what was configured without revealing the secrets.

- **`request_body`** `string | null`

   The request body sent when the method is `post`. Null for `get`.

- **`schedule_cron`** `string`

   Cron expression setting when the endpoint is requested, evaluated in UTC.

- **`check_retry_seconds`** `number | null`

   How long to wait before one confirming request settles a failed run. Null records the first
   result as it stands, which turns a single flake into a minute of recorded downtime.

- **`status`** [`CheckStatus`](/api/checks#check-status)

   The current state. A check is only ever stopped by you pausing it or by the team falling out of
   good standing. A failing endpoint never stops it, because the outage is the thing it is there
   to record.

- **`next_fetch_at`** [`ISODateString | null`](/api/checks#iso-date-string)

   When the next run is due. Null while the check is paused or disabled.

- **`missed_slot_count`** `number`

   How many scheduled slots have elapsed without a run, since the check was created or last
   resumed. Almost always because the project had no healthy server at the time.
   
   Missed slots are counted rather than run late: a slot describes the endpoint at one moment, so
   a late request would report the wrong minute. These are gaps in the uptime record, not
   downtime.

- **`last_up`** `boolean | null`

   Whether the most recent run found the endpoint up, meaning it answered with a 2xx. Null until
   the first run settles, and null again after a resume, which is what separates a check nothing
   has measured yet from one measured to be down.

- **`results_hourly`** `CheckHourlyResults[]`

   The last 24 hours of results, oldest first. Always 24 entries, so an hour with no runs reads as
   zeros rather than being absent, and a check that has never run still returns a full window. `up
   / (up + down)` over the window is the uptime for that period.

- **`last_checked_at`** [`ISODateString | null`](/api/checks#iso-date-string)

   When the endpoint was last requested, whether or not it answered. Cleared by a resume.

- **`last_success_at`** [`ISODateString | null`](/api/checks#iso-date-string)

   When the endpoint last returned a 2xx. Survives a pause, so it dates an ongoing outage.

- **`last_failure_at`** [`ISODateString | null`](/api/checks#iso-date-string)

   When the endpoint last failed to return a 2xx. Survives a pause.

- **`consecutive_failure_count`** `number`

   How many runs have failed in a row. Resets on the next success. Reporting only: the count never
   stops the check, however high it climbs.

- **`last_error`** `string | null`

   The error from the most recent failed run.

- **`last_error_stage`** [`CheckErrorStage | null`](/api/checks#check-error-stage)

   Which step of the most recent failed run went wrong.

- **`last_http_status`** `number | null`

   The HTTP status returned by the most recent run. Null when nothing answered.

- **`last_response_time_ms`** `number | null`

   How long the most recent run took, in milliseconds.

- **`created_at`** [`ISODateString`](/api/checks#iso-date-string)

- **`updated_at`** [`ISODateString`](/api/checks#iso-date-string)

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### CheckMethod

`"get" | "post"`

Stored as plain strings rather than Postgres enums: nothing queries by either, so a database enum
would only buy DDL on every added value. `CheckStatus` stays an enum because it IS queried, and
backs the dispatch index.

#### CheckStatus

`"active" | "paused" | "disabled"`

#### CheckErrorStage

`"request" | "response"`

## Check Test Result

### Fields

- **`ok`** `boolean`

   Whether the endpoint answered with a 2xx.

- **`status`** `number`

   The HTTP status the endpoint returned. Zero when no response was received at all.

- **`status_text`** `string`

- **`response_time_ms`** `number`

   How long the request took, in milliseconds.

- **`byte_length`** `number`

   How many bytes the endpoint returned.

- **`body_preview`** `string`

   The first 2 KB of the response body, for confirming you reached the endpoint you meant to.

- **`error_stage`** [`CheckErrorStage | null`](/api/checks#check-error-stage)

   Which step went wrong. Null when the test succeeded.

- **`error`** `string | null`

   What went wrong. Null when the test succeeded.

### Referenced Types

#### ISODateString

`ISODateString`

An ISO 8601 date-time string returned at the JSON API boundary.

#### CheckErrorStage

`"request" | "response"`

## List Checks

### Endpoint

Retrieve a list of checks.

```http
GET /v1/projects/:project_id/checks
```

**Scope:** `checks:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

### Query Parameters

- **`order_by`** `string`
  Field the results are ordered by. Optional. Defaults to `"created_at"`. Allowed values: `"name"`, `"created_at"`.

- **`status`** `string`
  Return only checks in this state. Optional. Allowed values: `"active"`, `"paused"`, `"disabled"`.

- **`source_id`** `string`
  Return only checks that write their observations to this source. Optional.

- **`limit`** `number`
  Maximum number of items to return. Optional. Defaults to `25`. Minimum: `1`. Maximum: `200`.

- **`after`** `string`
  Cursor from `pagination.next_cursor` of a previous response. Returns the resources after that page. Optional.

- **`before`** `string`
  Cursor from `pagination.prev_cursor` of a previous response. Returns the resources before that page. Optional.

- **`sort`** `string`
  Sort direction for the result set. Optional. Defaults to `"desc"`. Allowed values: `"asc"`, `"desc"`.

### Response

```ts
{
  message: string;
  data: Check[];
  status: 200;
  error: null;
  pagination: Pagination;
  endpoint: string;
}
```

### Comments

- `after` and `before` are mutually exclusive.

## Retrieve Check

### Endpoint

Retrieve a single check.

```http
GET /v1/projects/:project_id/checks/:check_id
```

**Scope:** `checks:read`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Response

Check retrieved

```ts
{
  message: string;
  data: Check;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

## Create Check

### Endpoint

Create a scheduled check in a project.

```http
POST /v1/projects/:project_id/checks
```

**Scope:** `checks:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

### Request Body

- **`name`** `string` -- **Required**
  Name shown for this check. Minimum length: `2`. Maximum length: `128`.

- **`source_id`** `string` -- **Required**
  Source that observations are written to. Nothing else about the response is stored. Minimum length: `1`.

- **`collection_slug`** `string` -- **Required**
  Collection within the source that observations land in. Give several checks the same slug to chart them together, one strip per check under a worst-of overall row. Minimum length: `1`. Maximum length: `64`.

- **`endpoint_url`** `string` -- **Required**
  HTTPS URL requested on every run. Must resolve to a public address. Minimum length: `1`. Maximum length: `2048`.

- **`method`** `string`
  HTTP method used to request the endpoint. Optional. Defaults to `"get"`. Allowed values: `"get"`, `"post"`.

- **`headers`** `object | null`
  Headers sent with every request, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.

- **`request_body`** `string | null`
  Body sent with every request. Only valid when `method` is `post`, for health endpoints that answer queries over POST. Optional.

- **`schedule_cron`** `string`
  Cron expression setting when the endpoint is requested, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Defaults to `"* * * * *"`. Minimum length: `1`. Maximum length: `128`.

- **`check_retry_seconds`** `integer | null`
  Seconds to wait before one confirming request settles a failed run. Send `null` to record the first result as it stands. Optional. Defaults to `10`.

### Response

Check created

```ts
{
  message: string;
  data: Check;
  status: 201;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- The endpoint must be reachable over HTTPS at a public address. Addresses inside private or reserved ranges are rejected, and the check is repeated on every run, not just at create.
- A check is created `active` even if the project has no servers yet. It starts running as soon as a server exists; an empty fleet delays the first run rather than changing the status.
- The response body is never stored or parsed. Each run writes one observation recording whether the endpoint answered, its status code and how long it took.
- A failing endpoint never stops a check, however long the failure lasts. Only pausing it, or the team falling out of good standing, stops one.
- `request_body` is only accepted when `method` is `post`. Sending one with a `get` check is rejected rather than ignored, so a half-configured check fails at create time instead of quietly measuring the wrong request.
- `headers` accepts at most 20 entries. A header whose value is `null` is ignored here, since on create there is nothing yet for it to remove. Names are matched case-insensitively, so two spellings of one name are stored as a single header.
- `schedule_cron` must be a valid cron expression. It is evaluated in UTC, and the finest granularity is one minute.
- `collection_slug` is required, and must be lowercase, start with a letter or digit, and may otherwise contain digits, letters, hyphens and underscores. Naming it here is what lets several checks deliberately share one collection and chart together; renaming the check later never moves the series, because the slug is stored rather than derived.
- `check_retry_seconds` is between 1 and 20, and defaults to 10. A failed run waits this long and requests once more before it is recorded, so a single flake does not become a recorded minute of downtime. Send `null` to record the first result as it stands.
- Each run writes one observation to `collection_slug` carrying `check_id`, `check_name`, `checked_at`, `up`, `http_status`, `response_time_ms`, `error_stage`, `error_message` and `attempts`. `up` is 1 when the endpoint returned a 2xx and 0 when it did not answer or answered with an error status, so the average of `up` over a period is the uptime for that period.
- `check_name` is the name the check carried when the run was recorded, so a chart grouped on `check_id` can label its series with something readable. Renaming the check does not rewrite observations already stored.
- `checked_at` is the scheduled minute rather than the moment the observation was written, so a run delayed by a confirming request still lands in the minute it describes.
- `name` must be unique within the project, so a check is identifiable in a list and in the strip it charts as.

## Update Check

### Endpoint

Update a check.

```http
POST /v1/projects/:project_id/checks/:check_id
```

**Scope:** `checks:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Request Body

- **`name`** `string`
  Name shown for this check. Optional. Minimum length: `2`. Maximum length: `128`.

- **`collection_slug`** `string | null`
  Collection within the source that observations land in. Give several checks the same slug to chart them together, one strip per check under a worst-of overall row. Optional.

- **`endpoint_url`** `string`
  HTTPS URL requested on every run. Must resolve to a public address. Optional. Minimum length: `1`. Maximum length: `2048`.

- **`method`** `string`
  HTTP method used to request the endpoint. Optional. Allowed values: `"get"`, `"post"`.

- **`headers`** `object | null`
  Headers sent with every request, for authenticating to the endpoint. Stored encrypted and never returned; responses list only the header names, as `headers_configured`. Optional.

- **`request_body`** `string | null`
  Body sent with every request. Only valid when `method` is `post`, for health endpoints that answer queries over POST. Optional.

- **`schedule_cron`** `string`
  Cron expression setting when the endpoint is requested, evaluated in UTC. Defaults to every minute, which is also the shortest supported gap. Optional. Minimum length: `1`. Maximum length: `128`.

- **`check_retry_seconds`** `integer | null`
  Seconds to wait before one confirming request settles a failed run. Send `null` to record the first result as it stands. Optional.

### Response

Check updated

```ts
{
  message: string;
  data: Check;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Changes take effect on the next run; a run already in flight finishes under the old configuration.
- A changed `schedule_cron` takes effect from the next run onward.
- At least one field must be provided.
- Any field not listed here is rejected, including `source_id`, which cannot be changed after create. Create a new check to record into a different source.
- Clearing `request_body` is required before changing `method` from `post` to `get`; a check cannot keep a body it would never send.
- `headers` is a patch, not a replacement, because the values are write-only and never returned. A name mapped to a string adds or replaces that header, a name mapped to `null` removes it, and a name you do not mention keeps its stored value. Omit the field to leave every header alone, or send `null` in place of the object to remove all of them.
- `headers` names are matched case-insensitively, so patching `authorization` replaces a stored `Authorization` rather than adding a second header. The spelling you send is the one stored and sent.
- `headers` accepts at most 20 entries, counted after the patch is applied.
- `schedule_cron` must be a valid cron expression, evaluated in UTC. Changing it restarts the missed-slot count from the edit, since slots before it belonged to a different schedule.
- Send `check_retry_seconds` as `null` to stop confirming failures and record the first result as it stands.
- Changing `collection_slug` starts a new series. Observations already written stay where they are, so a chart over the old collection stops at the edit.

## Delete Check

### Endpoint

Delete a check.

```http
DELETE /v1/projects/:project_id/checks/:check_id
```

**Scope:** `checks:delete`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Response

Check deleted

```ts
{
  message: string;
  data: null;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Observations the check already wrote are not deleted. They belong to the source and stay queryable, so a chart of past uptime keeps working.

## Pause Check

### Endpoint

Pause a check so it stops running.

```http
POST /v1/projects/:project_id/checks/:check_id/pause
```

**Scope:** `checks:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Response

Check paused

```ts
{
  message: string;
  data: Check;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Only an active check can be paused. Pausing an already-paused check succeeds and changes nothing.
- A pause is a gap in the uptime record rather than downtime. Nothing is recorded for the minutes it covers.

## Resume Check

### Endpoint

Resume a paused check.

```http
POST /v1/projects/:project_id/checks/:check_id/resume
```

**Scope:** `checks:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Response

Check resumed

```ts
{
  message: string;
  data: Check;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Resumes a check you paused. The failure count starts again from zero.
- A check the system stopped while the team was not in good standing is not resumable here and returns 400. It starts again on its own once the team is back in good standing.

## Test Check

### Endpoint

Request the endpoint once without recording anything.

```http
POST /v1/projects/:project_id/checks/:check_id/test
```

**Scope:** `checks:write`

### Path Parameters

- **`project_id`** `string` -- **Required**
  Unique identifier of the project.

- **`check_id`** `string` -- **Required**
  Unique identifier of the check.

### Response

```ts
{
  message: string;
  data: CheckTestResult;
  status: 200;
  error: null;
  pagination: null;
  endpoint: string;
}
```

### Comments

- Nothing is recorded and no health field or schedule is changed, so this is safe to run against a live check. The result does not appear in the uptime history.
- The response reports the HTTP status, how long the request took, and the first 2 KB of the body so you can confirm you reached the endpoint you meant to.
- There is no confirming retry here, unlike a scheduled run: a test reports what happened rather than settling a verdict.

