# Monitors

`POST https://api.serpkite.com/v1/monitors` · Credits: 1 per run, priced like the search or page fetch it makes (1 per 10 results, num=100 is 7); empty and failed runs are free

Saved /v1/search or /v1/news requests that run hourly, daily or weekly and report only results they haven't seen before, or /v1/webpage URLs reported when their content changes: in the run history, and to a signed monitor.results webhook when one is set.

A monitor saves a `/v1/search` or `/v1/news` request and runs it on a schedule (`interval`: `hourly`, `daily` or `weekly`, or `interval_seconds` from 1 hour to 30 days). Each run reports only the results among the top `num` it hasn't seen before. With `endpoint: "webpage"` and a `url`, it checks that page instead and reports it (`change: new|changed`, with its Markdown) whenever the content changes.

New results are always kept in the monitor's run history (`GET /v1/monitors/{id}/runs`, results for 24 hours) and, when the monitor has a `webhook_url`, posted as a signed [`monitor.results`](https://serpkite.com/docs/webhooks#other-events) event. The first run is due right away and reports everything it finds (`first_run: true`).

Each run is billed like the request it makes (1 credit per 10 results, `num: 100` is 7; empty and failed runs are free) on the monitor's API key: the key that created it, or last changed or ran it, under that key's limits and the account's spend cap. A monitor pauses itself after 10 failed runs in a row or when its key is revoked; `PATCH` it with `active: true` to resume.

A monitor keeps only the fields listed below; any other search field (`tbs`, `start_date`, `include_content`…) is a `400`.

## Request body

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `q` | string |  | The query, for search and news monitors. |
| `url` | string |  | The page to watch, for `endpoint: "webpage"`. |
| `endpoint` | string | `search` | What to run. One of: `search`, `news`, `webpage`. |
| `interval` | string | `daily` | How often to run. One of: `hourly`, `daily`, `weekly`. |
| `interval_seconds` | integer |  | Exact interval, 3,600–2,592,000 seconds. Overrides `interval`. |
| `webhook_url` | string |  | Receives signed `monitor.results` events. Without one, read new results from the run history. |
| `name` | string |  | Your label, up to 200 characters. |
| `active` | boolean | `true` | `false` creates the monitor paused. |
| `metadata` | object |  | Your own JSON object (up to 2 KB), echoed on the monitor and in its webhooks. |
| `country` | string | `us` | Country to search from, as a two-letter ISO code (us, gb, de, in…). Defaults to the country a location ends in. |
| `language` | string | `en` | Interface language, as a language code with an optional region (en, de, pt-BR, zh-TW…). |
| `location` | string |  | Canonical location for local results, e.g. "Austin, Texas, United States". Ending in a country name also sets country. |
| `time` | string |  | Restrict to recent results. Shorthand for tbs=qdr:*. One of: `hour`, `day`, `week`, `month`, `year`. |
| `num` | integer |  | Watch only the top `num` results (1–100). |
| `device` | string | `desktop` | Which SERP layout to fetch. One of: `desktop`, `mobile`. |
| `safe` | string | `off` | SafeSearch filtering. One of: `off`, `active`. |
| `include_domains` | string[] |  | Only results from these sources, up to 20: a domain (example.com, subdomains match), a path prefix (github.com/org) or a TLD (.gov). Compiled to site: operators and enforced on the results. Search, news, images, videos. |
| `exclude_domains` | string[] |  | Drop results from these sources, up to 20, same formats as include_domains. Search, news, images, videos. |
| `engine` | string or array | `google` | Which search providers may answer. google is Google only (SerpKite still fails over across its own proxy pools); auto falls back to other providers when Google is blocked or times out; consensus (search only) asks several independent indexes in parallel, merges the results by URL, ranks them by agreement and lists each result's sources, at the sum of one page per provider that returned results; a provider name or a list (e.g. google,brave) restricts the request to those. meta.engine names the provider that answered. One of: `google`, `auto`, `consensus`, `brave`, `bing`, `yahoo`, `duckduckgo`, `mojeek`, `wikipedia`. |

## Example request

cURL:

```bash
curl https://api.serpkite.com/v1/monitors \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"ai agents funding","endpoint":"news","interval":"daily","webhook_url":"https://example.com/hooks/serpkite"}'
```

## Response fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | The monitor's ID, for `GET`, `PATCH`, `DELETE /v1/monitors/{id}`, `POST /v1/monitors/{id}/run` (run at the next poll, within about 30 seconds) and `GET /v1/monitors/{id}/runs`. |
| `request` | object | The saved search (`q` and options) or watched `url`. |
| `interval_seconds / next_run_at / last_run_at` | mixed | The schedule. |
| `last_status / last_error / last_new_results` | mixed | The last run: `ok`, `error`, `webhook_failed` or `paused`, and how many new results it found. |
| `runs / consecutive_failures / credits_used` | number | Totals over all runs; the failure streak pauses the monitor at 10. |

## Example response

```json
{
  "id": "01926a3e-9d1f-7a2b-8c3d-4e5f6a7b8c9d",
  "name": "",
  "endpoint": "news",
  "request": {
    "q": "ai agents funding"
  },
  "metadata": null,
  "interval_seconds": 86400,
  "webhook_url": "https://example.com/hooks/serpkite",
  "active": true,
  "next_run_at": "2026-10-04T12:00:00Z",
  "last_run_at": "2026-10-03T12:00:05Z",
  "last_status": "ok",
  "last_error": null,
  "last_new_results": 10,
  "runs": 1,
  "consecutive_failures": 0,
  "credits_used": 1,
  "created_at": "2026-10-03T12:00:00Z"
}
```

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | A parameter is missing or invalid. |
| 401 | `unauthorized` | The API key is missing, invalid or revoked. |
| 402 | `insufficient_credits` | Your balance is too low. Buy a pack or wait for the monthly free grant. |
| 429 | `rate_limited` | Too many requests per second for your plan. Retry after the Retry-After header. |
| 503 | `upstream_error` | Google could not be fetched or parsed. Not billed; retry after Retry-After. |

All errors: https://serpkite.com/docs/errors

## Notes

- At most 100 monitors per account (`403 forbidden` beyond that).
- Run now (`POST /v1/monitors/{id}/run`) requires an active monitor. A paused monitor returns `409 invalid_request`; resume it with `PATCH {"active": true}`, which also schedules an immediate run.
- Changing the saved search with `PATCH` starts a new baseline: the next run reports every result as new.
- To spot new results a monitor keeps only hashes of the links it has reported; the new results themselves stay in the run history for 24 hours. See [Privacy and data retention](https://serpkite.com/docs/privacy-and-data-retention).

## Related

- [Scheduled monitoring guide](https://serpkite.com/docs/guides/scheduled-monitoring)
- [Webhooks](https://serpkite.com/docs/webhooks)
- [News](https://serpkite.com/docs/endpoints/news)