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

# Watches

> Pick a watch type, build the query, tune match sensitivity, preview the volume

A watch is a saved query that Signa evaluates against every data update for the offices it covers. Pick the type whose required filter is the narrowest fit for what you want to track, build the query body, and use [Preview](#preview-before-you-launch) to estimate volume before going live.

<Note>
  Creating, listing, and managing watches requires the `portfolios:manage` scope on your API key.
</Note>

## Quickstart: watch a class in the US

A complete path for the most common setup, "tell me about new USPTO filings in my Nice class." Everything below needs an API key with the `portfolios:manage` scope.

### 1. Preview the volume

Scope to the US with `filters.jurisdictions: ["US"]` (equivalently `filters.offices: ["US"]`; jurisdictions are auto-translated to offices at create time):

```bash theme={null}
curl -X POST "https://api.signa.so/v1/watches/preview" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: preview-us-class9-2026-06-12" \
  -d '{
    "query": {
      "version": "v2",
      "filters": { "niceClasses": [9], "jurisdictions": ["US"] }
    },
    "trial_window_days": 7
  }'
```

```json theme={null}
{
  "object": "watch_preview",
  "estimated_match_count": 312,
  "trial_window_days": 7,
  "request_id": "req_8kLm2nPq"
}
```

\~312 alerts/week is a real feed. If the count is overwhelming, narrow with `trigger_events: ["trademark.created"]` (new filings only, drops the update and status-change churn) before creating.

### 2. Create the watch

```bash theme={null}
curl -X POST "https://api.signa.so/v1/watches" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-us-class9-watch-001" \
  -d '{
    "name": "US class 9, new filings",
    "watch_type": "class",
    "query": {
      "version": "v2",
      "filters": { "niceClasses": [9], "jurisdictions": ["US"] },
      "trigger_events": ["trademark.created"]
    }
  }'
```

The response is the `Watch` object, save its `id` (`wat_*`). The watch is `active` immediately and is evaluated against every USPTO data update. Mutating calls like this one require the [`Idempotency-Key` header](/guides/resilience#idempotent-requests).

### 3. Register a webhook (push)

```bash theme={null}
curl -X POST "https://api.signa.so/v1/webhooks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-us-class9-webhook-001" \
  -d '{
    "url": "https://alerts.example.com/signa",
    "description": "US class 9 alerts",
    "enabled_events": ["alert.created"]
  }'
```

Store the returned `secret` immediately, it is shown once. Each delivery is an HMAC-signed POST carrying the full alert object, whose `trademark.id` you can pass straight to [`GET /v1/trademarks/{id}`](/api-reference/trademarks/get-trademark):

```json theme={null}
{
  "type": "alert.created",
  "id": "alt_8kLm2nPq",
  "timestamp": "2026-06-12T01:14:09.412Z",
  "data": {
    "id": "alt_8kLm2nPq",
    "object": "alert",
    "schema_version": "2026-06-01",
    "watch": { "id": "wat_7hRt4xQw", "name": "US class 9, new filings", "type": "class" },
    "customer_reference": null,
    "event": { "type": "trademark.created", "summary": "Trademark created", "diff": [] },
    "match": null,
    "trademark": {
      "id": "tm_9pQs3vNk",
      "mark_text": "ACME ROCKETS",
      "office_code": "US",
      "status": { "primary": "pending", "stage": "examination" },
      "nice_classes": [9],
      "links": { "self": "/v1/trademarks/tm_9pQs3vNk" }
    },
    "deadline": { "severity": "normal", "opposition_window_status": "open", "must_act_by": "2026-07-13T03:59:59.999Z" },
    "timestamps": { "occurred_at": "2026-06-12T01:10:00.000Z", "created_at": "2026-06-12T01:14:09.412Z" },
    "links": { "trademark": "/v1/trademarks/tm_9pQs3vNk", "watch": "/v1/watches/wat_7hRt4xQw" }
  }
}
```

Verify signatures on every delivery (see [Webhooks](/guides/monitoring/webhooks#signing)) and dedupe by the `webhook-id` header. No receiver yet? Use [a tunnel or request-bin pattern](/guides/monitoring/webhooks#testing-deliveries-before-you-have-a-receiver) to develop against real deliveries.

### 4. Add a polling fallback (pull)

Webhooks deliver in seconds but receivers go down. A periodic reconciliation pass over the watch's alerts catches anything you missed:

```bash theme={null}
curl "https://api.signa.so/v1/watches/wat_.../alerts?limit=100" \
  -H "Authorization: Bearer $SIGNA_API_KEY"
```

Compare the returned alert IDs against what your receiver recorded, and fetch any gaps with [`POST /v1/alerts/lookup`](/api-reference/monitoring/alerts/lookup) or process them directly from the list response. Page with `pagination.cursor` until `has_more` is `false`. For the full reconciliation pattern, see [Handling alerts](/guides/monitoring/alert-handling).

### Variations

* **US filings for a specific competitor**: `watch_type: "owner"` with `filters: { ownerId: "own_...", jurisdictions: ["US"] }`.
* **Confusingly similar US filings**: `watch_type: "similarity"` with `q: "yourmark"`, `strategies: ["exact", "phonetic", "fuzzy"]`, `min_match_tier: "phonetic"`, and `filters: { jurisdictions: ["US"] }`.
* **More jurisdictions later**: `PATCH` the watch with a full replacement `query`, one watch covers the set. `PATCH` replaces the entire `query` object and re-validates it (`version`, the type's required filters, `trigger_events`), so resend every field, not just the one you're changing:

  ```json theme={null}
  {
    "query": {
      "version": "v2",
      "filters": { "niceClasses": [9], "jurisdictions": ["US", "EU", "GB"] },
      "trigger_events": ["trademark.created"]
    }
  }
  ```

## Pick a watch type

| You want to...                                                                  | `watch_type` | Required field                                                                      |
| ------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
| Track one specific mark you own (renewals, status drift).                       | `mark`       | `filters.trademarkIds` (one ID)                                                     |
| Watch an entire portfolio of marks at once.                                     | `portfolio`  | `filters.trademarkIds` (1 or more IDs)                                              |
| Track a competitor by owner.                                                    | `owner`      | `filters.ownerId`                                                                   |
| Track a company as a resolved entity — including subsidiaries discovered later. | `owner`      | `filters.entityId` or `filters.entityGroup` — see [Entity watches](#entity-watches) |
| Watch new filings in a Nice class (or class set), optionally by jurisdiction.   | `class`      | `filters.niceClasses`                                                               |
| Detect confusingly similar new filings.                                         | `similarity` | `q` (text)                                                                          |

The `query` body uses the same vocabulary as [trademark search](/api-reference/trademarks/list-trademarks), with one casing difference: REST search query params are snake\_case (`nice_classes=9`), while watch-DSL filter keys are camelCase (`niceClasses: [9]`). If you can build the search, you can save it as a watch, just translate the key casing.

## The query DSL

This is the canonical accepted shape, validated strictly, anything outside it returns `400`:

```ts theme={null}
interface WatchQuery {
  version: string;                        // REQUIRED, non-empty string; new watches use "v2"
  q?: string;                             // keyword query (required for similarity)
  filters?: WatchFilters;                 // camelCase keys, see the full list below
  trigger_events?: WatchTriggerEvent[];    // non-empty subset of the five events; default = first three
  strategies?: WatchSearchStrategy[];      // similarity only, non-empty subset of exact/phonetic/fuzzy/prefix; default exact+fuzzy
  min_match_tier?: WatchMinMatchTier;      // similarity only, gates matches by attribution tier
}
```

### `version` (required)

A non-empty string. New watches use `"v2"`. Omitting `version` returns `400`.

### `q` keyword constraints

* Whitespace-separated. Up to 20 keywords. Each keyword must be at least 3 characters.
* Stop words are rejected with `400` to prevent watches that match too broadly. The current list is `the, and, or, not, for, a, an, of, in, on, to, is`.

### `filters`, the full key list

Filter keys are camelCase. Unknown keys, including snake\_case typos like `nice_classes`, are rejected with `400` (the error message suggests the camelCase spelling when it recognizes the typo).

Allowed keys:

`applicationNumber`, `attorneyFirmName`, `attorneyId`, `challengeStates`,
`entityGroup`, `entityId`, `expiryDate`, `filingDate`, `filingRoute`,
`firmId`, `goodsServicesText`, `hasMedia`, `hasProceedings`, `irNumber`,
`isMadrid`, `isRetracted`, `isSeriesMark`, `jurisdictions`,
`markFeatureType`, `markLegalCategory`, `niceClasses`, `office`, `offices`,
`originOfficeCode`, `ownerCountry`, `ownerHasLei`, `ownerId`, `ownerLei`,
`ownerName`, `ownerPubliclyTraded`, `ownerTicker`, `publicationDate`,
`registrationDate`, `registrationNumber`, `renewalDueDate`, `rightKind`,
`scopeKind`, `statusPrimary`, `statusReason`, `statusStage`,
`terminationDate`, `trademarkIds`, `updatedAt`, `viennaCodes`.

Notes:

* **ID filters accept both forms.** `trademarkIds`, `ownerId`, `attorneyId`,
  and `firmId` accept prefixed IDs (`tm_*`, `own_*`, `att_*`, `firm_*`) or
  raw UUIDs. Wrong-type or malformed IDs return `400` with the offending
  index in the field path.
* **Office codes are case-insensitive**: `filters.offices: ["uspto"]` is
  accepted and echoed back in the uppercase [WIPO ST.3](https://www.wipo.int/standards/en/st3.html)
  form (`["US"]`); legacy lowercase codes are accepted permanently as aliases.
  Each code must be a 2-10 character string that exists in [`GET /v1/offices`](/api-reference/reference/list-offices).
* `filters.jurisdictions` (e.g. `["US", "EU"]`) is auto-translated to
  `filters.offices` at create time, so either spelling of scope works.
* `entityId` / `entityGroup` accept `ent_*` ids or raw UUIDs and are
  mutually exclusive — see [Entity watches](#entity-watches).
* **`ownerTicker` and `ownerPubliclyTraded` are subsidiary-inclusive.** They
  share the same public-company/listing fields as `GET /v1/trademarks`, so a
  watch with `ownerTicker: "NKE"` fires on Nike's own filings **and** those of
  its subsidiaries (an owner's entity listing ticker, direct or inherited from a
  listed ancestor, folds into this field). `ownerPubliclyTraded: true` matches
  any owner with an active listing association (a confirmed SEC ticker, or a
  resolved entity that is listed or a subsidiary of a listed company); a `false`
  or absent value means no confirmed listing, not confirmed private.

### `trigger_events`

Any non-empty subset of these five values (anything else returns `400`; an empty array is also rejected, omit the field to subscribe to the default set):

**Default set** (applied when `trigger_events` is omitted):

* `trademark.created`
* `trademark.updated`
* `trademark.status_changed`

**Opt-in** (valid, but only delivered when you list them explicitly):

* `trademark.retracted`
* `trademark.corrected`

Narrow the default set to silence events you don't care about. For example, a portfolio watch that only fires on status changes:

```ts theme={null}
trigger_events: ['trademark.status_changed']
```

Or subscribe to the default events plus retractions:

```ts theme={null}
trigger_events: [
  'trademark.created',
  'trademark.updated',
  'trademark.status_changed',
  'trademark.retracted',
]
```

Each triggered event reaches you as an `alert.created` webhook delivery carrying the trademark event type in `data.event_type` — see [Receiving `trademark.*` events](/guides/monitoring/webhooks#receiving-trademark-events) for the delivery-side view (envelope shape, the pure-flip rule for `retracted` / `corrected`, and scope caveats).

<Note>
  **Retractions, corrections, and cancellations, in one paragraph.** `trademark.retracted` fires when the source office feed pulls or retracts a record (`is_retracted` flips false to true); `trademark.corrected` fires on the reverse flip, when a previously retracted record reappears. Both are opt-in: list them explicitly in `trigger_events`, or they never arrive. A legal cancellation is different and needs no opt-in: whenever a matching mark's status changes, including moving to `cancelled`, `withdrawn`, `abandoned`, or otherwise going dead, the watch fires a regular `trademark.status_changed` alert (subscribed by default). Any mark, owner, portfolio, or class watch already covers cancellations with no special setup.
</Note>

### `strategies` (similarity only)

Which search strategies feed a similarity watch's matching, a non-empty subset of `exact`, `phonetic`, `fuzzy`, `prefix`. Omit it to use the default (`exact` and `fuzzy`); an explicit empty array returns `400`. Add `phonetic` to catch sound-alikes ("NOVA" / "KNOVA" / "NOWA") or `prefix` for starts-with matching. Ignored for other watch types.

### `min_match_tier` (similarity only)

Gates which similarity matches are allowed to fire an alert, by the attribution tier of the match. One of:

* `exact`: only exact matches alert.
* `normalized`: adds normalized, phrase, synonym, identifier, and homoglyph matches.
* `fuzzy`: adds fuzzy and prefix matches.
* `phonetic`: the broadest tier, adds phonetic (sound-alike) matches.

Omit it and every tier, including `phonetic`, can fire an alert (the broadest gate). Ignored for other watch types.

## `delivery_mode` — per-alert delivery today

`delivery_mode` is a top-level field on the **watch** (alongside `name` and `query`). Only `always_per_alert` is accepted on create and update today — any other value returns `400`. The digest modes (`digest_above_threshold`, `digest_only`) appear in the watch schema and the field is stored to eventually drive digest batching, but **digest suppression is not live**: every watch delivers one `alert.created` webhook per alert, regardless of the stored mode.

Until digest modes ship, size your receiver for per-alert volume ([Preview](#preview-before-you-launch) estimates it) and batch on your side if you need digests — dedupe by `webhook-id` and roll alerts up on your own schedule. See [Known beta limitations](/guides/monitoring/beta-limitations#delivery-modes-only-always_per_alert).

Use [Preview](#preview-before-you-launch) to estimate volume for different `strategies` / `min_match_tier` combinations before going live.

## `customer_reference`, your own label

`customer_reference` is a top-level field on the watch (alongside `name` and `query`, not inside the query DSL), a free-text passthrough string you control. Signa never interprets it; it's there for your own correlation (a case number, internal watch ID, team name, routing key).

* Set it on create, `PATCH`, or in a bulk create, max 200 characters. Pass `null` to clear it.
* It's returned on the watch resource (`GET /v1/watches/{id}`).
* It is frozen at emit time and echoed onto every alert the watch produces, as `customer_reference` on both the REST `Alert` resource and the `alert.created` webhook body. Changing it later affects only alerts emitted after the change; already-emitted alerts keep the value they were stamped with.

```ts theme={null}
await signa.watches.create({
  name: 'Aurora Digital core mark, status changes',
  watch_type: 'mark',
  customer_reference: 'matter-2026-0481',
  query: {
    version: 'v2',
    filters: { trademarkIds: ['tm_8kLm2nPq'] },
    trigger_events: ['trademark.status_changed'],
  },
});
```

## Per-jurisdiction watches

Build separate watches per jurisdiction when you need different delivery cadence per region, or when different regional teams own different jurisdictions. Otherwise pass `filters.jurisdictions: ["US", "EU", "GB"]` once and let a single watch cover the set.

```ts theme={null}
await signa.watches.create({
  name: 'Apple Inc, class 9 worldwide',
  watch_type: 'owner',
  query: {
    version: 'v2',
    filters: {
      ownerId: 'own_7hRt4xQw',
      niceClasses: [9],
      jurisdictions: ['US', 'EU', 'GB', 'CA'],
    },
  },
});
```

## Entity watches

An **entity** (`ent_*` id, from [`GET /v1/entities`](/api-reference/entities/list-entities)) groups the many per-office owner records that belong to one real-world company. Entity watches let you say "watch everything Unilever does" without enumerating owner records — and without re-editing the watch when Signa's entity resolution links new owner records to the company later.

Two scopes, mutually exclusive (sending both returns `400`):

* **`filters.entityId`** — the entity's own marks: every trademark whose *current* owners are linked to that entity, across all offices. Derived singleton ids (an `ent_*` id wrapping a single unlinked owner) are valid targets too.
* **`filters.entityGroup`** — the entity's whole **GLEIF corporate family**: the ultimate parent plus all subsidiaries. Pass any family member's id; Signa climbs to the family root and covers every entity in the tree.

```ts theme={null}
await signa.watches.create({
  name: 'Unilever group -- all activity',
  watch_type: 'owner',
  query: {
    version: 'v1',
    filters: { entityGroup: 'ent_018f9b2e-9b6c-7c9c-b4f1-1234567890ab' },
  },
});
```

Semantics worth knowing:

* **Accepted id forms.** `ent_<uuid>` or a raw UUID (stored normalized). Wrong-prefix or malformed ids return `400`. For `watch_type: "owner"`, `entityId` or `entityGroup` satisfies the required-field rule in place of `ownerId`.
* **Validated at create time.** A nonexistent or suppressed entity returns `400` (the watch would never fire — we reject it instead of storing a dead watch). A corporate family too large to traverse (more than 5,000 related entities or deeper than 32 levels) returns `422` with type `entity_too_large` and `reason: "family_graph_too_large"` — watch a specific entity in the family with `entityId` instead.
* **Freshness is automatic.** Matching happens against each trademark's live entity linkage, refreshed continuously by Signa's entity-resolution pipeline. When a newly discovered subsidiary (or a newly linked owner record) is attached to the entity, its marks start matching your watch on their next change — no PATCH needed. `entityGroup` re-resolves the family on every evaluation, so family growth is picked up automatically. If the entity you watch is later fused into another entity, the watch follows the surviving entity on its own.
* **No member cap.** Unlike `?entity_id=` on trademark search (which caps at 10,000 member owners), entity *watches* have no member-owner limit — even the largest portfolios are watchable. Only the family-graph traversal bound above applies, and only to `entityGroup`.
* **Honest caveat: coverage depends on entity-resolution linkage.** An entity watch only sees marks whose owner records have been linked to the entity (or that belong to the watched singleton owner). Owner records that entity resolution hasn't linked yet — common for name variants, recent filings, or offices with sparse identifiers — won't match until they're linked. For belt-and-braces coverage of a specific known owner record, add a separate `ownerId` watch.

## Worked examples

### Track one mark

```ts theme={null}
await signa.watches.create({
  name: 'My core mark, status changes',
  watch_type: 'mark',
  query: {
    version: 'v2',
    filters: { trademarkIds: ['tm_8kLm2nPq'] },
    trigger_events: ['trademark.status_changed'],
  },
});
```

### Track a portfolio

```ts theme={null}
await signa.watches.create({
  name: 'Q4 acquisitions portfolio',
  watch_type: 'portfolio',
  query: {
    version: 'v2',
    filters: {
      trademarkIds: ['tm_8kLm2nPq', 'tm_9pQs3vNk', 'tm_7hRt4xQw'],
    },
  },
});
```

### Track a Nice class

```ts theme={null}
await signa.watches.create({
  name: 'New class-9 filings in US/EU',
  watch_type: 'class',
  query: {
    version: 'v2',
    filters: { niceClasses: [9], jurisdictions: ['US', 'EU'] },
    trigger_events: ['trademark.created'],
  },
});
```

### Detect a similar mark

```ts theme={null}
await signa.watches.create({
  name: 'Marks similar to Nike',
  watch_type: 'similarity',
  query: {
    version: 'v2',
    q: 'Nike',
    filters: { niceClasses: [9, 35] },
    strategies: ['exact', 'phonetic', 'fuzzy'],
    min_match_tier: 'phonetic',
  },
});
```

## Preview before you launch

Dry-run the query against the last N days of data to estimate volume:

```ts theme={null}
const preview = await signa.watches.preview({
  query: myQuery,
  trial_window_days: 30,
});
console.log(`${preview.estimated_match_count} alerts in the last 30 days`);
```

Preview uses the same logic as live watches, so the count is faithful. If you see thousands of matches, the query is too broad, tighten `filters`, narrow `strategies`, or set `min_match_tier` to a stricter tier (`exact` is the narrowest). By default it returns the actual matching marks; set `count_only: true` to get just the count.

Preview semantics worth knowing before you script against it (full details on [Preview Watch](/api-reference/monitoring/watches/preview)):

* **`estimate_basis`.** When the response carries `estimate_basis: "candidacy_upper_bound"`, the count is an upper-bound estimate, not an exact match count (the scan hit the server-side cap, the search backend was unreachable, or the time budget expired partway). Absent field means an exact count.
* **One preview at a time.** A second concurrent preview for your org returns `429` with a `Retry-After` header. Honor it, the SDK does.
* **`preview_timeout` (504).** If the time budget expires before any usable result exists you get a `preview_timeout` envelope with `retryable: false`. Narrow the query instead of retrying.
* **Latency.** `class`, `mark`, and `owner` previews complete in a few seconds; broad `similarity` previews are the heaviest and may approach the budget.
* **ID forms.** Preview accepts the same prefixed (`tm_*` / `own_*`) or raw-UUID ID filters as create.

After you update a live watch (widen its offices, broaden `trigger_events`), the new criteria take effect automatically on the next data update, the watch re-evaluates with its current shape going forward.

## Create up to 100 at once

```ts theme={null}
await signa.watches.bulk({ watches: [w1, w2, w3, /* ... */] });
```

The whole batch validates upfront. Partial failures don't insert, it's all or nothing.

## Health and proof of monitoring

Every watch carries an honest `health` block so that "no alert" is provably distinct from "not looking". `GET /v1/watches/{id}` returns the full object; `GET /v1/watches` returns just `health_status` per row (the full object would cost an extra query per row).

```json theme={null}
{
  "id": "wat_01HK...",
  "health_status": "healthy",
  "health": {
    "status": "healthy",
    "evaluated_at": "2026-07-07T09:12:00Z",
    "evaluations_30d": 61,
    "issues": [],
    "offices": [
      {
        "office_code": "US",
        "status": "ok",
        "coverage_through": "2026-07-06T23:59:59.999Z",
        "coverage_basis": "source_dates",
        "last_evaluated_at": "2026-07-07T09:12:00Z",
        "evaluations_30d": 30,
        "issue": null,
        "last_error": null
      }
    ]
  }
}
```

### Health states

The watch-level `status` is the worst state across its in-scope offices. Precedence, highest first: paused, unsupported, degraded, lagging, pending, healthy.

| Status        | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                        | What to do                                                    |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `healthy`     | Every in-scope office is evaluated and current.                                                                                                                                                                                                                                                                                                                                                                                                | Nothing.                                                      |
| `pending`     | Active watch, not yet evaluated, still within the office refresh window. Also shown right after you change a watch's `query` (per-office `issue` is `config_changed_pending`): the displayed coverage still describes the previous configuration, and the updated query has not been evaluated yet.                                                                                                                                            | Wait for the next evaluation.                                 |
| `lagging`     | Coverage has stopped advancing beyond the expected window. Per-office `issue` is `office_lagging` (ingestion is behind, or an evaluated office has no stated coverage) or `evaluation_backlog` (a newer indexed run has not been evaluated yet, even if the shown coverage looks recent). `never_evaluated` means an active watch aged past the window without a first evaluation, counted from when the office became available to the watch. | Usually resolves on its own. If it persists, contact support. |
| `degraded`    | The watch itself needs attention. Per-office `issue` is `failing` (repeated evaluation errors) or `unresolved_target` (an entity filter no longer resolves).                                                                                                                                                                                                                                                                                   | Check the entity filter or the stored query.                  |
| `unsupported` | The watch scope includes an office Signa does not yet ingest (per-office `issue` is `unsupported_office`). A watch scoped only to offices we do not cover shows this, never `healthy` with zero alerts.                                                                                                                                                                                                                                        | Remove the office from scope, or wait until we cover it.      |
| `paused`      | The watch is paused. Coverage is shown frozen at its last value; no new evaluation happens.                                                                                                                                                                                                                                                                                                                                                    | Resume the watch to continue monitoring.                      |

`issues` at the watch level is the deduplicated set of the per-office issue slugs.

<Note>
  **`coverage_through` is office-data time, not wall-clock.** It is the timestamp of the office's own published data that the watch has been evaluated through, frozen at evaluation time. It never advances just because time passed. `last_evaluated_at` is the separate wall-clock instant of the last evaluation. When an office lags, `coverage_through` stops advancing while `last_evaluated_at` may keep moving. `coverage_basis` tells you how strong the claim is: `source_dates` (strongest, the office's own published dates) beats `date_range`, which beats `run_completed` (weakest, "the data available to us as of run completion").
</Note>

<Warning>
  Health certifies that evaluation occurred against the stated office-data horizon. It does not certify that every relevant mark was surfaced. What a watch catches is bounded by its search strategy and by connector coverage. Health is proof that we looked, and of the data horizon we looked through, not a guarantee of exhaustive recall.
</Warning>

## What's not allowed in `query`

These are rejected with `400`:

* **DSL/presentation keys** (anywhere in the query, at any depth): `function_score`, `script_score`, `script`, `sort`, `cursor`, `aggregations`, `aggs`, `highlight`. Scripting and presentation concerns don't belong in a saved monitor.
* **`query.match` in any form**, both the object form (`match: {"nice_classes": [9]}`) and the string form (`match: "fuzzy"`). Matching is driven by `watch_type` plus `q` / `strategies` / `min_match_tier` (similarity) and the scoping fields under `filters`.
* **Unknown `filters` keys**, including snake\_case typos of valid keys (`nice_classes` instead of `niceClasses`). See [the full key list](#filters-the-full-key-list).
* **Unknown `trigger_events` values** (anything outside the five listed in [`trigger_events`](#trigger_events)) and empty `trigger_events` arrays.

## Common errors

| Status | Cause                                                                                                                                                                                                                       |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid query DSL, stop words in `q`, more than 20 keywords, a forbidden key, a missing required field for the selected `watch_type`, both `entityId` and `entityGroup` supplied, or an entity target that doesn't resolve. |
| `409`  | Your plan's watch limit has been reached.                                                                                                                                                                                   |
| `413`  | `query` exceeds 256 KB.                                                                                                                                                                                                     |
| `422`  | `entity_too_large` — the `entityGroup` corporate family exceeds the relationship-graph walk limit. Use `entityId` on a specific family member instead.                                                                      |

See [Create Watch](/api-reference/monitoring/watches/create) for the full error schema.
