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

# Monitors

> Monitor pages on a schedule and send change alerts

Through the Olostep `/v1/monitors` endpoint you can create persistent monitors that run on a fixed schedule, detect page changes, and notify you by email, webhook, or SMS.

* Create a monitor from a natural language `query`
* Run checks using natural-language schedules
* Deliver alerts via `email`, `webhook_url`, or `phone_number` (SMS)
* List, inspect, update, pause, resume, and delete monitors
* Read monitor snapshot events from private snapshots

By default, every monitor run captures a **full snapshot** of the monitored page — a complete picture of its current state at that moment. If you want the monitor to surface only what's new or changed between runs (deltas) instead of the full state, you must express that intent directly in the `query`.

## Installation

<CodeGroup>
  ```python Python theme={null}
  # pip install requests

  import requests
  ```

  ```js Node theme={null}
  // npm install node-fetch

  // ESM
  import fetch from 'node-fetch'

  // CommonJS
  const fetch = require('node-fetch')
  ```

  ```bash cURL theme={null}
  # macOS: builtin curl is fine
  ```
</CodeGroup>

## Create a monitor

Create a monitor with `POST /v1/monitors`. The endpoint validates your input, provisions the monitor, creates its internal schedule, and returns an active monitor object.

* `query` is required. If your `query` includes a URL, the monitor will focus on that input URL. By default, each run takes a full snapshot of what is being monitored — a picture of the current state of the page. If you want the monitor to track only what's new or changed between runs (for example, only new blog posts, or only price drops), you must specify that explicitly in the `query`; otherwise the full snapshot is captured every run.
* `frequency` accepts natural-language schedule instructions and defaults to UTC when no timezone is provided.
* Exactly one notification target is required: `email`, `webhook_url`, or `phone_number` (SMS).
* Use `output_schema` when you want structured extraction results. (optional)

### Example request

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"

  payload = {
      "query": "Track changes in https://example.com/products/widget-pro pricing and stock information",
      "frequency": "every day at 9am America/Los_Angeles",
      "email": "alerts@example.com",
      "output_schema": {
          "type": "object",
          "properties": {
              "title": {"type": "string"},
              "published_at": {"type": "string"}
          },
          "required": ["title"]
      },
      "metadata": {
          "product_id": "widget-pro",
          "team": "growth"
      }
  }

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  response = requests.post(f"{API_URL}/monitors", headers=headers, json=payload)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'

  const res = await fetch(`${API_URL}/monitors`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: 'Track changes in https://example.com/products/widget-pro pricing and stock information',
      frequency: 'every day at 9am America/Los_Angeles',
      email: 'alerts@example.com',
      output_schema: {
        type: 'object',
        properties: {
          title: { type: 'string' },
          published_at: { type: 'string' }
        },
        required: ['title']
      },
      metadata: {
        product_id: 'widget-pro',
        team: 'growth'
      }
    })
  })

  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Track changes in https://example.com/products/widget-pro pricing and stock information",
      "frequency": "every day at 9am America/Los_Angeles",
      "email": "alerts@example.com",
      "output_schema": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "published_at": { "type": "string" }
        },
        "required": ["title"]
      },
      "metadata": {
        "product_id": "widget-pro",
        "team": "growth"
      }
    }'
  ```
</CodeGroup>

### Response

Successful monitor creation returns HTTP `202` with a monitor object:

```json theme={null}
{
  "id": "monitor_n8q2x4m1ak",
  "object": "monitor",
  "created_at": 1777983215,
  "updated_at": 1777983222,
  "fda_id": "fda_n8q2x4m1ak",
  "team_id": "team_n8q2x4m1ak",
  "status": "active",
  "query": "Track changes in https://example.com/products/widget-pro pricing and stock information",
  "frequency": "every day at 9am America/Los_Angeles",
  "cron_expression": "0 0 * * ? *",
  "notification_channel": "email",
  "notification_target": "alerts@example.com",
  "output_schema": {
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "published_at": { "type": "string" }
    },
    "required": ["title"]
  },
  "metadata": {
    "product_id": "widget-pro",
    "team": "growth"
  }
}
```

### Structured monitor output

Set `output_schema` in the create request when you want monitor extraction results to follow a specific JSON structure. The schema must be valid JSON Schema.

## Notification channels

Monitors support one channel per monitor:

* `email`: set an `email` field
* `webhook`: set a `webhook_url` field
* `sms`: set a `phone_number` field (E.164 format, for example `+14155552671`)

You can set exactly one of these fields per request.

### Webhook example

```json theme={null}
{
  "query": "Watch for changes in https://example.com/terms legal terms",
  "frequency": "everyday at 10:00 am",
  "webhook_url": "https://hooks.example.com/olostep-monitor"
}
```

### SMS example

```json theme={null}
{
  "query": "Watch for changes in https://example.com/terms legal terms",
  "frequency": "everyday at 10:00 am",
  "phone_number": "+14155552671"
}
```

## Frequencies

Set `frequency` in natural language, for example:

* `every day at 9am America/Los_Angeles`
* `every 5 minutes`
* `every 6 hours`

The API interprets this schedule text and derives the cron expression automatically. If no timezone is provided, UTC is used by default.

## List monitors

Retrieve all monitors for your team with `GET /v1/monitors`.

By default, deleted monitors are filtered out. Use `?include_deleted=true` to include them.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.get(f"{API_URL}/monitors", headers=headers)
  result = response.json()
  print(f"Total monitors: {result['count']}")
  print(json.dumps(result, indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'

  const res = await fetch(`${API_URL}/monitors`, {
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  const result = await res.json()

  console.log(`Total monitors: ${result.count}`)
  console.log(result.monitors)
  ```

  ```bash cURL theme={null}
  curl -s -X GET "https://api.olostep.com/v1/monitors" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

### Response shape

```json theme={null}
{
  "monitors": [
    {
      "id": "monitor_a3yk6z49p8",
      "object": "monitor",
      "created_at": 1777983215,
      "cron_expression": "13 13 * * ? *",
      "fda_id": "fda_a3yk6z49p8",
      "frequency": "daily",
      "metadata": {},
      "notification_channel": "email",
      "notification_target": "example@olostep.com",
      "query": "Gather the new blogposts from example website and send me updates with new posts.",
      "status": "active",
      "team_id": "team_a3yk6z49p8F",
      "updated_at": 1777983222
    }
  ],
  "count": 1
}
```

## Get a monitor

Retrieve a single monitor with `GET /v1/monitors/:monitor_id`.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.get(f"{API_URL}/monitors/{MONITOR_ID}", headers=headers)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}`, {
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X GET "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

### Response shape

```json theme={null}
{
  "id": "monitor_a3yk6z49p8",
  "object": "monitor",
  "created_at": 1777983215,
  "cron_expression": "13 13 * * ? *",
  "fda_id": "fda_a3yk6z49p8",
  "frequency": "daily",
  "metadata": {},
  "notification_channel": "email",
  "notification_target": "example@olostep.com",
  "query": "Gather the new blogposts from example website and send me updates with new posts.",
  "status": "active",
  "team_id": "team_a3yk6z49p8",
  "updated_at": 1777983222
}
```

## List monitor events

Use `GET /v1/monitors/:monitor_id/events` to list snapshot events for a monitor.

This endpoint supports pagination:

* `limit` (default `25`, max `100`)
* `cursor` (opaque pagination token from a previous response)

Events are returned newest-first. Each item includes a short-lived pre-signed `snapshot_url` so you can fetch private snapshot content securely.

### Example

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.get(
      f"{API_URL}/monitors/{MONITOR_ID}/events?limit=10",
      headers=headers
  )
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}/events?limit=10`, {
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X GET "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak/events?limit=10" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

### Response shape

```json theme={null}
{
  "data": [
    {
      "id": "run_v7k2p9m3",
      "run_id": "run_v7k2p9m3",
      "created": 1777960800,
      "changed": true,
      "summary": "Price changed from $49 to $45 and stock moved to low availability.",
      "snapshot_url": "https://olostep-monitor-snapshot.s3.amazonaws.com/private/key?...signature..."
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

If `cursor` is malformed, the endpoint returns:

```json theme={null}
{
  "error": "Invalid cursor."
}
```

## Update a monitor

Update a monitor with `POST /v1/monitors/:monitor_id`.

Supported updates:

* `metadata` (merged with existing metadata; pass empty string values to delete keys)
* `frequency` (natural-language schedule text, for example `every weekday at 08:30 America/New_York`)

If you update `frequency`, the API recreates the monitor schedule internally. If no timezone is included in the text, UTC is used.

### Example request

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  payload = {
      "frequency": "every 2 hours",
      "metadata": {
          "owner": "ops-team",
          "deprecated_field": ""
      }
  }

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  response = requests.post(
      f"{API_URL}/monitors/{MONITOR_ID}",
      headers=headers,
      json=payload
  )
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      frequency: 'every 2 hours',
      metadata: {
        owner: 'ops-team',
        deprecated_field: ''
      }
    })
  })
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "frequency": "every 2 hours",
      "metadata": {
        "owner": "ops-team",
        "deprecated_field": ""
      }
    }'
  ```
</CodeGroup>

## Pause a monitor

Pause a monitor with `POST /v1/monitors/:monitor_id/pause`.

Pausing disables the monitor's underlying schedule so no further runs are triggered, and sets the monitor's `status` to `paused`. The monitor row, its configuration, and its past snapshots are kept — only future scheduled executions stop. You can resume the monitor later with `POST /v1/monitors/:monitor_id/resume`.

Only monitors with `status` of `active` can be paused. The request body is empty.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.post(f"{API_URL}/monitors/{MONITOR_ID}/pause", headers=headers)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}/pause`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak/pause" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

### Response

On success, returns `200` with the monitor and `status` set to `paused`:

```json theme={null}
{
  "id": "monitor_n8q2x4m1ak",
  "object": "monitor",
  "status": "paused",
  "updated_at": 1777986822,
  "query": "Track changes in https://example.com/products/widget-pro pricing and stock information",
  "frequency": "every day at 9am America/Los_Angeles",
  "notification_channel": "email",
  "notification_target": "alerts@example.com"
}
```

Possible error responses:

* `400` — `monitor_id` malformed, monitor already `deleted`, monitor not currently `active`, or monitor has no underlying schedule.
* `404` — Monitor not found.
* `409` — The monitor changed state during the pause attempt (no longer `active`), or the underlying schedule could not be found.

## Resume a monitor

Resume a paused monitor with `POST /v1/monitors/:monitor_id/resume`.

Resuming re-enables the monitor's underlying schedule and sets the monitor's `status` back to `active`. Scheduled runs continue on the existing `frequency` — no re-creation of the schedule is needed.

Only monitors with `status` of `paused` can be resumed. The request body is empty.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.post(f"{API_URL}/monitors/{MONITOR_ID}/resume", headers=headers)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}/resume`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak/resume" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

### Response

On success, returns `200` with the monitor and `status` set back to `active`:

```json theme={null}
{
  "id": "monitor_n8q2x4m1ak",
  "object": "monitor",
  "status": "active",
  "updated_at": 1777990422,
  "query": "Track changes in https://example.com/products/widget-pro pricing and stock information",
  "frequency": "every day at 9am America/Los_Angeles",
  "notification_channel": "email",
  "notification_target": "alerts@example.com"
}
```

Possible error responses:

* `400` — `monitor_id` malformed, monitor already `deleted`, monitor not currently `paused`, or monitor has no underlying schedule.
* `404` — Monitor not found.
* `409` — The monitor changed state during the resume attempt (no longer `paused`), or the underlying schedule could not be found.

## Delete a monitor

Delete a monitor with `DELETE /v1/monitors/:monitor_id`.

Deletion is soft for the monitor row (`status` becomes `deleted`) and also removes internal scheduling/shadow-agent resources tied to that monitor.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"
  MONITOR_ID = "monitor_n8q2x4m1ak"

  headers = {
      "Authorization": f"Bearer {API_KEY}"
  }

  response = requests.delete(f"{API_URL}/monitors/{MONITOR_ID}", headers=headers)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'
  const monitorId = 'monitor_n8q2x4m1ak'

  const res = await fetch(`${API_URL}/monitors/${monitorId}`, {
    method: 'DELETE',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>' }
  })
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X DELETE "https://api.olostep.com/v1/monitors/monitor_n8q2x4m1ak" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY"
  ```
</CodeGroup>

## Example use cases

Below are practical example requests for the `/v1/monitors` endpoint.

### Track uptime issues in the OpenAI API

Run an hourly check that watches the OpenAI API status and emails you when uptime issues are detected.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"

  payload = {
      "query": "Track uptime issues in OpenAI API and email me at info@olostep.com",
      "frequency": "every hour",
      "email": "alerts@example.com"
  }

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  response = requests.post(f"{API_URL}/monitors", headers=headers, json=payload)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'

  const res = await fetch(`${API_URL}/monitors`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: 'Track uptime issues in OpenAI API and email me at info@olostep.com',
      frequency: 'every hour',
      email: 'alerts@example.com'
    })
  })

  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Track uptime issues in OpenAI API and email me at info@olostep.com",
      "frequency": "every hour",
      "email": "alerts@example.com"
    }'
  ```
</CodeGroup>

### Track product reviews — full snapshot per run

Monitor a product page and deliver every review to a webhook on every run. Because the `query` does not ask to dedupe against previous runs, each run captures a **full snapshot** of the reviews currently on the page (the default behavior). Use this pattern when you want the complete state every time, for example to overwrite a downstream table.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"

  payload = {
      "query": "Monitor the reviews of this product https://www.walmart.com/reviews/product/957245477?sort=submission-desc and notify me by webhook. For each review extract the publication date, author, rating, title, and text content.",
      "frequency": "everyday at 9am",
      "webhook_url": "https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86",
      "output_schema": {
          "type": "object",
          "properties": {
              "reviews": {
                  "type": "array",
                  "items": {
                      "type": "object",
                      "properties": {
                          "publication_date": {"type": "string"},
                          "author": {"type": "string"},
                          "rating": {"type": "number"},
                          "title": {"type": "string"},
                          "text_content": {"type": "string"}
                      }
                  },
                  "required": [
                      "publication_date",
                      "author",
                      "rating",
                      "title",
                      "text_content"
                  ]
              }
          }
      }
  }

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  response = requests.post(f"{API_URL}/monitors", headers=headers, json=payload)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'

  const res = await fetch(`${API_URL}/monitors`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: 'Monitor the reviews of this product https://www.walmart.com/reviews/product/957245477?sort=submission-desc and notify me by webhook. For each review extract the publication date, author, rating, title, and text content.',
      frequency: 'everyday at 9am',
      webhook_url: 'https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86',
      output_schema: {
        type: 'object',
        properties: {
          reviews: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                publication_date: { type: 'string' },
                author: { type: 'string' },
                rating: { type: 'number' },
                title: { type: 'string' },
                text_content: { type: 'string' }
              }
            },
            required: [
              'publication_date',
              'author',
              'rating',
              'title',
              'text_content'
            ]
          }
        }
      }
    })
  })

  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Monitor the reviews of this product https://www.walmart.com/reviews/product/957245477?sort=submission-desc and notify me by webhook. For each review extract the publication date, author, rating, title, and text content.",
      "frequency": "everyday at 9am",
      "webhook_url": "https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86",
      "output_schema": {
        "type": "object",
        "properties": {
          "reviews": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "publication_date": { "type": "string" },
                "author": { "type": "string" },
                "rating": { "type": "number" },
                "title": { "type": "string" },
                "text_content": { "type": "string" }
              }
            },
            "required": [
              "publication_date",
              "author",
              "rating",
              "title",
              "text_content"
            ]
          }
        }
      }
    }'
  ```
</CodeGroup>

### Track only new rental listings — delta from previous run

Monitor a paginated rentals search and run a multi-step pipeline that surfaces **only newly appeared listings** versus the previous run, then enriches and scores each one before notifying the webhook. The full workflow — paginate, detect new listings against the previous checkpoint, enrich each with crime signals for its location, assign a 1-10 score from price/location/safety, persist state, and notify — is expressed directly as ordered steps in the `query`. Because the natural-language steps describe both the extraction and the delta logic, no `output_schema` is required: the query alone drives what gets fetched, compared, enriched, scored, and delivered.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import json

  API_KEY = "<YOUR_API_KEY>"
  API_URL = "https://api.olostep.com/v1"

  payload = {
      "query": "Monitor the first 3 pages of https://streeteasy.com/for-rent/new-jersey/price:1000-1750?sort_by=listed_desc, detect only newly appeared listings versus the previous checkpoint, enrich each new listing with crime signals for its location, assign a 1-10 overall score based on price/location/safety, persist state, and notify via webhook.",
      "frequency": "every hour",
      "webhook_url": "https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86"
  }

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  response = requests.post(f"{API_URL}/monitors", headers=headers, json=payload)
  print(response.status_code)
  print(json.dumps(response.json(), indent=2))
  ```

  ```js Node theme={null}
  const API_URL = 'https://api.olostep.com/v1'

  const res = await fetch(`${API_URL}/monitors`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: 'Monitor the first 3 pages of https://streeteasy.com/for-rent/new-jersey/price:1000-1750?sort_by=listed_desc, detect only newly appeared listings versus the previous checkpoint, enrich each new listing with crime signals for its location, assign a 1-10 overall score based on price/location/safety, persist state, and notify via webhook.',
      frequency: 'every hour',
      webhook_url: 'https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86'
    })
  })

  console.log(res.status)
  console.log(await res.json())
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.olostep.com/v1/monitors" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Monitor the first 3 pages of https://streeteasy.com/for-rent/new-jersey/price:1000-1750?sort_by=listed_desc, detect only newly appeared listings versus the previous checkpoint, enrich each new listing with crime signals for its location, assign a 1-10 overall score based on price/location/safety, persist state, and notify via webhook.",
      "frequency": "every hour",
      "webhook_url": "https://webhook.site/3fb2b00b-d66f-4c46-b778-f0c7b93d4d86"
    }'
  ```
</CodeGroup>

## Common validation errors

The monitor endpoints return clear validation errors for common invalid requests:

* Missing `query`
* Missing `frequency`, or invalid schedule instruction
* Missing all of `email`, `webhook_url`, and `phone_number`
* Providing more than one of `email`, `webhook_url`, and `phone_number` in the same request
* Invalid email format
* Invalid webhook URL (must be `http` or `https`)
* Invalid phone number format (must be E.164, for example `+14155552671`)
* Invalid `output_schema` (must be valid JSON Schema)
* Invalid `monitor_id` format

Example error:

```json theme={null}
{
  "error": "Invalid frequency. Provide a natural-language schedule instruction."
}
```
