Preview Watch
curl --request POST \
--url https://api.signa.so/v1/watches/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"query": {},
"trial_window_days": 123,
"count_only": true,
"result_limit": 123
}
'import requests
url = "https://api.signa.so/v1/watches/preview"
payload = {
"query": {},
"trial_window_days": 123,
"count_only": True,
"result_limit": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({query: {}, trial_window_days: 123, count_only: true, result_limit: 123})
};
fetch('https://api.signa.so/v1/watches/preview', 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/preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => [
],
'trial_window_days' => 123,
'count_only' => true,
'result_limit' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/watches/preview"
payload := strings.NewReader("{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.signa.so/v1/watches/preview")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/watches/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}"
response = http.request(request)
puts response.read_body{
"object": "watch_preview",
"estimated_match_count": 17,
"trial_window_days": 30,
"request_id": "req_8mQ2vXpL"
}
{
"object": "watch_preview",
"estimated_match_count": 842,
"estimate_basis": "query_upper_bound",
"partial": true,
"trial_window_days": 30,
"results": [],
"has_more": false,
"result_limit": 20,
"request_id": "req_4nRvXq2T"
}
Watches
Preview Watch
Dry-run a watch query: get a match count without creating a watch
POST
/
v1
/
watches
/
preview
Preview Watch
curl --request POST \
--url https://api.signa.so/v1/watches/preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"query": {},
"trial_window_days": 123,
"count_only": true,
"result_limit": 123
}
'import requests
url = "https://api.signa.so/v1/watches/preview"
payload = {
"query": {},
"trial_window_days": 123,
"count_only": True,
"result_limit": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({query: {}, trial_window_days: 123, count_only: true, result_limit: 123})
};
fetch('https://api.signa.so/v1/watches/preview', 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/preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => [
],
'trial_window_days' => 123,
'count_only' => true,
'result_limit' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.signa.so/v1/watches/preview"
payload := strings.NewReader("{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.signa.so/v1/watches/preview")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.signa.so/v1/watches/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": {},\n \"trial_window_days\": 123,\n \"count_only\": true,\n \"result_limit\": 123\n}"
response = http.request(request)
puts response.read_body{
"object": "watch_preview",
"estimated_match_count": 17,
"trial_window_days": 30,
"request_id": "req_8mQ2vXpL"
}
{
"object": "watch_preview",
"estimated_match_count": 842,
"estimate_basis": "query_upper_bound",
"partial": true,
"trial_window_days": 30,
"results": [],
"has_more": false,
"result_limit": 20,
"request_id": "req_4nRvXq2T"
}
Overview
Returns the number of trademarks that would have alerted if this query had been a live watch over the lasttrial_window_days (default 7). The preview evaluates the whole watch query:
q, strategies, min_match_tier, filters and trigger_events, with the same match gate
a live watch uses. Use it before Create Watch to
estimate volume and tune strategies / min_match_tier for similarity watches.
For a watch with a text query, the preview searches for q first and keeps the matches that
changed in the window, so results are ordered by relevance and every row matched q.
Requires the portfolios:manage scope.
Preview is a read-shaped operation, so the
Idempotency-Key header is not required. Sending
one, as in the example below, is always safe.Body Parameters
object
required
Same DSL as Create Watch, see
the query reference. ID-bearing filters
(
filters.trademarkIds, filters.ownerId, …) accept the same tm_* / own_* prefixed
forms as create.integer
Backtest window in days (1-365). Default 7.
boolean
Skip the matching marks and return just
estimated_match_count. Default false.integer
Page size for
results (1-50). Default 20. Ignored when count_only is true.Response
string
Always
"watch_preview".integer
Trademarks that would have alerted in the trial window.
array
A page of the actual matching trademarks, in the same summary shape as search results. Omitted when
count_only is true.boolean
Whether more matches exist beyond
results. Omitted when count_only is true.integer
Effective page size used for
results. Omitted when count_only is true.string
Present only when
estimated_match_count is not an exact count. Absent when it is exact.lower_bound: the search timed out, more than 10,000 marks matchedqin the window, the check against the window’s changes timed out, or the re-check of marks changed in the last 6 hours (changes the search index may not reflect yet) could not finish. The count is the matches the preview verified (0 if none); the real number can only be higher.query_upper_bound: the time budget ran out after the search but before the check against the window’s changes. The count is the marks that matchedqin the window plus the marks changed in the last 6 hours that the search index may not reflect yet; the real number can only be lower.candidacy_upper_bound: a watch without a text query could not be fully evaluated (search unreachable, a very large set of changes, or the time budget ran out). The count is the number of changed marks in the window.
results always holds verified matches only.boolean
Present and
true when the preview did not finish: the time budget ran out, the search timed out
or lost a shard, or the page of results could not be loaded (then results is empty and
has_more is false, but the count stands). The count and estimate_basis describe what was
evaluated.integer
Echo of the requested window.
string
Request identifier.
Latency and limits
Preview runs synchronously with a server-side time budget of about 20 seconds, and each database read inside it is capped at 10 seconds.mark and owner previews and narrow class previews
typically complete in a few seconds; similarity previews over broad scopes and long windows are
the heaviest and can approach the 20 second budget. A watch without a text query is scoped in the
database first, so a broad one (for example a class watch on one class in US over 30 days) can
hit the 10 second read cap and return 504 after about 10 seconds.
- For a watch with a text query, a timeout returns
200withpartial: true, the verified count and anestimate_basis. If search is unavailable the response is503 service_unavailable(retryable). - For a watch without a text query, a budget that runs out after candidates were found returns
200withpartial: true; one that runs out before any usable result exists returns504(see Errors). - Concurrent previews are limited per organization; exceeding the limit returns
429with aRetry-Afterheader. See Rate limits for header semantics.
Errors
| Status | type | When |
|---|---|---|
| 400 | validation_error | Invalid query (see Create Watch) |
| 413 | payload_too_large | query payload exceeds 256 KB |
| 429 | rate_limited | Another preview is already running for your organization, or the server’s preview capacity is saturated. Honor Retry-After (about 20 seconds); the SDK retries automatically. |
| 503 | service_unavailable | Search is unavailable, so a watch with a text query cannot be evaluated. Retry after Retry-After. |
| 504 | preview_timeout | A watch without a text query: the time budget expired, or a database read hit its 10 second cap, before any usable result existed. The response carries retryable: false; narrow the query (fewer offices, shorter trial_window_days, tighter filters) instead of retrying. |
504 preview_timeout
{
"error": {
"type": "preview_timeout",
"title": "Preview Timeout",
"status": 504,
"detail": "The preview could not produce a result within the server-side time budget.",
"suggestion": "Narrow the watch query, add office or jurisdiction filters, reduce trial_window_days, or scope filters.trademarkIds, and retry.",
"retryable": false
},
"request_id": "req_8mQ2vXpL"
}
Code Examples
curl -X POST "https://api.signa.so/v1/watches/preview" \
-H "Authorization: Bearer sig_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: preview-owner-watch-2026-06-12" \
-d '{
"query": {
"version": "v2",
"filters": { "ownerId": "own_7pQmX3Lv" }
},
"trial_window_days": 30
}'
import { Signa } from "@signa-so/sdk";
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
const preview = await signa.watches.preview({
query: {
version: "v2",
filters: { ownerId: "own_7pQmX3Lv" },
},
trial_window_days: 30,
});
console.log(`${preview.estimated_match_count} alerts in the last 30 days`);
{
"object": "watch_preview",
"estimated_match_count": 17,
"trial_window_days": 30,
"request_id": "req_8mQ2vXpL"
}
{
"object": "watch_preview",
"estimated_match_count": 842,
"estimate_basis": "query_upper_bound",
"partial": true,
"trial_window_days": 30,
"results": [],
"has_more": false,
"result_limit": 20,
"request_id": "req_4nRvXq2T"
}
Related Endpoints
- Create Watch - turn a preview into a live watch
- Bulk Create Watches - create up to 100 watches at once
- Rate limits - request and concurrency limits