---
title: "Update watch"
method: POST
path: "/update_watch"
---

# Update watch

`POST /update_watch`

## Request

- Base URL: `https://newsmind.app/api/v1`
- URL: `https://newsmind.app/api/v1/update_watch`
- Auth: OAuth 2 (authorization code, authorization URL `https://newsmind.app/oauth/authorize`, token URL `https://newsmind.app/oauth/token`, scopes: `mcp`)

Modify a saved watch (pause/resume via active, edit query/label/scope, change delivery channel/cadence). Only supplied fields change. Editing the query rebuilds the match set.

## Request body

- object
  - `watch_id` integer, required: From list_watches or create_watch.
  - `active` boolean: false pauses the watch (skipped by matching + delivery); true resumes it.
  - `label` string: Display name (e.g. 'M5 Mac mini'). Pass an empty string to clear it - the email digest then falls back to the query. Renaming never rebuilds the match set.
  - `query` string: New query. Rebuilds the watch's match set.
  - `feed_id` integer: New single-feed scope, from get_feeds. Pass null to widen back to all subscriptions; OMITTING it leaves the scope unchanged. Must be a feed you subscribe to: any other feed_id is refused with not_subscribed and nothing changes. Changing scope rebuilds the match set (rebackfilled_because 'scope').
  - `folder` string: New folder scope: limit to feeds in this subscription folder, spelled exactly as get_feeds returns it (not validated - a name no subscription uses matches nothing). Empty string widens back to all subscriptions. Changing scope rebuilds the match set (rebackfilled_because 'scope').
  - `threshold` number: Semantic similarity cutoff in (0,1]; any value is settable. Changing it re-runs the backfill so matches reflect the new cutoff, and the response returns a fresh threshold_preview. Calibration: below ~0.45 is effectively unfiltered, 0.55-0.60 is on-topic, above 0.70 is near-exact.
  - `delivery` 'in_session' | 'email' | 'webhook': Where matches are delivered: in_session = retrieved when you call get_watch_matches or get_new_articles; email = digest to the account email; webhook = POST to webhook_url. webhook_url is NOT checked here, unlike create_watch, which refuses delivery=webhook without one (webhook_url_required). Send one in the same call unless the watch already has one, or every send fails server-side with nothing surfaced to you. Speeding up delivery settles the matches that piled up under the slower setting rather than shipping them all at once — returned as settled_backlog, and they stay visible via get_watch_matches.
  - `cadence` 'realtime' | 'weekdays' | 'daily' | 'weekly': For email/webhook delivery: how often to send. weekdays = daily but Monday-Friday only (weekend matches ride Monday's digest); realtime = the next 15-minute delivery tick. Unlike create_watch there is no default - an unset cadence stays unset and sends daily. A cadence on an in_session watch schedules nothing and returns cadence_inert. Switching to a FASTER cadence settles the backlog the slower one accumulated (settled_backlog) — a watch moved from weekly to realtime sends the next match, not the week behind it.
  - `delivery_hour` integer: Daily/weekly delivery hour, 0-23, account timezone. Default 8.
  - `delivery_day` 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday': Weekly delivery weekday. Default monday.
  - `webhook_url` string: The https:// endpoint we POST match batches to. Must be a public host - loopback, private and link-local addresses are rejected with invalid_webhook_url, and the whole update aborts, so no other field in the call is applied. Pass an empty string to clear it.
  - `require_terms` string[]: Words an article must ALSO carry to match (max 12). ANY one is enough, not all - ['missing','Columbus','Ohio State'] keeps anything mentioning any of them. Use this to disambiguate a name that points at more than one person or thing: a watch on 'Brian Shaffer' with require_terms ['missing'] keeps the missing-persons coverage and drops the baseball player's stats page and two unrelated obituaries. DO NOT append the word to `query` instead - the query is matched as an exact phrase, so 'Brian Shaffer missing' matches nothing at all. Changing this rebuilds the match set.
  - `exclude_terms` string[]: Words that DEMOTE a match rather than dropping it (max 12). Matches still appear, sorted last and tagged `user_excluded`, so an over-broad guess is recoverable. Prefer require_terms when you know what you DO want.

## Response `200`

Success

- Watch: A saved-search definition. kind is keyword (phrase matching) or semantic (Max only, matching by meaning). delivery routes matches in-session, to email digests, or to a webhook; email/webhook take a cadence and optional delivery slot (hour + weekday, in the account's timezone). Stale keyword-only shape corrected in v0.42.0 — it had lagged reality since v0.22.
  - `watch_id` integer, required
  - `query` string, required
  - `label` string, nullable: Optional human-readable name. Returned as-is and null when unset; only the email digest substitutes the query.
  - `feed_id` integer, nullable: Optional scope to one feed.
  - `folder` string, nullable: Optional scope to a subscription folder.
  - `kind` 'keyword' | 'semantic', required
  - `threshold` number, nullable: Semantic only: cosine similarity cutoff.
  - `active` boolean, required
  - `delivery` 'in_session' | 'email' | 'webhook', required
  - `cadence` 'realtime' | 'weekdays' | 'daily' | 'weekly', nullable
  - `require_terms` string[], nullable: issue #23: OR-list an article must also carry. null = no filter.
  - `exclude_terms` string[], nullable: issue #23: OR-list that demotes a match at read time. Never deletes.
  - `delivery_hour` integer, nullable: Digest delivery hour in the account's timezone; null = default 08:00.
  - `delivery_day` string, nullable: Weekly digests: weekday name; null = default monday.
  - `webhook_url` string, nullable
  - `created_at` string, date-time, required
  - `backfilled_matches` integer: create: in-window articles that already matched.
  - `match_sources` string[]: create: publishers this watch already matched. Unrelated publishers suggest the query is ambiguous.
  - `threshold_preview` object: Semantic only: cutoff -> surviving match count, for picking a stricter threshold from data.
  - `rebackfilled_matches` integer: update: match count after an edit that rebuilt the set.
  - `rebackfilled_because` 'query' | 'threshold' | 'scope' | 'require_terms': update: which edit rebuilt the match set.
  - `zeroed_by` 'query' | 'threshold' | 'scope' | 'require_terms': update: present when the edit emptied a watch that previously had matches - usually an over-narrow edit.
  - `cadence_inert` boolean: update: a cadence was set on an in_session watch, where it schedules nothing.
  - `settled_backlog` integer: update: pending matches settled rather than emailed when delivery is sped up (in_session to email, or to a faster cadence), so the first digest is not the slower setting's backlog. Still readable via get_watch_matches.
  - `settled_backlog_message` string: update: accompanies settled_backlog - what was dropped from email and where it is still readable.
  - `ok` boolean
  - `message` string
  - `error` string: Set instead of a watch when the call was refused: max_required (semantic on a non-Max plan), invalid_webhook_url (not https, or a loopback/private/link-local host), watch_limit_reached, embedding_failed.

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `402`: Subscription required (trial expired or cancelled)
- `429`: Too many calls. Either the per-account rate limit ({error: "rate_limited", message}) or the account's in-flight work ({error: <sentence>, code: "busy"}). Wait Retry-After seconds.
- `500`: Server error on our side (error: internal_error). The team has been notified.
- `503`: Temporarily unable to answer. error: timeout (the request ran too long; narrow it), server_busy or tier_unavailable (retry after Retry-After seconds when sent).

---

[API](https://skmtc.dev/newsmind/apis/newsmind-api.md) · [All operations](https://skmtc.dev/newsmind/apis/newsmind-api/llms.txt) · [OpenAPI document](https://skmtc.dev/newsmind/apis/newsmind-api/revisions/f8af23fb5a10?raw)
