Creating, listing, and managing watches requires the
portfolios:manage scope on your API key.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 theportfolios:manage scope.
1. Preview the volume
Scope to the US withfilters.jurisdictions: ["US"] (equivalently filters.offices: ["uspto"]; jurisdictions are auto-translated to offices at create time):
trigger_events: ["trademark.created"] (new filings only, drops the update and status-change churn) before creating.
2. Create the watch
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.
3. Register a webhook (push)
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}:
webhook-id header. No receiver yet? Use a tunnel or request-bin pattern 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:POST /v1/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.
Variations
-
US filings for a specific competitor:
watch_type: "owner"withfilters: { ownerId: "own_...", jurisdictions: ["US"] }. -
Confusingly similar US filings:
watch_type: "similarity"withq: "yourmark",strategies: ["exact", "phonetic", "fuzzy"],min_match_tier: "phonetic", andfilters: { jurisdictions: ["US"] }. -
More jurisdictions later:
PATCHthe watch with a full replacementquery, one watch covers the set.PATCHreplaces the entirequeryobject and re-validates it (version, the type’s required filters,trigger_events), so resend every field, not just the one you’re changing:
Pick a watch type
The
query body uses the same vocabulary as trademark search, 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 returns400:
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
400to prevent watches that match too broadly. The current list isthe, 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, andfirmIdaccept prefixed IDs (tm_*,own_*,att_*,firm_*) or raw UUIDs. Wrong-type or malformed IDs return400with the offending index in the field path. - Office codes are case-insensitive:
filters.offices: ["USPTO"]is accepted and stored lowercase (["uspto"]). Each code must be a 2-10 character string that exists inGET /v1/offices. filters.jurisdictions(e.g.["US", "EU"]) is auto-translated tofilters.officesat create time, so either spelling of scope works.entityId/entityGroupacceptent_*ids or raw UUIDs and are mutually exclusive — see Entity watches.ownerTickerandownerPubliclyTradedare subsidiary-inclusive. They share the same public-company/listing fields asGET /v1/trademarks, so a watch withownerTicker: "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: truematches 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); afalseor 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.createdtrademark.updatedtrademark.status_changed
trademark.retractedtrademark.corrected
alert.created webhook delivery carrying the trademark event type in data.event_type — see Receiving trademark.* events for the delivery-side view (envelope shape, the pure-flip rule for retracted / corrected, and scope caveats).
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.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.
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 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.
Use Preview 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. Passnullto 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_referenceon both the RESTAlertresource and thealert.createdwebhook body. Changing it later affects only alerts emitted after the change; already-emitted alerts keep the value they were stamped with.
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 passfilters.jurisdictions: ["US", "EU", "GB"] once and let a single watch cover the set.
Entity watches
An entity (ent_* id, from GET /v1/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 (anent_*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.
- Accepted id forms.
ent_<uuid>or a raw UUID (stored normalized). Wrong-prefix or malformed ids return400. Forwatch_type: "owner",entityIdorentityGroupsatisfies the required-field rule in place ofownerId. - 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) returns422with typeentity_too_largeandreason: "family_graph_too_large"— watch a specific entity in the family withentityIdinstead. - 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.
entityGroupre-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 toentityGroup. - 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
ownerIdwatch.
Worked examples
Track one mark
Track a portfolio
Track a Nice class
Detect a similar mark
Preview before you launch
Dry-run the query against the last N days of data to estimate volume: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):
estimate_basis. When the response carriesestimate_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
429with aRetry-Afterheader. Honor it, the SDK does. preview_timeout(504). If the time budget expires before any usable result exists you get apreview_timeoutenvelope withretryable: false. Narrow the query instead of retrying.- Latency.
class,mark, andownerpreviews complete in a few seconds; broadsimilaritypreviews are the heaviest and may approach the budget. - ID forms. Preview accepts the same prefixed (
tm_*/own_*) or raw-UUID ID filters as create.
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
Health and proof of monitoring
Every watch carries an honesthealth 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).
Health states
The watch-levelstatus is the worst state across its in-scope offices. Precedence, highest first: paused, unsupported, degraded, lagging, pending, healthy.
issues at the watch level is the deduplicated set of the per-office issue slugs.
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”).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.matchin any form, both the object form (match: {"nice_classes": [9]}) and the string form (match: "fuzzy"). Matching is driven bywatch_typeplusq/strategies/min_match_tier(similarity) and the scoping fields underfilters.- Unknown
filterskeys, including snake_case typos of valid keys (nice_classesinstead ofniceClasses). See the full key list. - Unknown
trigger_eventsvalues (anything outside the five listed intrigger_events) and emptytrigger_eventsarrays.
Common errors
See Create Watch for the full error schema.