Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 73 additions & 3 deletions api-reference/v2-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5379,6 +5379,75 @@
"type",
"url"
]
},
{
"type": "object",
"title": "Search target",
"description": "Runs web search queries on each check and alerts on new results that match the monitor's goal. Requires a non-empty top-level `goal` on the monitor unless `judgeEnabled` is `false`.",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Optional stable ID for this target. Generated if omitted."
},
"type": {
"type": "string",
"enum": [
"search"
]
},
"queries": {
"type": "array",
"minItems": 1,
"maxItems": 12,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"description": "Search queries to run on each check (1-12)."
},
"searchWindow": {
"type": "string",
"enum": [
"5m",
"15m",
"1h",
"6h",
"24h",
"7d"
],
"default": "24h",
"description": "Recency filter — only consider results published within this window."
},
"maxResults": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 10,
"description": "Total results to evaluate per check, merged and deduped across all queries (a combined cap, not per-query)."
},
"includeDomains": {
"type": "array",
"maxItems": 50,
"items": {
"type": "string"
},
"description": "Optional. Restrict results to these domains."
},
"excludeDomains": {
"type": "array",
"maxItems": 50,
"items": {
"type": "string"
},
"description": "Optional. Drop results from these domains."
}
},
"required": [
"type",
"queries"
]
}
]
},
Expand Down Expand Up @@ -5416,7 +5485,7 @@
"type": "string",
"maxLength": 2000,
"nullable": true,
"description": "Plain-language goal used to judge whether changed pages are meaningful. If provided and `judgeEnabled` is omitted, judging is enabled automatically."
"description": "Plain-language goal used to judge whether changed pages are meaningful. If provided and `judgeEnabled` is omitted, judging is enabled automatically. Required (non-empty) when any target is a `search` target, unless `judgeEnabled` is `false`."
},
"judgeEnabled": {
"type": "boolean",
Expand Down Expand Up @@ -5463,7 +5532,7 @@
"type": "string",
"maxLength": 2000,
"nullable": true,
"description": "Plain-language goal used to judge whether changed pages are meaningful. If provided and `judgeEnabled` is omitted, judging is enabled automatically."
"description": "Plain-language goal used to judge whether changed pages are meaningful. If provided and `judgeEnabled` is omitted, judging is enabled automatically. Required (non-empty) when any target is a `search` target, unless `judgeEnabled` is `false`."
},
"judgeEnabled": {
"type": "boolean",
Expand Down Expand Up @@ -5715,7 +5784,8 @@
},
"metadata": {
"type": "object",
"nullable": true
"nullable": true,
"description": "Extra per-page metadata. For search monitors this includes `searchStatus`, the finer-grained search disposition behind the top-level `status`: `alert` (maps to `new`), `already_seen`, `watching`, `ignored` (all map to `same`), or `skipped` (maps to `error`)."
},
"judgment": {
"$ref": "#/components/schemas/MonitorPageJudgment"
Expand Down
201 changes: 197 additions & 4 deletions features/monitoring.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import DiffMarkdown from "/snippets/v2/monitor/diff/markdown.mdx";
import DiffJson from "/snippets/v2/monitor/diff/json.mdx";
import DiffMixed from "/snippets/v2/monitor/diff/mixed.mdx";

Firecrawl monitoring detects when content on a website changes and get notified by webhook or email. It runs recurring scrapes or crawls and compare each result against the last retained snapshot. Use monitors to track product pages, docs, blogs, changelogs, competitor sites, or any page where changes matter.
Firecrawl monitoring detects when content on a website changes and get notified by webhook or email. It runs recurring scrapes, crawls, or web searches and compares each result against the last retained snapshot. Use monitors to track product pages, docs, blogs, changelogs, competitor sites, or any page where changes matter — or to watch the whole web for new results that match a goal (see [Web monitoring](#web-monitoring)).

Each check records page-level results as `same`, `new`, `changed`, `removed`, or `error`. You can receive a webhook as each monitored page finishes, a webhook for every completed check, email summaries when changes or errors happen, or any combination of those notifications.

Expand Down Expand Up @@ -150,14 +150,15 @@ Supported natural language examples:
- `daily at 5:30 PM`
- `weekly`

The minimum interval is 15 minutes. API responses always return the normalized cron expression. For text schedules, `timezone` controls when phrases like `daily at 9am` run. Text schedules are spread by monitor ID before they are converted to cron so many monitors do not all run at the same instant.
The minimum interval is 5 minutes. API responses always return the normalized cron expression. For text schedules, `timezone` controls when phrases like `daily at 9am` run. Text schedules are spread by monitor ID before they are converted to cron so many monitors do not all run at the same instant.

## Targets

Monitors support two target types:
Monitors support three target types:

- `scrape`: Runs one scrape per URL in `urls`.
- `crawl`: Runs a full crawl for `url` on each check, then diffs all discovered pages.
- `search`: Runs web search `queries` on each check and alerts on new matching results. See [Web monitoring](#web-monitoring) for its fields and behavior.

Each monitor accepts 1-50 targets. `retentionDays` defaults to `30` and can be set up to `365`.

Expand Down Expand Up @@ -273,14 +274,206 @@ The check detail response includes `estimatedCredits`, `actualCredits`, summary
</Tab>
</Tabs>

## Web monitoring

Scrape and crawl monitors watch URLs you name. A **web** monitor watches the whole web. Instead of diffing pages you already know about, you give it a `goal`, and Firecrawl turns that goal into search queries, runs them on every check, and alerts on **new** results that match. It's discovery rather than diffing.

Each check runs the same cycle: take the goal and its `queries`, apply a recency window, run the search, dedupe the results by fingerprint (canonical URL plus title plus snippet), let the optional AI judge decide which new results are meaningful to the goal, and alert through the same webhook and email channels as scrape and crawl monitors. Scheduling, goals, judging, and notifications all work exactly as described above.

<Note>
Page and crawl monitors diff content on URLs you name; web monitors discover new results across the web. Same scheduling, judge, and notifications underneath.
</Note>

### Search target

A search target uses `type: "search"` and replaces `urls` with the queries to run and how to score the results:

```json Search target
{
"type": "search",
"queries": ["open source AI coding assistant launch"],
"searchWindow": "24h",
"maxResults": 10,
"includeDomains": ["news.ycombinator.com"]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `type` | `"search"` | Selects the search target. |
| `queries` | `string[]` | Search queries to run on each check. |
| `searchWindow` | `"5m" \| "15m" \| "1h" \| "6h" \| "24h" \| "7d"` | Recency filter — only consider results published within this window. |
| `maxResults` | `number` | Total results to evaluate per check, `1`–`50`. Defaults to `10`. This is a combined cap across all `queries` (results are merged and deduped first), not a per-query limit — so an individual query may contribute fewer results, or none, if other queries fill the cap first. |
| `includeDomains` | `string[]` | Optional. Restrict results to these domains. Mutually exclusive with `excludeDomains`. |
| `excludeDomains` | `string[]` | Optional. Drop results from these domains. Mutually exclusive with `includeDomains`. |

The monitor-level `goal` and `judgeEnabled` apply just as in [Goals and judging](#goals-and-judging). Firecrawl expands the goal into queries when you don't supply your own, and the judge scores each new result against the goal.

### Judging

How much work each check does per result is controlled by the monitor's `judgeEnabled` — the same flag described in [Goals and judging](#goals-and-judging). With judging on, Firecrawl scrapes each matching result and evaluates its content against the goal, billed at 1 credit per judged result on top of the search call. With `judgeEnabled: false`, a web monitor returns the deduped search results with no AI judging — just the new SERP hits, and pays only the search-call credits (2 credits per 10 results).

### Statuses and dedup

Search results use the **same page-level `status` enum** as scrape and crawl monitors, so existing webhook and check-result consumers work unchanged. A search result maps to:

- `new` — a result that matched the goal for the first time. This is what alerts.
- `same` — a result already seen on a previous check (no new alert).
- `error` — a result that could not be evaluated (for example, the scrape for judging was skipped).

The finer-grained search disposition is exposed on each page's `metadata.searchStatus`, one of:

| `searchStatus` | Page `status` | Meaning |
|----------------|---------------|---------|
| `alert` | `new` | New result the judge considers meaningful — fires a notification. |
| `already_seen` | `same` | Fingerprint matched a result from an earlier check. |
| `watching` | `same` | New result the judge isn't confident about yet; tracked but not alerted. |
| `ignored` | `same` | New result the judge scored as not meaningful to the goal. |
| `skipped` | `error` | Result could not be judged this check (for example, scrape failure or degraded judging). |

A result alerts once when it first appears as `new`. If its title or snippet changes on re-index it fingerprints differently (canonical URL plus title plus snippet) and can fire again. One real-world event spread across many articles alerts only once.

Editing the monitor's `goal` or `queries` bumps its `goalVersion`, which invalidates prior judge verdicts. Re-evaluation is lazy rather than a bulk re-judge: existing results are not all re-judged at once. Instead, each result is re-judged the next time it resurfaces in a check, picking up the new `goalVersion` then. Results that don't resurface keep their old verdict and `goalVersion` until they reappear.

### Create a web monitor

A web monitor is created the same way as a scrape monitor — the only differences are the target (`type: "search"` with `queries`, `searchWindow`, `maxResults`, and optional domain filters) and that there are no URLs:

<CodeGroup>
```python Python
from firecrawl import Firecrawl

firecrawl = Firecrawl(
# No API key needed to get started — add one for higher rate limits:
# api_key="fc-YOUR-API-KEY",
)

monitor = firecrawl.create_monitor(
name="AI coding assistant launches",
schedule={"text": "every 30 minutes", "timezone": "UTC"},
goal="Alert when a new open-source AI coding assistant is announced. Ignore funding rounds and unrelated AI news.",
targets=[
{
"type": "search",
"queries": ["open source AI coding assistant launch"],
"searchWindow": "24h",
"maxResults": 10,
}
],
notification={
"email": {
"enabled": True,
"recipients": ["alerts@example.com"],
"includeDiffs": True,
}
},
)

print(monitor.id)
```

```js Node
import Firecrawl from "@mendable/firecrawl-js";

const firecrawl = new Firecrawl({
// No API key needed to get started — add one for higher rate limits:
// apiKey: "fc-YOUR-API-KEY",
});

const monitor = await firecrawl.createMonitor({
name: "AI coding assistant launches",
schedule: { text: "every 30 minutes", timezone: "UTC" },
goal:
"Alert when a new open-source AI coding assistant is announced. Ignore funding rounds and unrelated AI news.",
notification: {
email: {
enabled: true,
recipients: ["alerts@example.com"],
includeDiffs: true,
},
},
targets: [
{
type: "search",
queries: ["open source AI coding assistant launch"],
searchWindow: "24h",
maxResults: 10,
},
],
});

console.log(monitor.id);
```

```bash cURL
curl -s -X POST "https://api.firecrawl.dev/v2/monitor" \
-H "Authorization: Bearer $FIRECRAWL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "AI coding assistant launches",
"schedule": {
"text": "every 30 minutes",
"timezone": "UTC"
},
"goal": "Alert when a new open-source AI coding assistant is announced. Ignore funding rounds and unrelated AI news.",
"notification": {
"email": {
"enabled": true,
"recipients": ["alerts@example.com"],
"includeDiffs": true
}
},
"targets": [
{
"type": "search",
"queries": ["open source AI coding assistant launch"],
"searchWindow": "24h",
"maxResults": 10
}
]
}'
```
</CodeGroup>

When a new matching result is discovered, the `monitor.page` webhook reports it with status `new` and, when judging ran, a `judgment` describing why it matters:

```json monitor.page
{
"success": true,
"type": "monitor.page",
"id": "019df960-5f2a-75fb-a98b-bd2d32ca67d4",
"webhookId": "f1e2d3c4-0000-0000-0000-000000000000",
"data": [
{
"monitorId": "019df960-06e7-7383-9d89-82c0113dc31a",
"checkId": "019df960-5f2a-75fb-a98b-bd2d32ca67d4",
"url": "https://news.ycombinator.com/item?id=40000000",
"status": "new",
"error": null,
"isMeaningful": true,
"judgment": {
"meaningful": true,
"confidence": "high",
"reason": "A new open-source AI coding assistant was announced, which matches the monitor goal."
}
}
],
"metadata": {
"environment": "production"
}
}
```

## Pricing

Monitors don't introduce a separate per-monitor fee. Each check pays for the underlying scrape or crawl it performs, plus an optional credit per changed page when meaningful-change judging is enabled.
Monitors don't introduce a separate per-monitor fee. Each check pays for the underlying scrape, crawl, or search it performs, plus an optional credit per changed page when meaningful-change judging is enabled.

| Component | Credits |
|-----------|---------|
| Scrape monitor | 1 credit per URL per check |
| Crawl monitor | 1 credit per discovered page per check |
| Web monitor | 2 credits per 10 results per check |
| Web monitor judging | 1 credit per result judged, when AI judging is enabled (covers scraping and evaluating the result) |
| Meaningful change enabled | 1 additional credit per changed page that the judge validates |
| Format add-ons (JSON, PDF, question, enhanced mode, etc.) | Same as standalone [scrape](/features/scrape) |

Expand Down