> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prefetch.io/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /monitor

> Watch pages on a schedule and get told what changed. Free to create; each check costs 1 credit per page it fetches, plus 1 per change judged against your goal.

## Overview

A monitor re-checks pages on a schedule and compares each one with the previous check. Instead of polling a pricing page, a changelog or a policy page yourself, you get a webhook with just the diff when something changes.

<Steps>
  <Step title="Create">
    `POST /monitor` with what to watch, how often, and — optionally — a goal describing which changes matter.
  </Step>

  <Step title="Baseline">
    The first check records every page. Nothing is announced: every page is new by definition.
  </Step>

  <Step title="Check">
    Each scheduled check fetches every page again and marks it `same`, `new`, `changed`, `removed` or `error`, with a diff.
  </Step>

  <Step title="Get told">
    Changes that match your goal are POSTed to your webhook. You can also read any check with [`GET /monitor/{id}/checks/{checkId}`](/api-reference/endpoint/monitor-check).
  </Step>
</Steps>

## Example request

<CodeGroup>
  ```bash cURL theme={"theme":"css-variables"}
  curl -X POST "https://api.prefetch.io/monitor" \
    -H "X-API-Key: $PREFETCH_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Competitor pricing",
      "schedule": { "text": "every weekday at 9am", "timezone": "Europe/Athens" },
      "goal": "Alert me when a plan price or its included features change",
      "targets": [
        {
          "type": "scrape",
          "urls": ["https://example.com/pricing"],
          "change_tracking": { "modes": ["json"], "json_prompt": "Each plan: name, monthly price, headline features" }
        },
        {
          "type": "crawl",
          "url": "https://example.com/changelog",
          "crawl_options": { "limit": 25, "include_paths": ["^/changelog/"] }
        }
      ],
      "webhook": { "url": "https://you.example.org/hooks/prefetch" }
    }'
  ```

  ```javascript Node.js theme={"theme":"css-variables"}
  const res = await fetch("https://api.prefetch.io/monitor", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.PREFETCH_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Competitor pricing",
      schedule: { text: "every weekday at 9am", timezone: "Europe/Athens" },
      goal: "Alert me when a plan price or its included features change",
      targets: [
        {
          type: "scrape",
          urls: ["https://example.com/pricing"],
          change_tracking: { modes: ["json"], json_prompt: "Each plan: name, monthly price, headline features" },
        },
        {
          type: "crawl",
          url: "https://example.com/changelog",
          crawl_options: { limit: 25, include_paths: ["^/changelog/"] },
        },
      ],
      webhook: { url: "https://you.example.org/hooks/prefetch" },
    }),
  });

  const { data } = await res.json();
  console.log(data.id, data.schedule.cron, data.webhook.secret);
  ```

  ```python Python theme={"theme":"css-variables"}
  import os, requests

  r = requests.post(
      "https://api.prefetch.io/monitor",
      headers={"X-API-Key": os.environ["PREFETCH_API_KEY"]},
      json={
          "name": "Competitor pricing",
          "schedule": {"text": "every weekday at 9am", "timezone": "Europe/Athens"},
          "goal": "Alert me when a plan price or its included features change",
          "targets": [
              {
                  "type": "scrape",
                  "urls": ["https://example.com/pricing"],
                  "change_tracking": {"modes": ["json"], "json_prompt": "Each plan: name, monthly price, headline features"},
              },
              {
                  "type": "crawl",
                  "url": "https://example.com/changelog",
                  "crawl_options": {"limit": 25, "include_paths": ["^/changelog/"]},
              },
          ],
          "webhook": {"url": "https://you.example.org/hooks/prefetch"},
      },
  )
  monitor = r.json()["data"]
  print(monitor["id"], monitor["webhook"]["secret"])
  ```
</CodeGroup>

## Example response

```json theme={"theme":"css-variables"}
{
  "success": true,
  "data": {
    "id": "mon_3f9a1c2b7d4e4f6a8b0c1d2e3f4a5b6c",
    "name": "Competitor pricing",
    "status": "active",
    "schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Athens", "text": "every weekday at 9am" },
    "next_run_at": "2026-10-12T06:00:00.000Z",
    "last_run_at": null,
    "current_check_id": null,
    "goal": "Alert me when a plan price or its included features change",
    "judge_enabled": true,
    "targets": [
      { "id": "tgt_8d1e0f2a3b4c5d6e", "type": "scrape", "urls": ["https://example.com/pricing"], "...": "every option, with its effective value" },
      { "id": "tgt_1a2b3c4d5e6f7a8b", "type": "crawl", "url": "https://example.com/changelog", "...": "..." }
    ],
    "webhook": {
      "url": "https://you.example.org/hooks/prefetch",
      "headers": {},
      "metadata": {},
      "events": ["monitor.page", "monitor.check.completed"],
      "secret": "whsec_4f1c9e2a7b3d8e6f0a5c1b9d2e7f3a8c4b6d0e1f9a2c5b7d"
    },
    "retention_days": 30,
    "estimated_credits_per_check": 55,
    "estimated_credits_per_month": 1155,
    "last_check_summary": null,
    "created_at": "2026-10-09T10:00:00.000Z",
    "updated_at": "2026-10-09T10:00:00.000Z"
  },
  "meta": {
    "requestId": "b8f1d2e3-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "durationMs": 61
  }
}
```

The status is `201 Created`. Keep the `webhook.secret` — you need it to [verify each webhook](/api-reference/endpoint/monitor-webhook-page#verify-the-signature). You can read it again with [`GET /monitor/{id}`](/api-reference/endpoint/monitor-get).

## What to watch

A monitor has up to 50 **targets**, of two kinds. You can mix them.

| Target | Watches | Each check |
| - | - | - |
| `scrape` | A fixed list of up to 100 URLs | Fetches each page the way [`GET /scrape`](/api-reference/endpoint/scrape) would, rendering JavaScript and getting past bot protection |
| `crawl` | A site, or a section of one | Re-crawls it with the same options as [`POST /crawl`](/api-reference/endpoint/crawl), so pages that appear or disappear are reported too |

One check fetches at most **500 pages** across all targets — every scrape URL plus every crawl's `limit`.

<Note>
  A crawl can only report a page as `removed` when it runs out of pages to visit. A crawl that stops at its `limit` cannot tell a page that was taken down from one it never reached, so it reports no removals. Give a crawl target a `limit` comfortably above the section's size if removals matter to you.
</Note>

## Schedules

Write the schedule as a phrase or as five-field cron, in any IANA timezone:

```json theme={"theme":"css-variables"}
{ "schedule": { "text": "every weekday at 9am", "timezone": "America/New_York" } }
```

```json theme={"theme":"css-variables"}
{ "schedule": { "cron": "*/30 * * * *" } }
```

Phrases you can use include `every 30 minutes`, `every 15 minutes starting at :07`, `hourly`, `every 2 hours`, `daily`, `daily at 9am`, `daily at 5:30 PM`, `every weekday at 9am`, `weekly`, `every monday at 8:00` and `monthly`.

* Runs must be **at least 5 minutes apart** — every gap, not just the first.
* Phrases that leave the minute open (`hourly`, `daily`) get a fixed minute of their own, so monitors created at the same time do not all fire at once.
* The response always shows the cron the monitor actually runs on.
* Times follow the timezone across daylight-saving changes.

## Detecting changes

Each page of each check gets one status:

| Status | Meaning |
| - | - |
| `same` | Fetched again; nothing changed. |
| `new` | Seen for the first time — on the first check, or a page that appeared since. |
| `changed` | Fetched again; the content differs. The page carries a `diff`. |
| `removed` | A page the monitor knew answered 404 or 410, or a crawl that ran to completion no longer finds it. Reported once. |
| `error` | Could not be checked. The last good snapshot is kept, so the next good check compares against it. |

By default the page's **markdown** is compared, and `diff.text` holds a unified diff with the same hunks structured in `diff.hunks`. Whitespace and line-ending churn never counts as a change.

To watch specific facts instead of the whole text, use the **json** mode. Describe the fields, and each check extracts them and compares them field by field:

```json theme={"theme":"css-variables"}
{
  "type": "scrape",
  "urls": ["https://example.com/pricing"],
  "change_tracking": { "modes": ["json"], "json_prompt": "Each plan: name, monthly price, headline features" }
}
```

A page is then `changed` only when a tracked field changes, and `diff.fields` says exactly which one: `plans[0].price: "$19/mo" → "$24/mo"`. Pass `json_schema` instead of, or as well as, `json_prompt` to fix the shape. Use `"modes": ["json", "markdown"]` to get both diffs.

<Tip>
  Most false alarms come from page furniture: a "last updated" date, a rotating testimonial, a visitor counter. Leave them out with `scrape_options.exclude_selectors`, or switch to the json mode and track only what you care about. Images are left out of the comparison by default, because their URLs change with every deploy.
</Tip>

## Goals and judging

Add a `goal` — one plain sentence about what you want to be told — and every changed, new or removed page is judged against it:

```json theme={"theme":"css-variables"}
{ "goal": "Alert me when a plan price or its included features change" }
```

Each judged page gets `is_meaningful` and a `judgment` with a `reason`. Pages ruled **not meaningful** are still recorded on the check, but don't send a webhook. Good goals restate their scope ("the Pro plan", "the top 10", "API changes") and name exclusions only when they matter.

Judging is on whenever a goal is set; pass `judge_enabled: false` to keep a goal without judging. The first check's baseline is never judged.

## Webhooks

With a `webhook`, two events are POSTed to you:

| Event | When |
| - | - |
| [`monitor.page`](/api-reference/endpoint/monitor-webhook-page) | A page changed, appeared, was removed, or started failing — and, with a goal, the judge ruled it meaningful |
| [`monitor.check.completed`](/api-reference/endpoint/monitor-webhook-check) | A check finished, with its summary counts |

Every request is signed with the monitor's `webhook.secret`. Answer `2xx` within 10 seconds; failures are retried after 1, 5 and 15 minutes. The webhook URL must use `https://`.

```json theme={"theme":"css-variables"}
{
  "webhook": {
    "url": "https://you.example.org/hooks/prefetch",
    "headers": { "Authorization": "Bearer your-token" },
    "metadata": { "env": "production" },
    "events": ["monitor.page"]
  }
}
```

## Credits

Creating, reading and changing a monitor is free. **Each check** is billed to the API key that created the monitor:

* **1 credit per page fetched** — the [`/scrape`](/api-reference/endpoint/scrape) rate, or 4 on targets tracking `json`.
* **1 credit per page judged** against your goal — changed, new or removed pages only.

Pages that fail to load are never charged, and neither are skipped checks. `estimated_credits_per_month` is an upper bound: it assumes every page is fetched and judged on every check.

A check never spends more than the key has left. When credits run out partway, it stops and reports `partial`; when the key has none at all, the check is recorded as `skipped_no_credits` and the monitor keeps its schedule, resuming as soon as credits refill.

<Warning>
  A monitor bills on every check for as long as it is active. A crawl target with `limit: 100` on a 5-minute schedule fetches 28,800 pages a day. Start small, and pause monitors you are not using.
</Warning>

## Limits

| Limit | Value |
| - | - |
| Targets per monitor | 50 |
| URLs per scrape target | 100 |
| Pages per check | 500 |
| Monitors per API key | 50 |
| Shortest schedule | Every 5 minutes |
| Check history | `retention_days`, 30 by default, up to 365 |

<Note>
  Monitors need a direct Prefetch API key. They are not available through RapidAPI, which meters usage from a response header — and a scheduled check runs when there is no response to carry one.
</Note>

## Next

<CardGroup cols={2}>
  <Card title="GET /monitor/{id}/checks/{checkId}" icon="code-compare" href="/api-reference/endpoint/monitor-check">
    Read what a check found, page by page.
  </Card>

  <Card title="monitor.page webhook" icon="bell" href="/api-reference/endpoint/monitor-webhook-page">
    The event you receive, and how to verify it.
  </Card>

  <Card title="POST /monitor/{id}/run" icon="play" href="/api-reference/endpoint/monitor-run">
    Run a check now instead of waiting for the schedule.
  </Card>

  <Card title="PATCH /monitor/{id}" icon="pen" href="/api-reference/endpoint/monitor-update">
    Pause, resume, or change what a monitor watches.
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /monitor
openapi: 3.1.0
info:
  title: Prefetch API
  description: >-
    Extract brand identity, company data, screenshots, IAB classifications, and
    clean page content from any URL.
  version: 1.0.0
  contact:
    email: support@prefetch.io
servers:
  - url: https://api.prefetch.io
    description: Production
security:
  - apiKey: []
tags:
  - name: Endpoints
    description: Data extraction endpoints. All require authentication and consume credits.
  - name: Health
    description: Health and readiness probes. No authentication required.
paths:
  /monitor:
    post:
      tags:
        - Endpoints
      summary: Create a monitor
      description: >-
        Watches pages on a schedule and tells you what changed. Each check
        re-fetches every target — a fixed list of URLs (`scrape`) or a whole
        site (`crawl`) — and compares each page with the previous check: `same`,
        `new`, `changed`, `removed` or `error`, with a diff. Give it a `goal`
        and every change is judged against it, so only meaningful changes reach
        your webhook.


        The first check records a baseline: every page is `new`, and no
        `monitor.page` events are sent for it.


        **Credit cost: none to create.** Each check bills 1 credit per page it
        fetches (the `/scrape` rate, 4 on targets tracking `json`), plus 1 per
        changed page judged against the goal.
      operationId: createMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MonitorRequest'
      responses:
        '201':
          description: Monitor created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    $ref: '#/components/schemas/MonitorData'
                  meta:
                    $ref: '#/components/schemas/Meta'
                required:
                  - success
                  - data
                  - meta
              example:
                success: true
                data:
                  id: mon_3f9a1c2b7d4e4f6a8b0c1d2e3f4a5b6c
                  name: Competitor pricing
                  status: active
                  schedule:
                    cron: 0 9 * * 1-5
                    timezone: Europe/Athens
                    text: every weekday at 9am
                  next_run_at: '2026-10-12T06:00:00.000Z'
                  last_run_at: null
                  current_check_id: null
                  goal: Alert me when a plan price or its included features change
                  judge_enabled: true
                  targets:
                    - id: tgt_8d1e0f2a3b4c5d6e
                      type: scrape
                      urls:
                        - https://example.com/pricing
                      scrape_options:
                        only_main_content: true
                        include_links: true
                        include_images: false
                        include_selectors: []
                        exclude_selectors: []
                      change_tracking:
                        modes:
                          - json
                        json_prompt: 'Each plan: name, monthly price, headline features'
                    - id: tgt_1a2b3c4d5e6f7a8b
                      type: crawl
                      url: https://example.com/changelog
                      crawl_options:
                        limit: 25
                        max_depth: 2
                        include_paths:
                          - ^/changelog/
                        exclude_paths: []
                        allow_subdomains: false
                        ignore_query_params: true
                        respect_robots_txt: true
                        use_sitemap: true
                        delay_ms: 0
                        concurrency: 2
                      scrape_options:
                        only_main_content: true
                        include_links: true
                        include_images: false
                        include_selectors: []
                        exclude_selectors: []
                      change_tracking:
                        modes:
                          - markdown
                  webhook:
                    url: https://you.example.org/hooks/prefetch
                    headers: {}
                    metadata:
                      env: production
                    events:
                      - monitor.page
                      - monitor.check.completed
                    secret: whsec_4f1c9e2a7b3d8e6f0a5c1b9d2e7f3a8c4b6d0e1f9a2c5b7d
                  retention_days: 30
                  estimated_credits_per_check: 55
                  estimated_credits_per_month: 1155
                  last_check_summary: null
                  created_at: '2026-10-09T10:00:00.000Z'
                  updated_at: '2026-10-09T10:00:00.000Z'
                meta:
                  requestId: b8f1d2e3-4a5b-4c6d-8e7f-9a0b1c2d3e4f
                  durationMs: 61
        '400':
          description: Invalid or missing parameters. The message names the field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            Revoked key, expired key, credit limit, blocklisted URL, monitor
            limit reached, or a RapidAPI subscription — monitors need a direct
            Prefetch API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Monitoring is temporarily unavailable. Retry shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    MonitorRequest:
      type: object
      required:
        - name
        - schedule
        - targets
      properties:
        name:
          type: string
          maxLength: 256
          example: Competitor pricing
        schedule:
          $ref: '#/components/schemas/MonitorSchedule'
        goal:
          type: string
          maxLength: 2000
          nullable: true
          description: >-
            What you want to be told about, in one plain sentence. With a goal,
            every changed page is judged against it, and only meaningful changes
            send a `monitor.page` webhook. 1 credit per page judged.
          example: Alert me when a plan price or its included features change
        judge_enabled:
          type: boolean
          description: 'Judge changes against the goal. Default: `true` when a goal is set.'
        targets:
          type: array
          items:
            $ref: '#/components/schemas/MonitorTarget'
          minItems: 1
          maxItems: 50
          description: What to watch. At most 500 pages per check across all targets.
        webhook:
          $ref: '#/components/schemas/MonitorWebhookRequest'
        retention_days:
          type: integer
          minimum: 1
          maximum: 365
          default: 30
          description: How long check history is kept.
    MonitorData:
      type: object
      properties:
        id:
          type: string
          example: mon_3f9a1c2b7d4e4f6a8b0c1d2e3f4a5b6c
        name:
          type: string
        status:
          type: string
          enum:
            - active
            - paused
        schedule:
          type: object
          properties:
            cron:
              type: string
              description: >-
                The schedule the monitor runs on — always cron, whatever you
                submitted.
              example: 0 9 * * 1-5
            timezone:
              type: string
              example: Europe/Athens
            text:
              type: string
              nullable: true
              description: The phrase it was created from, if any.
              example: every weekday at 9am
        next_run_at:
          type: string
          format: date-time
          nullable: true
          description: '`null` while paused.'
        last_run_at:
          type: string
          format: date-time
          nullable: true
        current_check_id:
          type: string
          nullable: true
          description: The check running right now, if any.
        goal:
          type: string
          nullable: true
        judge_enabled:
          type: boolean
        targets:
          type: array
          items:
            $ref: '#/components/schemas/MonitorTarget'
          description: >-
            Normalised: every target has an id and every option its effective
            value.
        webhook:
          $ref: '#/components/schemas/MonitorWebhook'
        retention_days:
          type: integer
        estimated_credits_per_check:
          type: integer
          description: >-
            Upper bound: every page fetched, and judged when a goal is set. A
            real check costs less — only fetched pages are billed, and only
            changed ones judged.
        estimated_credits_per_month:
          type: integer
          description: '`estimated_credits_per_check` times the runs in an average month.'
        last_check_summary:
          allOf:
            - $ref: '#/components/schemas/MonitorSummary'
            - type: object
              properties:
                check_id:
                  type: string
                status:
                  type: string
                  enum:
                    - queued
                    - running
                    - completed
                    - failed
                    - partial
                    - skipped_overlap
                    - skipped_no_credits
                finished_at:
                  type: string
                  format: date-time
          nullable: true
          description: Counts from the most recent check, or `null` before the first one.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          description: When the settings last changed. Checks do not touch it.
    Meta:
      type: object
      properties:
        requestId:
          type: string
          format: uuid
          description: Unique identifier for this request.
          example: a3f2c1d4-7b6e-4f2a-9c1d-8e3f2a1b4c5d
        durationMs:
          type: integer
          nullable: true
          description: Time taken to process the request in milliseconds.
          example: 1842
      required:
        - requestId
        - durationMs
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
          description: >-
            Always false. Absent, like `meta`, on authentication and rate-limit
            errors, which are answered before a request is processed.
        error:
          type: string
          description: >-
            Human-readable error message. The wording can change: branch on
            `error_code`.
          example: Credit limit exceeded
        error_code:
          type: string
          enum:
            - INVALID_REQUEST
            - PAYLOAD_TOO_LARGE
            - NOT_FOUND
            - CONFLICT
            - UNAUTHORIZED
            - FORBIDDEN
            - CREDITS_EXHAUSTED
            - RATE_LIMITED
            - CONCURRENCY_LIMITED
            - URL_BLOCKED
            - URL_NOT_PERMITTED
            - DNS_RESOLUTION_FAILED
            - TARGET_BLOCKED
            - TARGET_CONNECTION_FAILED
            - TARGET_TIMEOUT
            - TARGET_SSL_ERROR
            - TARGET_SERVER_ERROR
            - TARGET_CLIENT_ERROR
            - TARGET_NOT_A_PAGE
            - TARGET_UNLOADABLE
            - RESEARCH_INCONCLUSIVE
            - TIMEOUT
            - SERVICE_BUSY
            - SERVICE_UNAVAILABLE
            - INTERNAL_ERROR
          description: >-
            What went wrong, for machines. Stable: a code keeps its meaning once
            published. See [Error handling](/concepts/errors).
          example: CREDITS_EXHAUSTED
        retryable:
          type: boolean
          description: >-
            Whether the same request can succeed if sent again after a short
            wait (honour `Retry-After` when present). False for problems with
            the request or the target.
          example: false
        retryAfterSeconds:
          type: integer
          description: 'On `RATE_LIMITED`: seconds until a request will be accepted.'
        limit:
          type: integer
          description: >-
            On `CONCURRENCY_LIMITED`: how many requests this key may have in
            flight.
        meta:
          $ref: '#/components/schemas/Meta'
      required:
        - error
        - error_code
        - retryable
    MonitorSchedule:
      type: object
      description: >-
        Exactly one of `cron` or `text`. A bare string is accepted too, and read
        as cron when it has five cron fields.
      properties:
        cron:
          type: string
          description: Five-field cron expression. Runs must be at least 5 minutes apart.
          example: '*/30 * * * *'
        text:
          type: string
          description: >-
            A plain-English schedule, converted to cron: "every 30 minutes",
            "every 15 minutes starting at :07", "hourly", "every 2 hours",
            "daily", "daily at 9am", "daily at 5:30 PM", "every weekday at 9am",
            "weekly", "every monday at 8:00", "monthly".
          example: every weekday at 9am
        timezone:
          type: string
          default: UTC
          description: IANA timezone the schedule runs in.
          example: Europe/Athens
    MonitorTarget:
      oneOf:
        - $ref: '#/components/schemas/MonitorScrapeTarget'
        - $ref: '#/components/schemas/MonitorCrawlTarget'
      discriminator:
        propertyName: type
        mapping:
          scrape: '#/components/schemas/MonitorScrapeTarget'
          crawl: '#/components/schemas/MonitorCrawlTarget'
    MonitorWebhookRequest:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          description: HTTPS endpoint that receives the events.
          example: https://you.example.org/hooks/prefetch
        headers:
          type: object
          additionalProperties:
            type: string
          description: >-
            Extra headers sent with every event, at most 20. `Host`,
            `Content-Type` and `X-Prefetch-*` are reserved.
          example:
            Authorization: Bearer your-token
        metadata:
          type: object
          description: Echoed back in every event, up to 4 KB.
          example:
            env: production
        events:
          type: array
          items:
            type: string
            enum:
              - monitor.page
              - monitor.check.completed
          description: 'Events to receive. Default: both.'
        secret:
          type: string
          description: >-
            Signing secret, 16–128 printable characters. Generated when omitted,
            and kept across updates unless you replace it.
        rotate_secret:
          type: boolean
          description: 'Update only: generate a new signing secret.'
    MonitorWebhook:
      type: object
      nullable: true
      properties:
        url:
          type: string
        headers:
          type: object
          additionalProperties:
            type: string
        metadata:
          type: object
        events:
          type: array
          items:
            type: string
            enum:
              - monitor.page
              - monitor.check.completed
        secret:
          type: string
          description: Verifies the `X-Prefetch-Signature` header on every event.
          example: whsec_4f1c9e2a7b3d8e6f0a5c1b9d2e7f3a8c4b6d0e1f9a2c5b7d
    MonitorSummary:
      type: object
      properties:
        total_pages:
          type: integer
        same:
          type: integer
        new:
          type: integer
        changed:
          type: integer
        removed:
          type: integer
        error:
          type: integer
    MonitorScrapeTarget:
      type: object
      title: Scrape target
      description: >-
        Watches a fixed list of pages, each fetched the way `GET /scrape` would
        fetch it.
      required:
        - type
        - urls
      properties:
        id:
          type: string
          description: >-
            Stable target id. Generated when omitted; on update, a target
            watching the same URLs keeps its id — and its snapshots —
            automatically.
          example: tgt_8d1e0f2a3b4c5d6e
        type:
          type: string
          enum:
            - scrape
        urls:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 100
          description: The pages to watch.
          example:
            - https://example.com/pricing
        scrape_options:
          $ref: '#/components/schemas/MonitorScrapeOptions'
        change_tracking:
          $ref: '#/components/schemas/MonitorChangeTracking'
    MonitorCrawlTarget:
      type: object
      title: Crawl target
      description: >-
        Re-crawls a site on every check, so pages that appear or disappear are
        reported as well as pages that change.
      required:
        - type
        - url
      properties:
        id:
          type: string
          description: Stable target id. Generated when omitted.
          example: tgt_1a2b3c4d5e6f7a8b
        type:
          type: string
          enum:
            - crawl
        url:
          type: string
          description: Where each crawl starts.
          example: https://example.com/changelog
        crawl_options:
          $ref: '#/components/schemas/MonitorCrawlOptions'
        scrape_options:
          $ref: '#/components/schemas/MonitorScrapeOptions'
        change_tracking:
          $ref: '#/components/schemas/MonitorChangeTracking'
    MonitorScrapeOptions:
      type: object
      description: >-
        How each page is rendered before it is compared — the content-shaping
        options of `GET /scrape`.
      properties:
        only_main_content:
          type: boolean
          default: true
          description: >-
            Compare the main content only, ignoring navigation, sidebars and
            footers.
        include_links:
          type: boolean
          default: true
          description: Keep link URLs in the compared markdown.
        include_images:
          type: boolean
          default: false
          description: >-
            Keep images in the compared markdown. Off by default: image URLs
            carry cache-busting hashes that change on every deploy.
        include_selectors:
          type: array
          items:
            type: string
          description: CSS selectors to compare instead of the detected main content.
          example:
            - '#pricing'
        exclude_selectors:
          type: array
          items:
            type: string
          description: >-
            CSS selectors to leave out of the comparison — the way to silence a
            timestamp or a rotating banner.
          example:
            - .last-updated
            - .testimonial
    MonitorChangeTracking:
      type: object
      properties:
        modes:
          type: array
          items:
            type: string
            enum:
              - markdown
              - json
          default:
            - markdown
          description: >-
            `markdown` diffs the page text line by line. `json` extracts the
            fields described by `json_prompt` or `json_schema` on every check
            and diffs them field by field, so a page is `changed` only when a
            tracked field changes. Both together report `changed` when either
            surface changed.
        json_prompt:
          type: string
          description: 'For the `json` mode: the fields to track, in plain English.'
          example: 'Each plan: name, monthly price, headline features'
        json_schema:
          type: object
          description: 'For the `json` mode: a JSON Schema for the extraction.'
    MonitorCrawlOptions:
      type: object
      description: The crawl options `POST /crawl` takes, without `allow_external_links`.
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 500
          default: 25
          description: Most pages each check crawls. Every page is billed on every check.
        max_depth:
          type: integer
          minimum: 0
          maximum: 5
          default: 2
          description: How many links deep to follow from the start URL.
        include_paths:
          type: array
          items:
            type: string
          description: >-
            Regular expressions matched against a URL's path and query. A URL
            must match at least one to be monitored.
          example:
            - ^/changelog/
        exclude_paths:
          type: array
          items:
            type: string
          description: Regular expressions. A matching URL is skipped.
        allow_subdomains:
          type: boolean
          default: false
          description: Follow links onto subdomains of the start URL.
        ignore_query_params:
          type: boolean
          default: true
          description: Treat URLs that differ only by query string as one page.
        respect_robots_txt:
          type: boolean
          default: true
          description: Obey the rules in `robots.txt`.
        use_sitemap:
          type: boolean
          default: true
          description: Seed each crawl from the site's sitemap as well as from links.
        delay_ms:
          type: integer
          minimum: 0
          maximum: 30000
          default: 0
          description: Politeness delay between pages.
        concurrency:
          type: integer
          minimum: 1
          maximum: 5
          default: 2
          description: Pages fetched in parallel.
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your Prefetch API key. Obtain one from the dashboard.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.