# Watch and refresh

A watch is a saved query whose reruns answer with what changed. The first run keys every record of the result on its own identity (a posting's URL, a profile's LinkedIn URL, an award's id, a USAJOBS control number) and stores a fingerprint per key. A refresh reruns the same query, bills like any run, and returns only the new and changed records, plus the keys that disappeared. Records are never copied: they stay on the run, and the changes are rebuilt from the last run's result when you ask.

Use it for the weekly loop behind monitoring: one watch per office, contractor, keyword or search, one refresh per week, and your pipeline reads the delta instead of the whole list.

## Create a watch

```text
POST /v1/watch
```

Same body as [run](/docs/api/run) plus a `name`. A capability with its input, or `provider` + `endpoint` with the provider's request for a raw endpoint.

```bash
curl -X POST https://api.routergrowth.com/v1/watch \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Booz Allen Navy postings", "capability": "company.jobs",
       "input": {"company": "Booz Allen Hamilton", "query": "Navy", "posted_within": "month", "limit": 20},
       "routing": {"max_cost": "0.10"}}'
```

Answers `201` with the watch, the run receipt and the changes. On the first run every record is new.

```json
{
  "watch": {"watch_id": "wch_...", "name": "Booz Allen Navy postings", "kind": "capability",
            "capability": "company.jobs", "key_field": "url", "records": 14, "runs": 1,
            "last_changes": {"status": "succeeded", "new": 14, "changed": 0, "removed": 0, "unchanged": 0}},
  "run": {"run_id": "run_...", "status": "succeeded", "billing": {"quoted": "0.03", "charged": "0.021"}},
  "changes": {"counts": {"records": 14, "new": 14, "changed": 0, "removed": 0, "unchanged": 0},
              "new": [{"title": "Capture Manager, Navy", "url": "https://...", "posted_at": "2026-09-15"}],
              "changed": [], "removed": []}
}
```

The request waits up to `wait_seconds` (default 30) for the run. A slow provider answers `202` with `changes: null` and a `pending_run_id`; fetch the watch with `wait` to collect the result.

## Refresh

```text
POST /v1/watch/{watch_id}/refresh?wait=60
```

Reruns the query. `changes.new` holds records whose key was not there last time, `changes.changed` the records whose content moved (ranking fields such as `position` are ignored), `changes.removed` the keys no longer returned. A refresh whose run fails changes nothing: the fingerprints stay, the next refresh diffs against them, and `last_changes.error` says what happened. A refresh while one is still executing answers `409`.

## Read and list

```text
GET /v1/watch                 every watch with counts and the last refresh summary, no records
GET /v1/watch/{watch_id}      the watch, the last run receipt, the last changes with records; ?wait=60 holds while a refresh runs
DELETE /v1/watch/{watch_id}
```

## How records are keyed

| Result | Key |
| --- | --- |
| company.jobs, web.search, news.search, seo.serp | the record's `url` |
| people.search, linkedin.search | `linkedin_url` |
| social.* | the post or comment `id` |
| USAJOBS `/search` | `PositionURI`; `/historicjoa`: `usajobsControlNumber` |
| USAspending award search | `generated_internal_id` |
| any other list of records | the first of url, link, id, email; else a hash of the record |
| a single-record answer (one enrichment, one page) | nothing to diff; the watch reports zero records |

`key_field` on the watch tells you which one was used. Two providers can name the same person differently; a watch diffs one query on one route, it does not merge identities across providers.

## Cost

A watch costs nothing to keep. Each refresh is one run at the capability's quoted price, so the weekly cost of a watch is the price of its query, and the delta is free. Cheap signal queries (postings, news, USAJOBS, awards) are the ones to refresh often; people lists are page-priced, so refresh them on a longer cadence.

Over MCP the same loop is the `watch` tool (create, or refresh with `watch_id`) and `watches` (list, read, delete).
