---
title: 'REST API'
description: 'Complete reference for the Yorker REST API — all endpoints, methods, request/response schemas.'
section: 'Reference'
canonical_url: 'https://yorkermonitoring.com/docs/reference/api'
---

# REST API

The Yorker REST API provides programmatic access to monitors, alerts, SLOs, notification channels, and locations. All endpoints return JSON and validate request bodies through shared Zod schemas.

---

## Authentication

To authenticate API requests, include your API key in the `Authorization` header as a Bearer token.

```
Authorization: Bearer sk_...
```

All endpoints require authentication. Unauthenticated requests return `401 Unauthorized`.

---

## Base URL

```
https://yorkermonitoring.com
```

---

## Common Response Patterns

### Success

Successful responses return a `2xx` status code with a JSON body.

### Validation Error (400)

```json
{
  "error": "Validation failed",
  "details": {
    "fieldErrors": { "name": ["String must contain at least 1 character(s)"] },
    "formErrors": []
  }
}
```

### Not Found (404)

```json
{ "error": "Check not found" }
```

### Conflict (409)

```json
{ "error": "Channel is in use by alert rule \"my-alert\". Remove it from those first." }
```

---

## Checks

### List Checks

To list all checks for your team:

```
GET /api/checks
```

**Response** `200`

```json
{
  "checks": [
    {
      "id": "chk_abc123",
      "teamId": "team_xyz",
      "name": "Homepage",
      "type": "http",
      "configJson": { "url": "https://example.com", "method": "GET", "timeoutMs": 30000, "followRedirects": true, "maxRedirects": 5, "assertions": [] },
      "frequencySeconds": 300,
      "locations": ["loc_us_east", "loc_eu_central"],
      "enabled": true,
      "createdAt": "2025-01-15T10:00:00.000Z",
      "updatedAt": null
    }
  ]
}
```

### Create Check

To create a new check:

```
POST /api/checks
```

**Request body** -- validated through `CreateCheckSchema`

For HTTP checks:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | `string` | Yes | -- | Check name (1-255 characters). |
| `type` | `"http"` | Yes | -- | Check type. |
| `enabled` | `boolean` | No | `true` | Whether the check is active. |
| `frequencySeconds` | `number` | No | `300` | Check interval (10-86400). |
| `locations` | `string[]` | Yes | -- | Location IDs (at least one). |
| `httpConfig` | `object` | Yes | -- | HTTP configuration (see below). |

`httpConfig` fields:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `url` | `string` | Yes | -- | Target URL (must be a valid URL). |
| `method` | `string` | No | `"GET"` | `GET` \| `POST` \| `PUT` \| `DELETE` \| `PATCH` \| `HEAD` |
| `headers` | `object` | No | -- | Request headers (string key-value pairs). |
| `body` | `string` | No | -- | Request body. |
| `auth` | `object` | No | -- | Auth config (`basic`, `bearer`, or `api-key`). |
| `followRedirects` | `boolean` | No | `true` | Follow HTTP redirects. |
| `maxRedirects` | `number` | No | `5` | Maximum number of redirects. |
| `timeoutMs` | `number` | No | `30000` | Request timeout in milliseconds. |
| `assertions` | `array` | No | `[]` | Assertion rules. See [Assertions](/docs/reference/assertions). |

For browser checks:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | `string` | Yes | -- | Check name (1-255 characters). |
| `type` | `"browser"` | Yes | -- | Check type. |
| `enabled` | `boolean` | No | `true` | Whether the check is active. |
| `frequencySeconds` | `number` | No | `300` | Check interval (10-86400). |
| `locations` | `string[]` | Yes | -- | Location IDs (at least one). |
| `browserConfig` | `object` | Yes | -- | Browser configuration (see below). |

`browserConfig` fields:

| Field | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
| `browserMode` | `string` | Yes | — | `"scripted"` \| `"url"` | Execution mode. |
| `script` | `string` | Yes (scripted) | — | — | Playwright script content (for `"scripted"` mode). |
| `url` | `string` | Yes (url) | — | Valid URL | Target URL (for `"url"` mode). |
| `timeoutMs` | `number` | No | `30000` | 5000-120000 | Script timeout in milliseconds. |
| `viewport` | `object` | No | `{ width: 1280, height: 720 }` | — | Viewport dimensions. |
| `device` | `string` | No | — | — | Playwright device name for emulation. |
| `screenshotMode` | `string` | No | `"every_step"` | `every_step` \| `failure_only` \| `disabled` | Screenshot capture mode. |
| `videoEnabled` | `boolean` | No | `false` | — | Record video. |

For MCP checks:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | `string` | Yes | — | Check name (1-255 characters). |
| `type` | `"mcp"` | Yes | — | Check type. |
| `enabled` | `boolean` | No | `true` | Whether the check is active. |
| `frequencySeconds` | `number` | No | `300` | Check interval (10-86400). |
| `locations` | `string[]` | Yes | — | Location IDs (at least one). |
| `mcpConfig` | `object` | Yes | — | MCP configuration (see below). |

`mcpConfig` fields:

| Field | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
| `endpoint` | `string` | Yes | — | Valid URL | Streamable HTTP endpoint of the MCP server. |
| `timeoutMs` | `number` | No | `30000` | 5000-120000 | Request timeout in milliseconds. |
| `auth` | `object` | No | — | `basic`, `bearer`, `api-key` | Same auth shape as HTTP checks. |
| `expectedTools` | `string[]` | No | — | — | Tool names that must be present. Missing tools fail the check. |
| `testCalls` | `array` | No | — | — | Tool invocations to exercise. Each entry has `toolName`, optional `arguments` (object), and optional `expectedOutputContains`. |
| `detectSchemaDrift` | `boolean` | No | `true` | — | Emit events when tool list or signatures change between runs. |

**Response** `201`

```json
{
  "check": {
    "id": "chk_abc123",
    "teamId": "team_xyz",
    "name": "Homepage",
    "type": "http",
    "configJson": { "url": "https://example.com", "method": "GET", "timeoutMs": 30000, "followRedirects": true, "maxRedirects": 5, "assertions": [] },
    "frequencySeconds": 300,
    "locations": ["loc_us_east"],
    "enabled": true,
    "createdAt": "2025-01-15T10:00:00.000Z",
    "updatedAt": null
  }
}
```

**Error** `403` -- Plan limit exceeded.

### Get Check

To get a single check with its 50 most recent results:

```
GET /api/checks/:id
```

**Response** `200`

```json
{
  "check": { "id": "chk_abc123", "name": "Homepage", "..." : "..." },
  "results": [
    {
      "id": "res_xyz789",
      "checkId": "chk_abc123",
      "runId": "run_abc",
      "locationId": "loc_us_east",
      "status": "success",
      "responseTimeMs": 142,
      "httpStatusCode": 200,
      "startedAt": "2025-01-15T10:05:00.000Z",
      "completedAt": "2025-01-15T10:05:00.142Z"
    }
  ]
}
```

### Update Check

To update an existing check:

```
PUT /api/checks/:id
```

**Request body** -- all fields are optional (partial update):

| Field | Type | Description |
|---|---|---|
| `name` | `string` | Check name. |
| `configJson` | `object` | Full HTTP or browser config object (replaces existing). |
| `frequencySeconds` | `number` | Check interval. |
| `locations` | `string[]` | Location IDs. |
| `enabled` | `boolean` | Whether the check is active. |

**Response** `200`

```json
{ "check": { "id": "chk_abc123", "..." : "..." } }
```

### Delete Check

To delete a check and its results:

```
DELETE /api/checks/:id
```

**Response** `200`

```json
{ "success": true }
```

### List Check Results

To get paginated results for a check:

```
GET /api/checks/:id/results
```

**Query parameters:**

| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
| `limit` | `number` | `50` | `200` | Number of results to return. |
| `offset` | `number` | `0` | -- | Pagination offset. |
| `detail` | `"true"` | -- | -- | Include Tier B debug data (network requests, console logs, screenshots). |

**Response** `200`

```json
{
  "results": [
    {
      "id": "res_xyz789",
      "checkId": "chk_abc123",
      "runId": "run_abc",
      "locationId": "loc_us_east",
      "status": "success",
      "responseTimeMs": 142,
      "httpStatusCode": 200,
      "timing": {
        "dnsLookupMs": 12,
        "tcpConnectMs": 18,
        "tlsHandshakeMs": 25,
        "ttfbMs": 80,
        "contentTransferMs": 7,
        "totalMs": 142
      },
      "startedAt": "2025-01-15T10:05:00.000Z",
      "completedAt": "2025-01-15T10:05:00.142Z"
    }
  ],
  "limit": 50,
  "offset": 0
}
```

When `detail=true`, browser check results also include `networkRequestsJson`, `screenshotsJson`, and `consoleLogsJson` fields.

### Trigger Ad-Hoc Run

To trigger an immediate check run across all assigned locations:

```
POST /api/checks/:id/trigger
```

**Request body:** none

**Response** `200`

```json
{
  "triggered": true,
  "locations": 3
}
```

---

## Alerts

### List Alert Rules

To list all alert rules for a check:

```
GET /api/checks/:id/alerts
```

**Response** `200`

```json
{
  "alerts": [
    {
      "id": "alert_abc123",
      "checkId": "chk_xyz",
      "name": "downtime-alert",
      "enabled": true,
      "conditions": [
        { "type": "consecutive_failures", "count": 3 }
      ],
      "channelIds": ["nch_abc"],
      "channels": [
        { "type": "slack", "webhookUrl": "https://hooks.slack.com/..." }
      ],
      "createdAt": "2025-01-15T10:00:00.000Z",
      "updatedAt": null
    }
  ]
}
```

The `channels` array contains resolved channel configurations for display. The `channelIds` array contains the raw channel IDs.

### Create Alert Rule

To create an alert rule for a check:

```
POST /api/checks/:id/alerts
```

**Request body** -- validated through `CreateAlertRuleSchema`:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | `string` \| `null` | No | `null` | Alert rule name. |
| `enabled` | `boolean` | No | `true` | Whether the rule is active. |
| `conditions` | `array` | Yes | -- | At least one condition. See [Alert Conditions](#alert-conditions). |
| `channelIds` | `string[]` | Yes | -- | At least one notification channel ID. |

**Response** `201`

```json
{ "id": "alert_abc123" }
```

### Update Alert Rule

To update an existing alert rule:

```
PUT /api/alerts/:alertId
```

**Request body** -- validated through `UpdateAlertRuleSchema` (all fields optional):

| Field | Type | Description |
|---|---|---|
| `name` | `string` \| `null` | Alert rule name. |
| `enabled` | `boolean` | Whether the rule is active. |
| `conditions` | `array` | At least one condition (replaces existing). |
| `channelIds` | `string[]` | At least one channel ID (replaces existing). |

**Response** `200`

```json
{ "success": true }
```

### Delete Alert Rule

To delete an alert rule:

```
DELETE /api/alerts/:alertId
```

**Response** `200`

```json
{ "success": true }
```

### Alert Conditions

Each condition in the `conditions` array must have a `type` field. Available types:

| Type | Fields | Description |
|---|---|---|
| `consecutive_failures` | `count` (default: `2`, min: `1`) | Alert after N consecutive failures. |
| `response_time_threshold` | `maxMs` (required) | Alert when response time exceeds threshold. |
| `multi_location_failure` | `minLocations` (default: `2`, min: `2`), `windowSeconds` (default: `300`) | Alert when failures occur from multiple locations within a time window. |
| `ssl_expiry` | `daysBeforeExpiry` (default: `14`, min: `1`), `severity` (optional) | Alert when SSL certificate nears expiration. |
| `ssl_certificate_changed` | `severity` (optional) | Alert when the leaf certificate fingerprint changes between runs. |
| `ssl_self_signed` | `severity` (optional) | Alert when a self-signed or untrusted-root certificate is detected. |
| `ssl_protocol_deprecated` | `minProtocol` (default: `TLSv1.2`, allowed: `TLSv1.2`, `TLSv1.3`), `severity` (optional) | Alert when the TLS handshake negotiates a protocol older than `minProtocol`. |
| `burn_rate` | `sloId` (required), `burnRateThreshold` (positive number), `longWindowMinutes` (min `60`), `shortWindowMinutes` (min `5`, must be less than long) | SLO burn-rate alert. Typically generated automatically from SLOs with `burnRateAlertsEnabled: true`. |

All SSL conditions (including `ssl_expiry`) accept an optional `severity` field with value `critical`, `warning`, or `info`.

---

## Alert Instances

Alert instances represent individual occurrences of a triggered alert rule. They track state transitions through a lifecycle: `active` -> `acknowledged` -> `resolved` (or `recovered` automatically).

### List Alert Instances

To list alert instances for your team:

```
GET /api/alerts/instances
```

**Query parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `state` | `string` | -- | Filter by state. Comma-separated: `active`, `acknowledged`, `recovered`, `resolved`. |
| `checkId` | `string` | -- | Filter by check ID. |
| `limit` | `number` | `50` | Results per page (1-200). |
| `offset` | `number` | `0` | Pagination offset. |

**Response** `200`

```json
{
  "instances": [
    {
      "id": "ainst_abc123",
      "ruleId": "alert_xyz",
      "checkId": "chk_xyz",
      "state": "active",
      "severity": "critical",
      "startedAt": "2025-01-15T10:00:00.000Z",
      "acknowledgedAt": null,
      "recoveredAt": null,
      "resolvedAt": null,
      "durationMs": null,
      "notificationCount": 2,
      "contextJson": {},
      "muted": false,
      "ruleName": "downtime-alert",
      "checkName": "Homepage"
    }
  ],
  "limit": 50,
  "offset": 0
}
```

### Get Alert Instance

To get a single alert instance with its full event timeline:

```
GET /api/alerts/instances/:id
```

**Response** `200`

```json
{
  "instance": {
    "id": "ainst_abc123",
    "ruleId": "alert_xyz",
    "checkId": "chk_xyz",
    "teamId": "team_xyz",
    "state": "acknowledged",
    "severity": "critical",
    "triggeringResultId": "res_abc",
    "triggeringTraceId": "abc123def456",
    "recoveryResultId": null,
    "startedAt": "2025-01-15T10:00:00.000Z",
    "acknowledgedAt": "2025-01-15T10:05:00.000Z",
    "acknowledgedBy": "user_abc",
    "recoveredAt": null,
    "resolvedAt": null,
    "durationMs": null,
    "notificationCount": 2,
    "contextJson": {},
    "muted": false,
    "ruleName": "downtime-alert",
    "checkName": "Homepage"
  },
  "events": [
    {
      "id": "aevt_abc",
      "instanceId": "ainst_abc123",
      "type": "triggered",
      "actorId": null,
      "createdAt": "2025-01-15T10:00:00.000Z"
    },
    {
      "id": "aevt_def",
      "instanceId": "ainst_abc123",
      "type": "acknowledged",
      "actorId": "user_abc",
      "createdAt": "2025-01-15T10:05:00.000Z"
    }
  ]
}
```

### Acknowledge Alert Instance

To acknowledge an active alert instance (transitions from `active` to `acknowledged`):

```
POST /api/alerts/instances/:id/acknowledge
```

**Request body:** none

**Response** `200`

```json
{ "success": true, "state": "acknowledged" }
```

**Error** `409` -- Instance is not in `active` state.

### Resolve Alert Instance

To manually resolve an alert instance (transitions from `active` or `acknowledged` to `resolved`):

```
POST /api/alerts/instances/:id/resolve
```

**Request body:** none

**Response** `200`

```json
{ "success": true, "state": "resolved", "durationMs": 300000 }
```

**Error** `409` -- Instance is already `recovered` or `resolved`.

---

## Notification Channels

### List Channels

To list all notification channels for your team:

```
GET /api/notification-channels
```

**Response** `200`

```json
{
  "channels": [
    {
      "id": "nch_abc123",
      "name": "ops-slack",
      "type": "slack",
      "config": { "type": "slack", "webhookUrl": "https://hooks.slack.com/..." },
      "createdAt": "2025-01-15T10:00:00.000Z",
      "updatedAt": null
    }
  ]
}
```

### Create Channel

To create a notification channel:

```
POST /api/notification-channels
```

**Request body** -- validated through `CreateNotificationChannelSchema`:

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | Channel name (1-100 characters). Must be unique within the team. |
| `channel` | `object` | Yes | Channel configuration. |

`channel` object (discriminated by `type`):

**Slack:**

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | `"slack"` | Yes | Channel type. |
| `webhookUrl` | `string` (URL) | Yes | Slack incoming webhook URL. |

**Email:**

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | `"email"` | Yes | Channel type. |
| `addresses` | `string[]` | Yes | At least one valid email address. |

**Webhook:**

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `type` | `"webhook"` | Yes | -- | Channel type. |
| `url` | `string` (URL) | Yes | -- | Webhook endpoint URL. |
| `method` | `"POST"` \| `"PUT"` | No | `"POST"` | HTTP method. |
| `headers` | `object` | No | -- | Custom headers. |

**Response** `201`

```json
{
  "channel": {
    "id": "nch_abc123",
    "name": "ops-slack",
    "type": "slack",
    "config": { "type": "slack", "webhookUrl": "https://hooks.slack.com/..." }
  }
}
```

**Error** `409` -- A channel with that name already exists.

### Get Channel

To get a single notification channel:

```
GET /api/notification-channels/:id
```

**Response** `200`

```json
{
  "channel": {
    "id": "nch_abc123",
    "name": "ops-slack",
    "type": "slack",
    "config": { "type": "slack", "webhookUrl": "https://hooks.slack.com/..." },
    "createdAt": "2025-01-15T10:00:00.000Z",
    "updatedAt": null
  }
}
```

### Update Channel

To update a notification channel:

```
PUT /api/notification-channels/:id
```

**Request body** -- validated through `UpdateNotificationChannelSchema` (all fields optional):

| Field | Type | Description |
|---|---|---|
| `name` | `string` | Channel name (1-100 characters). |
| `channel` | `object` | Channel configuration. The `type` field cannot change -- delete and recreate to change type. |

**Response** `200`

```json
{ "success": true }
```

**Error** `400` -- Channel type cannot be changed.
**Error** `409` -- A channel with that name already exists.

### Delete Channel

To delete a notification channel:

```
DELETE /api/notification-channels/:id
```

The channel must not be referenced by any alert rules or SLOs. Remove references first.

**Response** `200`

```json
{ "success": true }
```

**Error** `409` -- Channel is still in use by an alert rule or SLO.

---

## SLOs

### List SLOs

To list all SLO definitions for your team:

```
GET /api/slos
```

**Query parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `checkId` | `string` | -- | Filter by check ID. |
| `sloType` | `string` | -- | Filter by SLO type: `check` or `third_party`. |
| `limit` | `number` | `50` | Results per page (1-200). |
| `offset` | `number` | `0` | Pagination offset. |

**Response** `200`

```json
{
  "slos": [
    {
      "id": "slo_abc123",
      "checkId": "chk_xyz",
      "teamId": "team_xyz",
      "name": "Homepage SLO",
      "targetBasisPoints": 9990,
      "windowDays": 30,
      "burnRateAlertsEnabled": true,
      "channelIds": ["nch_abc"],
      "enabled": true,
      "sloType": "check",
      "patternId": null,
      "sliType": "availability",
      "perfThresholdMs": null,
      "scope": "check",
      "createdAt": "2025-01-15T10:00:00.000Z",
      "updatedAt": null,
      "checkName": "Homepage"
    }
  ],
  "limit": 50,
  "offset": 0
}
```

### Create SLO

To create a new SLO definition:

```
POST /api/slos
```

**Request body** -- validated through `CreateSloSchema` (discriminated union on `sloType`):

For check-based SLOs:

| Field | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
| `sloType` | `"check"` | Yes | -- | -- | SLO type. Defaults to `"check"` if omitted. |
| `checkId` | `string` | Yes | -- | -- | Check ID this SLO tracks. |
| `name` | `string` | Yes | -- | 1-255 characters | SLO name. |
| `targetBasisPoints` | `number` | Yes | -- | Integer, 1-9999 | Availability target (e.g., 9990 = 99.90%). |
| `windowDays` | `number` | Yes | -- | `7` \| `14` \| `30` | Rolling window in days. |
| `burnRateAlertsEnabled` | `boolean` | No | `true` | -- | Enable burn rate alerting. |
| `channelIds` | `string[]` | No | -- | -- | Notification channel IDs for alerts. |
| `enabled` | `boolean` | No | `true` | -- | Whether the SLO is active. |

**Response** `201`

```json
{
  "slo": {
    "id": "slo_abc123",
    "checkId": "chk_xyz",
    "teamId": "team_xyz",
    "name": "Homepage SLO",
    "targetBasisPoints": 9990,
    "windowDays": 30,
    "burnRateAlertsEnabled": true,
    "channelIds": [],
    "enabled": true,
    "sloType": "check",
    "patternId": null,
    "sliType": "availability",
    "perfThresholdMs": null,
    "scope": "check",
    "createdAt": "2025-01-15T10:00:00.000Z",
    "updatedAt": null
  }
}
```

### Get SLO

To get a single SLO definition:

```
GET /api/slos/:id
```

**Response** `200`

```json
{
  "slo": {
    "id": "slo_abc123",
    "checkId": "chk_xyz",
    "name": "Homepage SLO",
    "targetBasisPoints": 9990,
    "windowDays": 30,
    "burnRateAlertsEnabled": true,
    "channelIds": ["nch_abc"],
    "enabled": true,
    "sloType": "check",
    "checkName": "Homepage",
    "..." : "..."
  }
}
```

### Update SLO

To update an existing SLO definition:

```
PUT /api/slos/:id
```

**Request body** -- validated through `UpdateSloSchema` (all fields optional):

| Field | Type | Constraints | Description |
|---|---|---|---|
| `name` | `string` | 1-255 characters | SLO name. |
| `checkId` | `string` | -- | Check ID (cannot be set on team-wide SLOs). |
| `targetBasisPoints` | `number` | Integer, 1-9999 | Availability target. |
| `windowDays` | `number` | `7` \| `14` \| `30` | Rolling window. |
| `burnRateAlertsEnabled` | `boolean` | -- | Enable burn rate alerting. |
| `channelIds` | `string[]` | -- | Notification channel IDs. |
| `enabled` | `boolean` | -- | Whether the SLO is active. |
| `perfThresholdMs` | `number` | Integer, min 1. Only valid for performance SLIs. | Performance threshold. |

Note: `sloType`, `scope`, and `sliType` are immutable after creation. Changing them would invalidate historical data.

**Response** `200`

```json
{
  "slo": { "id": "slo_abc123", "..." : "..." }
}
```

### Delete SLO

To delete an SLO definition:

```
DELETE /api/slos/:id
```

**Response** `200`

```json
{ "success": true }
```

### Get SLO Status

To get the current computed state of an SLO (availability, error budget, burn rates):

```
GET /api/slos/:id/status
```

**Response** `200`

```json
{
  "state": {
    "sloId": "slo_abc123",
    "checkId": "chk_xyz",
    "patternId": null,
    "name": "Homepage SLO",
    "sloType": "check",
    "sliType": "availability",
    "targetBasisPoints": 9990,
    "windowDays": 30,
    "availability": 0.9995,
    "totalCount": 8640,
    "successCount": 8636,
    "avgDurationMs": null,
    "perfComplianceRatio": null,
    "errorBudgetTotal": 43.2,
    "errorBudgetConsumed": 21.6,
    "errorBudgetRemaining": 21.6,
    "budgetConsumedRatio": 0.5,
    "burnRate1h": 0.0,
    "burnRate6h": 0.2,
    "burnRate24h": 0.5
  }
}
```

| Field | Type | Description |
|---|---|---|
| `availability` | `number` | Current availability ratio (0-1). |
| `totalCount` | `number` | Total check runs in the window. |
| `successCount` | `number` | Successful check runs. |
| `errorBudgetTotal` | `number` | Total error budget in estimated minutes. |
| `errorBudgetConsumed` | `number` | Error budget consumed in estimated minutes. |
| `errorBudgetRemaining` | `number` | Error budget remaining in estimated minutes. |
| `budgetConsumedRatio` | `number` | Fraction of budget consumed (0-1). |
| `burnRate1h` | `number` | 1-hour burn rate. |
| `burnRate6h` | `number` | 6-hour burn rate. |
| `burnRate24h` | `number` | 24-hour burn rate. |

---

## Locations

### List Locations

To list all available monitoring locations (hosted and private):

```
GET /api/locations
```

**Query parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `include_deprecated` | `"true"` | -- | Also include deprecated locations (for migration UI). |

**Response** `200`

```json
{
  "locations": [
    {
      "id": "loc_us_east",
      "name": "us-east",
      "region": "iad",
      "displayName": "US East (Ashburn)",
      "type": "hosted",
      "status": "active",
      "health": "active",
      "latitude": 39.0438,
      "longitude": -77.4874
    },
    {
      "id": "loc_staging_eu",
      "name": "staging-eu",
      "region": "private",
      "displayName": "Staging EU",
      "type": "private",
      "status": "active",
      "health": "active",
      "teamId": "team_xyz",
      "lastHeartbeat": "2025-01-15T10:03:45.000Z"
    }
  ]
}
```

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Location ID (use this in check `locations` arrays). |
| `name` | `string` | Short name. |
| `region` | `string` | Fly region code (hosted) or `"private"`. |
| `displayName` | `string` | Human-readable name. |
| `type` | `string` | `"hosted"` or `"private"`. |
| `status` | `string` | Lifecycle status: `"active"`, `"deprecated"`, or `"retired"`. |
| `health` | `string` | Runtime health: `"active"`, `"degraded"`, or `"offline"`. Hosted locations are always `"active"`; private locations are derived from heartbeat recency. |
| `replacedBy` | `string` | Successor location ID (only for deprecated/retired locations). |
| `teamId` | `string` | Only set for private locations. |
| `lastHeartbeat` | `string` (ISO-8601) | Only set for private locations. Most recent runner check-in. |
| `latitude` | `number` | Geographic latitude (optional). |
| `longitude` | `number` | Geographic longitude (optional). |

---

## NL Generation

`POST /api/checks/generate` has two modes, distinguished by whether the request body includes a `spec` field:

- **Playwright mode** — default. Generates a Playwright browser script from a natural language description via Claude. Used by the dashboard's NL monitor builder.
- **Spec mode** — generates HTTP checks from an OpenAPI document by reusing the same pipeline as `POST /api/specs/:id/generate-checks`. The spec can be referenced by ID, fetched from a URL, or matched by name against an existing spec.

### Generate Playwright Script

To generate a Playwright monitoring script from a natural language description:

```
POST /api/checks/generate
```

**Request body:**

| Field | Type | Required | Description |
|---|---|---|---|
| `description` | `string` | Yes | Natural language description of what to monitor (min 10 characters). |
| `targetUrl` | `string` (URL) | No | Target URL for the script. |
| `previousScript` | `string` | No | Existing script to refine. |
| `refinement` | `string` | No | Refinement instructions (used with `previousScript`). |

**Response** `200`

```json
{
  "mode": "playwright",
  "script": "// @step: Navigate to homepage\nawait page.goto('https://example.com');\n...",
  "description": "Monitor the login flow",
  "model": "claude-sonnet-4-20250514"
}
```

**Error** `503` — NL creation not configured (server missing `ANTHROPIC_API_KEY`).

### Generate Checks From OpenAPI Spec

To generate HTTP checks from an OpenAPI spec in a single round trip — useful when an integration knows the spec source but does not want to manage the spec entity separately:

```
POST /api/checks/generate
```

**Request body:**

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `spec` | `object` | Yes | — | Discriminated by `spec.source`. See variants below. |
| `locations` | `string[]` | Yes | — | Locations to assign to generated checks. Must contain at least one. |
| `frequencySeconds` | `integer` | No | `300` | Check frequency in seconds, between `10` and `86400`. |
| `validateHeaders` | `boolean` | No | `false` | Also validate response headers against the spec (toggles `validateHeaders` on the generated `openapi_conformance` assertion). |
| `confirm` | `boolean` | No | `false` | Required when the spec yields more than 50 *new* operations. |

**Spec reference variants** — `spec.source` selects how the spec is resolved:

| `source` | Required fields | Optional fields | Behavior |
|---|---|---|---|
| `"id"` | `specId` | — | Uses the spec already stored on the team. Returns `404` if not found. |
| `"url"` | `specUrl` (HTTP or HTTPS URL) | `name` (1–255 chars) | Fetches the spec via the SSRF-guarded loader (blocks private/reserved IPs and credential-bearing URLs; plain HTTP is permitted for internal networks, redirects are rejected to prevent SSRF). If a spec with the same content hash already exists for the team, it is reused; otherwise a new spec entity is inserted. When `name` is omitted it is derived from `info.title`, falling back to `api-{hostname}`, with a numeric suffix appended on collision. |
| `"name"` | `specName` | — | Looks up an existing spec by exact name (per-team unique). Returns `404` if not found. |

**Response** `200` (or `201` if at least one check was created) — same envelope as [`POST /api/specs/:id/generate-checks`](#generate-checks-from-spec). The `mode` field marks this as the spec branch and `spec.newlyCreated` indicates whether the URL variant inserted a brand-new spec row.

```json
{
  "mode": "spec",
  "spec": { "id": "spec_abc", "name": "Petstore API", "newlyCreated": true },
  "created": [
    { "id": "chk_xyz", "name": "GET /pets", "operationKey": "GET /pets" }
  ],
  "skipped": [
    { "operationKey": "GET /pets/{id}", "reason": "already_exists" }
  ],
  "summary": {
    "operationsInSpec": 12,
    "eligible": 11,
    "created": 10,
    "skipped": 1,
    "labelAttachmentFailures": 0
  }
}
```

**Errors:**

- `400` — invalid request body (`{ error: "Validation failed", details: {...} }`).
- `403` — plan limit reached. Body contains `{ error: "<plan limit message>" }`.
- `404` — spec not found (`id` and `name` variants only).
- `409` — confirmation required: `{ error: "This spec would create N new monitors. Confirm to create all of them.", requiresConfirmation: true, operationCount: N, threshold: 50 }`. Re-submit with `confirm: true`.
- `409` — (`url` variant only) a spec with the resolved name already exists with different content. Pass an explicit `name` or use the `id` variant.
- `422` — the loaded spec content failed to parse, OR the upstream URL fetch returned an error / non-OpenAPI body / exceeded the 10 MB cap. Body contains `{ error: "<details>" }`.

---

## API Specs

API specs store OpenAPI documents that HTTP checks can validate against via the `openapi_conformance` assertion. See [Assertions → openapi_conformance](/docs/reference/assertions#openapi_conformance).

### List Specs

```
GET /api/specs
```

Returns a summary list (no full contents — use the detail endpoint to fetch the parsed spec).

### Create Spec

```
POST /api/specs
```

**Request body** — discriminated by `sourceType`:

**Upload mode:**

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | Spec name (1-255 characters). |
| `sourceType` | `"upload"` | Yes | — |
| `content` | `string` | Yes | Raw OpenAPI JSON or YAML (up to 4 MB). |

**URL mode:**

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | Spec name (1-255 characters). |
| `sourceType` | `"url"` | Yes | — |
| `sourceUrl` | `string` (URL) | Yes | URL Yorker fetches the spec from. |

### Get Spec

```
GET /api/specs/:id
```

Returns the full parsed spec including `contentJson`.

### Update Spec

```
PUT /api/specs/:id
```

Accepts `name`, `content` (switches to upload mode), or `sourceUrl` (switches to url mode). You cannot provide both `content` and `sourceUrl` in the same request.

### Delete Spec

```
DELETE /api/specs/:id
```

Fails if any checks still reference the spec via an `openapi_conformance` assertion.

### Sync URL-mode Spec

```
POST /api/specs/:id/sync
```

Forces Yorker to re-fetch a `url`-mode spec immediately. Runners invalidate their cached copy on the next poll.

### Generate Checks From Spec

```
POST /api/specs/:id/generate-checks
```

Generates one HTTP check per operation in the spec. Operations that already have a corresponding check are skipped — re-running this endpoint is idempotent. Returns `201` when at least one check is created, `200` otherwise.

**Request body:**

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `locations` | `string[]` | Yes | — | Locations to assign to generated checks. Must contain at least one. |
| `frequencySeconds` | `integer` | No | `300` | Check frequency in seconds, between `10` and `86400`. |
| `validateHeaders` | `boolean` | No | `false` | Also validate response headers against the spec (toggles `validateHeaders` on the generated `openapi_conformance` assertion). |
| `confirm` | `boolean` | No | `false` | Required when the spec yields more than 50 *new* operations. Re-submit with `true` to proceed. |

**Response** `201` / `200`

```json
{
  "spec": { "id": "spec_abc", "name": "Petstore API" },
  "created": [
    { "id": "chk_xyz", "name": "GET /pets", "operationKey": "GET /pets" }
  ],
  "skipped": [
    { "operationKey": "GET /pets/{id}", "reason": "already_exists" }
  ],
  "summary": {
    "operationsInSpec": 12,
    "eligible": 11,
    "created": 10,
    "skipped": 1,
    "labelAttachmentFailures": 0
  }
}
```

| Field | Type | Description |
|---|---|---|
| `spec` | `object` | The spec the checks were generated from (`id`, `name`). |
| `created` | `array` | Checks created in this call (`id`, `name`, `operationKey`). |
| `skipped` | `array` | Operations the generator skipped (`operationKey`, `reason`). Reasons include `already_exists`, `no_responses`, `no_server_url`, `invalid_url`, `deprecated`, and others — see [`GenerateChecksSkipReason`](https://github.com/yorker-monitoring/yorker/blob/main/packages/shared/src/schemas/spec.ts). |
| `summary.operationsInSpec` | `number` | Total operations the spec exposes. |
| `summary.eligible` | `number` | Operations that passed the per-operation filters. |
| `summary.created` | `number` | New checks created in this call. |
| `summary.skipped` | `number` | Operations skipped (existing + filtered). |
| `summary.labelAttachmentFailures` | `number` | Label attachments that failed mid-loop (extremely rare). |

**Errors:**

- `400` — invalid request body (`{ error: "Validation failed", details: {...} }`).
- `403` — plan limit reached. Body contains `{ error: "<plan limit message>" }`.
- `404` — spec not found (or belongs to another team).
- `409` — confirmation required: `{ error: "This spec would create N new monitors. Confirm to create all of them.", requiresConfirmation: true, operationCount: N, threshold: 50 }`. Re-submit with `confirm: true`.
- `422` — the stored spec content failed to parse (`{ error: "<parser message>" }`).

---

## Maintenance Windows

Maintenance windows silence or pause checks during scheduled work. See [Configuration → `maintenanceWindows`](/docs/reference/configuration#maintenancewindows) for the YAML schema.

### List Maintenance Windows

```
GET /api/maintenance-windows
```

### Create Maintenance Window

```
POST /api/maintenance-windows
```

**Request body:**

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | `string` | Yes | — | Window name (1-200 characters). |
| `mode` | `"pause"` \| `"continue"` | No | `"pause"` | `pause` stops running checks; `continue` runs them but silences notifications. |
| `checkIds` | `string[]` \| `null` | Yes | — | Array of check IDs to cover. Pass `null` to apply to **all** checks for the team. If provided as an array, it must contain at least one ID. |
| `startsAt` | `string` (ISO-8601) | Yes | — | Start timestamp. |
| `endsAt` | `string` (ISO-8601) | Yes | — | End timestamp. Must be after `startsAt`. |
| `recurring` | `boolean` | No | `false` | Enable recurrence. |
| `recurrenceRule` | `string` (RRULE) | Required if `recurring` | — | RFC 5545 recurrence rule. Supported: `FREQ=DAILY`, `FREQ=WEEKLY` (optionally with `BYDAY`), `FREQ=MONTHLY` (optionally with `BYMONTHDAY`). Recurring window duration cannot exceed 31 days. |

### Get / Update / Delete Maintenance Window

```
GET    /api/maintenance-windows/:id
PUT    /api/maintenance-windows/:id
DELETE /api/maintenance-windows/:id
```
