Watch Diagnostics
curl --request GET \
--url https://api.signa.so/v1/watches/{id}/diagnostics \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.signa.so/v1/watches/{id}/diagnostics"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.signa.so/v1/watches/{id}/diagnostics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/watches/{id}/diagnostics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/watches/{id}/diagnostics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.signa.so/v1/watches/{id}/diagnostics")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/watches/{id}/diagnostics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "US",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.status_changed",
"trigger_event_in_filter": true,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": true,
"reason": "alert fired",
"delivery_mode_effective": "per_alert",
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000001",
"office_code": "US",
"completed_at": "2026-07-06T04:12:00.000Z",
"search_indexed_at": "2026-07-06T04:18:00.000Z"
},
"opensearch_score": null,
"alert_id": "alt_4tYpL2Qn",
"opposition": {
"must_act_by": "2026-09-04",
"recomputed_close": "2026-09-04",
"rule_id": "us_opposition",
"rule_source": "15 U.S.C. § 1063(a)",
"rule_version": "2026-05-06",
"close_adjustment": "unchanged",
"window_status": "open"
},
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_2mR8vNkT"
}
{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "EM",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.updated",
"trigger_event_in_filter": false,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": false,
"reason": "trigger event trademark.updated not in watch.trigger_events",
"delivery_mode_effective": null,
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000002",
"office_code": "EM",
"completed_at": "2026-07-06T02:40:00.000Z",
"search_indexed_at": "2026-07-06T02:45:00.000Z"
},
"opensearch_score": null,
"alert_id": null,
"opposition": null,
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_3vXq7RmT"
}
Watches
Watch Diagnostics
Check whether a watch fired, or should have fired, an alert for a specific trademark
GET
/
v1
/
watches
/
{id}
/
diagnostics
Watch Diagnostics
curl --request GET \
--url https://api.signa.so/v1/watches/{id}/diagnostics \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.signa.so/v1/watches/{id}/diagnostics"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.signa.so/v1/watches/{id}/diagnostics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.signa.so/v1/watches/{id}/diagnostics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/watches/{id}/diagnostics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.signa.so/v1/watches/{id}/diagnostics")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/watches/{id}/diagnostics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "US",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.status_changed",
"trigger_event_in_filter": true,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": true,
"reason": "alert fired",
"delivery_mode_effective": "per_alert",
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000001",
"office_code": "US",
"completed_at": "2026-07-06T04:12:00.000Z",
"search_indexed_at": "2026-07-06T04:18:00.000Z"
},
"opensearch_score": null,
"alert_id": "alt_4tYpL2Qn",
"opposition": {
"must_act_by": "2026-09-04",
"recomputed_close": "2026-09-04",
"rule_id": "us_opposition",
"rule_source": "15 U.S.C. § 1063(a)",
"rule_version": "2026-05-06",
"close_adjustment": "unchanged",
"window_status": "open"
},
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_2mR8vNkT"
}
{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "EM",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.updated",
"trigger_event_in_filter": false,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": false,
"reason": "trigger event trademark.updated not in watch.trigger_events",
"delivery_mode_effective": null,
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000002",
"office_code": "EM",
"completed_at": "2026-07-06T02:40:00.000Z",
"search_indexed_at": "2026-07-06T02:45:00.000Z"
},
"opensearch_score": null,
"alert_id": null,
"opposition": null,
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_3vXq7RmT"
}
Overview
Answers the question every monitoring customer eventually asks: “I expected an alert for this trademark. Why didn’t I get one?” Given a watch and a trademark, this endpoint walks through evaluation step by step and reports where it stopped, or confirms that an alert fired. It’s read-only: calling it never changes anything. Requires theportfolios:manage scope.
Path Parameters
string
required
Watch ID (
wat_*).Query Parameters
string
required
Trademark ID (
tm_*) to check against this watch. A trademark ID from another org returns 404.Response
string
Echoed watch ID (
wat_*).string
Echoed trademark ID (
tm_*).string
Uppercase ST.3 code of the office that issued the trademark (e.g.
US, EM).boolean
true if Signa evaluated this trademark against the watch within the current data window.
false means either the change wasn’t recorded in time, or it has aged past the window. See
reason.boolean
true if this watch’s filters include the trademark’s office.boolean
true if a change record for this trademark exists within the data window. A recorded change
is a prerequisite for a match.string | null
The lifecycle event associated with the match:
trademark.created, trademark.updated,
trademark.status_changed, trademark.retracted, or trademark.corrected.boolean
true if trigger_event_type is included in the watch’s query.trigger_events filter. false
explains a match that was silently dropped.number | null
The persisted search relevance score (
match_score) from the most recent
alert for this (watch, trademark). null when no alert exists or the watch
has no scored (q) clause. Informational only.number | null
The watch’s stored
query.score_threshold, surfaced only for legacy watches
that still carry one. Inert — no longer gates matching, and rejected on
new writes. Prefer min_match_tier. null when unset.'exact' | 'normalized' | 'fuzzy' | 'phonetic' | null
The watch’s effective
query.min_match_tier: which match attribution tiers can fire an alert.
null when the watch doesn’t gate by tier (every tier can fire). See
min_match_tier for tier meanings.boolean
true if an alert exists for this (watch, trademark) pair.string | null
Alert ID (
alt_*) when one fired, otherwise null. Retrieve it with
Get Alert, or cross-reference it against
List webhook deliveries to confirm your
endpoint received it.string
Human-readable explanation for the outcome.
| Value | Meaning |
|---|---|
alert fired | An alert exists for this pair. alert_id is set. |
watch does not include office {code} | This watch’s filters don’t cover the trademark’s office. |
trademark evaluated more than {N} days ago; provenance no longer available | The change is older than the data window; Signa can no longer explain the outcome. |
trademark not in candidacy window for the most recent {office} sync | No change was recorded recently enough for evaluation to consider this trademark. |
trigger event {type} not in watch.trigger_events | The change happened, but its event type is excluded by this watch’s filter. |
would alert but rolled into digest | The watch’s delivery mode resolved to a digest instead of an immediate alert. |
no matching reason available | None of the above applied. Contact support if you see this. |
'per_alert' | 'digest' | null
The delivery mode that applied. Today this is
per_alert or null, since always_per_alert
is the only mode a watch can be created with.object | null
Computed opposition-window state for this trademark.
null whenever no window can be cited:
the trademark has no publication date, no opposition rule is modeled for its office and filing
route, or the window could not be computed and there is no alert row to fall back on.Show opposition
Show opposition
string | null
ISO date: last day to file. When an alert exists this is the value frozen onto that alert at emit time, not a fresh computation.
string | null
The engine’s close for this mark as computed right now.
null when there is no alert (then must_act_by already is the recomputed close) and on the degraded path where the engine threw. When it differs from must_act_by, something moved after the alert was frozen: the office published a closure notice, a rule was corrected, or calendar coverage grew. Both dates are published rather than silently reconciled.string | null
Stable, opaque slug of the opposition rule cited (e.g.
us_opposition), and the join key to List Opposition Rules. Effectively always present when the opposition block is present, since an unmodeled office yields a null block rather than a block with a null rule_id. It is still emitted when the window itself could not be computed (rule_source and rule_version null), so a degraded block stays resolvable; the only path that yields null is that degraded path, when the rule lookup itself came back empty.string | null
Human-readable name of the source the rule cites (e.g.
"15 U.S.C. § 1063(a)"). For display, not identity.string | null
Date the rule was last verified against its sources.
'moved' | 'unchanged' | 'not_checked' | 'month_end_overflow' | null
Whether the close date behind
must_act_by was checked against the office’s holiday calendar, and whether that check moved it. not_checked means no calendar stands behind it: the close falls outside the calendar’s pinned coverage years, the rule carries no calendar, or there is no publication date. A rollover only ever moves a close later, so a not_checked date is correct-or-early. null on the degraded path, where no window was produced at all (alongside a null rule_source / rule_version). It always describes the date this response serves, in this order: the marker frozen onto the alert (must_act_by_adjustment, the best evidence, computed for exactly that stored date); otherwise, if the frozen date disagrees with recomputed_close, not_checked, because the fresh marker describes a different date; otherwise the recomputed marker. Alert rows carry the frozen marker directly as deadline.must_act_by_adjustment on GET /v1/alerts and in the webhook body. Note the deliberate divergence when it reads not_checked: for the 20 days after that close, search buckets the mark as OPEN (a rollover could only have moved the close later) while POST /v1/oppositions/compute reports closed against the served, unverified date. Read the window as possibly still open and confirm with the office. See opposition windows.'open' | 'closed' | 'not_started' | 'unknown' | null
Coarse window status.
object
Retention horizons for the data behind this response. See
retention windows for how they interact with alerts and
webhook deliveries.
string
Request identifier.
Internal evaluation fields
Internal evaluation fields
These fields appear in the response but describe internal evaluation state. Safe to ignore when
debugging alert delivery.
lease_state: internal evaluation state; safe to ignore.evaluation_epoch: internal evaluation state; safe to ignore.replay_epoch_origin: internal evaluation state; safe to ignore.opensearch_score: internal evaluation state; safe to ignore (currently alwaysnull).last_relevant_sync_run: internal evaluation state; safe to ignore.
Errors
| Status | type | When |
|---|---|---|
| 400 | validation_error | trademark_id query parameter missing |
| 403 | forbidden | Caller lacks portfolios:manage |
| 404 | not_found | The watch or trademark doesn’t exist, belongs to another org, or the trademark is out of the watch’s scope with no alert on record. These cases return the same response so an ID guess can’t disclose another org’s data. |
Code Examples
curl "https://api.signa.so/v1/watches/wat_8kLm2nPq/diagnostics?trademark_id=tm_9vXq3Rmt" \
-H "Authorization: Bearer sig_YOUR_KEY"
import { Signa } from "@signa-so/sdk";
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const trace = await signa.watches.diagnostics("wat_8kLm2nPq", {
trademarkId: "tm_9vXq3Rmt",
});
console.log(trace.reason);
{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "US",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.status_changed",
"trigger_event_in_filter": true,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": true,
"reason": "alert fired",
"delivery_mode_effective": "per_alert",
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000001",
"office_code": "US",
"completed_at": "2026-07-06T04:12:00.000Z",
"search_indexed_at": "2026-07-06T04:18:00.000Z"
},
"opensearch_score": null,
"alert_id": "alt_4tYpL2Qn",
"opposition": {
"must_act_by": "2026-09-04",
"recomputed_close": "2026-09-04",
"rule_id": "us_opposition",
"rule_source": "15 U.S.C. § 1063(a)",
"rule_version": "2026-05-06",
"close_adjustment": "unchanged",
"window_status": "open"
},
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_2mR8vNkT"
}
{
"watch_id": "wat_8kLm2nPq",
"trademark_id": "tm_9vXq3Rmt",
"office_code": "EM",
"evaluated": true,
"office_in_scope": true,
"candidacy_passed": true,
"trigger_event_type": "trademark.updated",
"trigger_event_in_filter": false,
"score_threshold": null,
"min_match_tier": null,
"alert_fired": false,
"reason": "trigger event trademark.updated not in watch.trigger_events",
"delivery_mode_effective": null,
"lease_state": "released",
"evaluation_epoch": 0,
"replay_epoch_origin": null,
"last_relevant_sync_run": {
"id": "018f9b2e-0000-7000-8000-000000000002",
"office_code": "EM",
"completed_at": "2026-07-06T02:40:00.000Z",
"search_indexed_at": "2026-07-06T02:45:00.000Z"
},
"opensearch_score": null,
"alert_id": null,
"opposition": null,
"data_window": {
"trademark_changes_retention_days": 90,
"deliveries_retention_days": 30,
"alerts_retention_days": 90,
"diagnostic_freshness_horizon_days": 90
},
"request_id": "req_3vXq7RmT"
}
Related Endpoints
- Retrieve Alert - fetch the alert this diagnosis references
- List webhook deliveries - confirm your endpoint received the alert
- Watches guide - watch types and the query DSL
- Monitoring troubleshooting - retention windows and debugging walkthrough