> ## 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.

# Search Trademarks

> Search, filter, and browse trademarks across all supported offices

## Overview

The canonical endpoint for discovering trademarks. Pass a text query to search by brand name with relevance ranking, or use filters to browse by office, status, class, date, and more. Every request must include at least one filter or a text query (`q`).

## Query Parameters

<ParamField query="q" type="string">
  Search query text (1-500 characters). When provided, results are ranked by relevance using multi-strategy matching (exact + fuzzy by default). Optional: omit for filter-only browsing.
</ParamField>

<ParamField query="strategies" type="string" default="exact,fuzzy">
  Search strategies to apply when `q` is provided, comma-separated. Any combination of `exact`, `fuzzy`, `phonetic`, `prefix`. Defaults to `exact,fuzzy` for fast results. Use `exact,phonetic,fuzzy,prefix` for comprehensive trademark clearance searches. Only applies to the default `similar` match mode.
</ParamField>

<ParamField query="match" type="string" default="similar">
  How `q` is matched against the mark text. `similar` (default) runs the ranked relevance ladder. The deterministic modes — `exact`, `starts_with`, `ends_with`, `contains` — match the folded (case- and accent-insensitive) mark text literally and return results sorted by date, with `relevance_score: null`. See [Match modes](#match-modes).
</ParamField>

<ParamField query="mark_text_not_contains" type="string">
  Exclude marks whose folded text contains this substring (case- and accent-insensitive). Composable with any `match` mode.
</ParamField>

<ParamField query="limit" type="integer" default="20">
  Results per page (1-100).
</ParamField>

<ParamField query="cursor" type="string">
  Opaque pagination cursor from a previous response.
</ParamField>

<ParamField query="include_total" type="boolean" default="false">
  When `true`, includes `pagination.total_count` in the response. Exact up to 10,000 matches; beyond that the count is capped and `pagination.total_count_approximate` is `true`. See [Pagination](/api-reference/pagination#total-count).
</ParamField>

<ParamField query="highlights" type="boolean" default="false">
  When `true`, includes highlight snippets for `mark_text` and `owner_names` fields under the default `similar` match mode. In deterministic match modes (`exact`, `starts_with`, `ends_with`, `contains`), `highlights` is inert.
</ParamField>

<ParamField query="international_registrations" type="string" default="grouped">
  How Madrid International Registrations are presented. `grouped` (default) returns one row per mark/IR, with per-territory coverage folded into that row. `expanded` returns one row per Madrid designation instead. A request that combines `expanded` with something the grouped index can't honor falls back to `grouped` automatically; when that happens, `search_meta.fallback_reason` explains why.
</ParamField>

<ParamField query="include" type="string">
  Comma-separated optional row projections. `full_goods_services` returns full `classifications[].goods_services_text` instead of the truncated (about 280 character) summary text.
</ParamField>

<ParamField query="fields" type="string">
  Sparse top-level field projection, comma-separated (e.g. `?fields=mark_text,status,owners`). `id` and `object` are always retained. Unknown field names, or nested paths like `status.primary`, return `400`.
</ParamField>

### Filters

All filter parameters are accepted at the top level of the query string. Arrays use comma-separated values. Date ranges support `_gte`, `_gt`, `_lte`, and `_lt` suffixes.

<ParamField query="offices" type="string">Uppercase ST.3 office codes, comma-separated (e.g. `?offices=US,EM`). Legacy lowercase codes (e.g. `uspto`, and `eu` for EUIPO) are accepted as permanent aliases.</ParamField>

<ParamField query="jurisdictions" type="string">
  Jurisdiction codes, comma-separated (e.g. `?jurisdictions=US,EU`), selecting **rights that protect, or seek protection, in these territories**. By default this is protection-scope: a request for a country also matches regional rights whose membership covers it, so `jurisdictions=FR` returns French national marks, Madrid designations of France, **and** EU trade marks (a EUTM protects France). Requesting a regional code (`EU`) matches the regional rights themselves (EUTMs and IRs designating the EUIPO), not member-state national marks. Use `territory_match=direct` to restore literal territory-leg matching. This is distinct from `offices`, which filters on the register a mark was filed with.
</ParamField>

<ParamField query="territory_match" type="string" default="protection">
  How `jurisdictions` matches. `protection` (default) applies protection-scope: a country request also matches regional rights whose membership covers it (a EUTM for `jurisdictions=FR`). `direct` matches only literal territory legs (national filings and Madrid designations of the exact territory), reproducing the pre-July-2026 behavior. Inert when no `jurisdictions` filter is present. Also accepted by [Suggest](/api-reference/trademarks/trademark-suggest) and [Image search](/api-reference/trademarks/trademark-image-search).
</ParamField>

<ParamField query="nice_classes" type="string">Nice classification numbers 1-45, comma-separated (e.g. `?nice_classes=9,42`).</ParamField>
<ParamField query="us_design_codes" type="string">USPTO design search codes, comma-separated.</ParamField>
<ParamField query="filing_basis" type="string">Namespaced filing basis codes, comma-separated (e.g. `US:1A,US:44E`).</ParamField>
<ParamField query="us_register_type" type="string">USPTO register type: `principal` or `supplemental`.</ParamField>
<ParamField query="status_primary" type="string">Primary status: `active`, `pending`, `inactive`, `unknown`. Comma-separated for multiple.</ParamField>
<ParamField query="status_stage" type="string">Status stage, comma-separated (e.g. `registered`, `published`, `examining`).</ParamField>
<ParamField query="opposition_status" type="string">Derived opposition window state: `open`, `not_started`, `closed`, or `unknown`.</ParamField>
<ParamField query="opposition_closes_after" type="string">Opposition window close-date lower bound (YYYY-MM-DD).</ParamField>
<ParamField query="opposition_closes_before" type="string">Opposition window close-date upper bound (YYYY-MM-DD).</ParamField>
<ParamField query="seniority_claims" type="string">Seniority claim state: `claimed`, `none`, or `unknown`.</ParamField>
<ParamField query="mark_feature_type" type="string">Mark type: `word`, `figurative`, `combined`, `three_dimensional`.</ParamField>
<ParamField query="filing_route" type="string">Filing route: `direct_national`, `madrid_designation`, `direct_regional`.</ParamField>
<ParamField query="owner_id" type="string">Owner ID (`own_...`). Marks for that single per-office owner record.</ParamField>
<ParamField query="owner_name" type="string">Owner name substring match.</ParamField>

<ParamField query="entity_id" type="string">
  Resolved entity ID (`ent_...`). Returns marks across **all member owners** of the entity (every office): the global-portfolio filter. Accepts an entity id derived for an owner that hasn't been linked to a cross-office entity yet. An entity resolving to more than 10,000 member owners returns `422 entity_too_large`. See [Entities](/api-reference/parties/search-entities).
</ParamField>

<ParamField query="entity_group" type="string">
  Entity GROUP ID (`ent_...`). Returns marks across the whole GLEIF corporate family (root + all descendants): "all Pfizer-group marks". Group-level, never identity. Also bounded by `422 entity_too_large` (see [Errors](/api-reference/errors)).
</ParamField>

<ParamField query="owner_publicly_traded" type="boolean">`true` to return marks whose owner has an active listing association: a confirmed active SEC ticker match, OR an owner whose resolved entity is itself listed or a subsidiary of a listed company. `false` means no confirmed listing, not confirmed private.</ParamField>
<ParamField query="owner_has_lei" type="boolean">`true` to return marks whose owner has a confirmed GLEIF LEI match. `false` means no confirmed LEI match.</ParamField>
<ParamField query="owner_ticker" type="string">Exact owner ticker match, uppercased server-side (e.g. `AAPL`). Ticker matching is subsidiary-inclusive: `owner_ticker=NKE` returns Nike's own marks **and** those of its subsidiaries (for example Converse marks under Nike), because each owner's entity listing ticker, whether direct or inherited from a listed ancestor, folds into this field. Direct-vs-inherited provenance is not exposed in search; it lives on the entity `listing` block (see [Retrieve Entity](/api-reference/parties/retrieve-entity)).</ParamField>
<ParamField query="owner_lei" type="string">Exact owner LEI match, uppercased server-side.</ParamField>
<ParamField query="attorney_id" type="string">Attorney ID (`att_...`).</ParamField>
<ParamField query="firm_id" type="string">Firm ID (`firm_...`).</ParamField>
<ParamField query="filing_date_gte" type="string">Filing date lower bound (YYYY-MM-DD).</ParamField>
<ParamField query="filing_date_lt" type="string">Filing date upper bound (exclusive).</ParamField>
<ParamField query="registration_date_gte" type="string">Registration date lower bound.</ParamField>
<ParamField query="registration_date_lt" type="string">Registration date upper bound.</ParamField>
<ParamField query="expiry_date_gte" type="string">Expiry date lower bound.</ParamField>
<ParamField query="expiry_date_lt" type="string">Expiry date upper bound.</ParamField>
<ParamField query="has_media" type="boolean">`true` to require at least one image.</ParamField>
<ParamField query="is_madrid" type="boolean">`true` to restrict to Madrid Protocol filings.</ParamField>

### Per-filter office support

Filter completeness varies by trademark office because each source publishes different fields. Use [List Offices](/api-reference/reference/list-offices) as the source of truth: each office returns a live `coverage` block with percentages for fields that drive filters and projections, including media, goods/services text, design codes, publication dates, registration data, priority and seniority claims, filing basis, first-use dates, owner links, attorney links, and status effective dates.

`opposition_status` and `opposition_closes_*` derive from indexed opposition-window dates. `opposition_status` uses a UTC calendar day for the open/closed boundary, so within about one day of a window edge it can differ slightly from the office-timezone-precise `opposition_window.status` response field. Use `opposition_window` for exact timing. WIPO Madrid IR publication dates are not yet covered, so IR opposition windows are treated as unknown and the response includes `search_meta.warnings[]` with code `partial_opposition_coverage`. A record with an open-ended opposition window (`window_opens` set but `window_closes` absent) is not currently assigned to the `open`, `not_started`, or `closed` buckets.

`seniority_claims` is currently EUIPO-only with low coverage and always emits `partial_seniority_coverage` when used. `claimed` matches records with known seniority claims, `none` matches EUIPO records that authoritatively report no seniority claims, and `unknown` matches records with no seniority signal.

Grouped-grain rows currently surface `design_codes` for `vienna` and `us_design_search` systems only. Design codes with `system='other'` are omitted at mark grain until a reindex follow-up promotes them.

DB-backed embedded trademark rows, such as portfolio fallback rows that have not been hydrated from search, emit `priority_date: null` and `design_codes: []`. These are search-hydrated projections.

<Expandable title="More filters">
  | Parameter                        | Type    | Description                                                                                                                                |
  | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
  | `office`                         | string  | Single office code shortcut                                                                                                                |
  | `vienna_codes`                   | string  | Vienna figurative codes, comma-separated                                                                                                   |
  | `status_reason`                  | string  | Status reason codes, comma-separated                                                                                                       |
  | `challenge_states`               | string  | Active challenge states, comma-separated                                                                                                   |
  | `mark_legal_category`            | string  | `standard`, `certification`, `collective`                                                                                                  |
  | `right_kind`                     | string  | Right kind (e.g. `trademark`)                                                                                                              |
  | `scope_kind`                     | string  | Territorial scope kind, comma-separated                                                                                                    |
  | `owner_country`                  | string  | Two-letter owner country code                                                                                                              |
  | `goods_services_text`            | string  | Free-text search in G\&S descriptions                                                                                                      |
  | `application_number`             | string  | Exact application number (requires `office`)                                                                                               |
  | `registration_number`            | string  | Exact registration number (requires `office`)                                                                                              |
  | `ir_number`                      | string  | Madrid International Registration number                                                                                                   |
  | `origin_office_code`             | string  | Origin office code for Madrid filings                                                                                                      |
  | `renewal_due_date_gte`           | string  | Renewal due date lower bound                                                                                                               |
  | `renewal_due_date_lt`            | string  | Renewal due date upper bound                                                                                                               |
  | `publication_date_gte`           | string  | Publication date lower bound                                                                                                               |
  | `publication_date_lt`            | string  | Publication date upper bound                                                                                                               |
  | `status_effective_date_gte`      | string  | Status effective date lower bound                                                                                                          |
  | `status_effective_date_lt`       | string  | Status effective date upper bound                                                                                                          |
  | `priority_date_gte`              | string  | Priority date lower bound                                                                                                                  |
  | `priority_date_lt`               | string  | Priority date upper bound                                                                                                                  |
  | `first_use_anywhere_date_gte`    | string  | First use anywhere date lower bound                                                                                                        |
  | `first_use_anywhere_date_lt`     | string  | First use anywhere date upper bound                                                                                                        |
  | `first_use_in_commerce_date_gte` | string  | First use in commerce date lower bound                                                                                                     |
  | `first_use_in_commerce_date_lt`  | string  | First use in commerce date upper bound                                                                                                     |
  | `termination_date_gte`           | string  | Termination date lower bound                                                                                                               |
  | `termination_date_lt`            | string  | Termination date upper bound                                                                                                               |
  | `updated_at_gte`                 | string  | Record updated-at lower bound. Accepts `YYYY-MM-DD` (coerced to start of day UTC) or full ISO 8601 datetime (e.g. `2024-01-15T00:00:00Z`). |
  | `updated_at_lt`                  | string  | Record updated-at upper bound. Accepts `YYYY-MM-DD` or full ISO 8601 datetime.                                                             |
  | `has_proceedings`                | boolean | `true` to require at least one proceeding                                                                                                  |
  | `is_retracted`                   | boolean | `true` to restrict to retracted marks                                                                                                      |
  | `is_series_mark`                 | boolean | `true` to restrict to series marks                                                                                                         |
  | `renewal_due_before`             | string  | Alias for `renewal_due_date_lt`                                                                                                            |
</Expandable>

## Match modes

The `match` parameter controls how `q` is matched against the mark text.

* **`similar`** (default) — ranked, fuzzy matching. Runs the relevance ladder (tune it with `strategies` / `ranking_profile`) and orders results by `relevance_score`. Use this for brand searches and clearance.
* **`exact`**, **`starts_with`**, **`ends_with`**, **`contains`** — deterministic. Each matches the *folded* mark text (case- and accent-insensitive) literally: `exact` requires the whole mark to equal `q`, the others anchor to the start, end, or anywhere in the mark. Results are ordered by date (not relevance), every row has `relevance_score: null`, `highlights` is inert, and `search_meta.strategies_used` is `[]`. Use these when you need predictable, reproducible matching rather than ranking.

The deterministic modes accept a single-character `q` (one non-empty folded char), whereas `similar` requires at least 2 folded chars and `contains` requires at least 3. They also treat the metacharacters `( ) [ ] { } | \` as **literal** text to match, so a query like `mercedes (benz)` searches for that exact string; the ranked `similar` path rejects those same characters with a `400 validation_error` (use a deterministic mode to search for them literally).

`search_meta.match` echoes the mode that was applied on every response (including `similar`).

**`mark_text_not_contains`** excludes any mark whose folded text contains the given substring. It composes with every match mode (including `similar`), so you can, for example, search for `sun` while excluding `sunset`.

### Worked examples

```bash cURL — contains theme={null}
# Every mark whose folded text contains "cola"
curl -G "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=cola" \
  --data-urlencode "match=contains"
```

```bash cURL — exclude a substring theme={null}
# Ranked "sun" search, minus anything containing "sunset"
curl -G "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=sun" \
  --data-urlencode "mark_text_not_contains=sunset"
```

### Validation rules

| Rule                                                                                                                                     | Result if violated     |
| ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| Deterministic modes (`exact`/`starts_with`/`ends_with`/`contains`) require a `q`                                                         | `400 validation_error` |
| Deterministic `q` must be non-empty after folding (so combining-marks-only input is rejected), but a single folded character is accepted | `400 validation_error` |
| `similar` (default) requires a folded `q` of at least 2 characters                                                                       | `400 validation_error` |
| `contains` requires a folded `q` of at least 3 characters                                                                                | `400 validation_error` |
| `strategies` and `ranking_profile` are only valid with `match=similar`                                                                   | `400 validation_error` |
| `mark_text_not_contains` requires at least 3 folded characters (same floor as `contains`, since it builds the same substring wildcard)   | `400 validation_error` |
| `match` must be one of the five allowed values; `mark_text_not_contains` must be a string                                                | `400 validation_error` |

## Response

<ResponseField name="data" type="object[]">Trademark summary records: the slimmer list shape with the fields most useful for result cards. See [Retrieve Trademark](/api-reference/trademarks/retrieve-trademark) for the full record shape returned by single-record lookups.</ResponseField>
<ResponseField name="has_more" type="boolean">Whether more results are available.</ResponseField>
<ResponseField name="pagination.cursor" type="string">Pass this as `?cursor=` to get the next page.</ResponseField>
<ResponseField name="pagination.total_count" type="integer">Total matches. Only present when `include_total=true`. **Canonical location**: read this field across every list endpoint.</ResponseField>
<ResponseField name="pagination.total_count_approximate" type="boolean">Emitted alongside `total_count`. `false` when the count is exact (up to 10,000 matches); `true` when the search index capped the count at 10,000 for a deep search.</ResponseField>

<ResponseField name="search_meta" type="object">
  Query metadata.

  <Expandable title="search_meta fields">
    <ResponseField name="search_meta.search_id" type="string">Unique search identifier.</ResponseField>
    <ResponseField name="search_meta.query" type="string | null">The query text used.</ResponseField>
    <ResponseField name="search_meta.strategies_used" type="string[]">Search strategies that were applied. Empty `[]` for deterministic `match` modes.</ResponseField>
    <ResponseField name="search_meta.match" type="string">The [match mode](#match-modes) that was applied: `similar`, `exact`, `starts_with`, `ends_with`, or `contains`. Echoed on every response.</ResponseField>
    <ResponseField name="search_meta.international_registrations" type="string">The Madrid presentation mode actually served: `grouped` or `expanded`. See the `international_registrations` query parameter above.</ResponseField>
    <ResponseField name="search_meta.territory_match" type="string">The `territory_match` mode actually applied: `protection` (default) or `direct`. Echoed on every response so you can confirm which matching semantics ran.</ResponseField>
    <ResponseField name="search_meta.fallback_reason" type="string | undefined">Present only when the requested `international_registrations` mode couldn't be honored and the response fell back to `grouped`.</ResponseField>
    <ResponseField name="search_meta.warnings" type="object[] | undefined">Non-fatal warnings about the search. Two families share this array: **strategy-skip** warnings (a requested `strategies` value produced no clauses for the query shape, e.g. `strategies=[phonetic]` on a query too short or high-collision — carries `strategy`) and **filter-coverage** warnings (an applied filter has partial index coverage — carries `severity`, `affected_filter`, `affected_offices`, `behavior`). Filter-coverage warnings are currently emitted for `opposition_status` / `opposition_closes_*` (`partial_opposition_coverage`) and `seniority_claims` (`partial_seniority_coverage`).</ResponseField>
    <ResponseField name="search_meta.warnings[].code" type="string">Stable warning code for client handling.</ResponseField>
    <ResponseField name="search_meta.warnings[].message" type="string">Human-readable warning message.</ResponseField>
    <ResponseField name="search_meta.warnings[].strategy" type="string | undefined">Present on strategy-skip warnings: the requested strategy that produced no clauses.</ResponseField>
    <ResponseField name="search_meta.warnings[].severity" type="string | undefined">`info` or `warning`. Present on filter-coverage warnings.</ResponseField>
    <ResponseField name="search_meta.warnings[].affected_filter" type="string | undefined">Filter whose behavior is qualified by the warning. Present on filter-coverage warnings.</ResponseField>
    <ResponseField name="search_meta.warnings[].affected_offices" type="string[] | undefined">Office codes affected by the caveat.</ResponseField>
    <ResponseField name="search_meta.warnings[].behavior" type="string | undefined">Human-readable behavior explanation. Present on filter-coverage warnings.</ResponseField>
    <ResponseField name="search_meta.execution_time_ms" type="integer">Query execution time in milliseconds.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="aggregations" type="object">Faceted bucket counts. Present on `GET` when `aggregations=` is provided and on `POST` when `options.aggregations` is provided.</ResponseField>
<ResponseField name="aggregation_metadata" type="object">Human-readable labels keyed by the public ids in `owner_id`, `attorney_id`, `firm_id`, and `entity_id` aggregation buckets. Omitted when no labels resolve.</ResponseField>
<ResponseField name="relevance_score" type="number | null">Normalized relevance score for the ranked `similar` mode. Present when `q` is provided; `null` for filter-only queries, when `sort` is specified, and for every row under a deterministic `match` mode (`exact`/`starts_with`/`ends_with`/`contains`).</ResponseField>

<ResponseField name="match_explanation" type="object | undefined">
  Explains relevance scoring. Omitted when `sort` is specified or under a deterministic `match` mode.

  <Expandable title="match_explanation fields">
    <ResponseField name="match_explanation.strategies_matched" type="string[]">Array of strategy names that matched (e.g. `["exact", "fuzzy"]`).</ResponseField>
    <ResponseField name="match_explanation.boost_factors" type="object[]">Array of `{factor, weight}` objects describing score adjustments.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="primary_image_url" type="string | null">URL to the primary trademark image. Format: `https://api.signa.so/v1/trademarks/{id}/media/{media_id}`. Only present when `has_media` is true, `null` otherwise.</ResponseField>
<ResponseField name="status.effective_date" type="string | null">Office-reported date when the current status took effect, when available.</ResponseField>
<ResponseField name="publication_date" type="string | null">First publication date for the mark, used as the opposition-window trigger when the office and filing route are modeled.</ResponseField>
<ResponseField name="termination_date" type="string | null">Date the right terminated or ceased, when the office reports it.</ResponseField>
<ResponseField name="priority_date" type="string | null">Earliest priority date projected from priority claims, when available. On DB-backed embedded rows such as portfolio marks not yet in the search index, this search-hydrated-only child-table projection may be `null`.</ResponseField>
<ResponseField name="office_updated_at" type="string | null">The source office's own record timestamp. This is distinct from `updated_at`, which is Signa's sync/index timestamp.</ResponseField>

<ResponseField name="design_codes" type="object[]">
  Design or figurative classification codes. Empty `[]` when none are present. On DB-backed embedded rows such as portfolio marks not yet in the search index, this search-hydrated-only child-table projection may be `[]`.

  <Expandable title="Design code fields">
    <ResponseField name="design_codes[].system" type="string">Code system, such as `vienna` or `us_design_search`.</ResponseField>
    <ResponseField name="design_codes[].code" type="string">Office/system code value.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="opposition_window" type="object | null">
  Computed opposition window for modeled office/route rows. `null` means the office/route is not modeled, the window could not be computed safely, or the grouped row spans multiple territories; `status: "unknown"` means the office/route is modeled but no publication date is available.

  <Expandable title="Opposition window fields">
    <ResponseField name="opposition_window.status" type="string">`not_started`, `open`, `closed`, or `unknown`.</ResponseField>
    <ResponseField name="opposition_window.window_opens" type="string | null">Office-local opening date, or `null` when status is `unknown`.</ResponseField>
    <ResponseField name="opposition_window.window_closes" type="string | null">Office-local closing date, or `null` when status is `unknown`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="classifications" type="object[]">
  Nice classifications on the row.

  <Expandable title="Classification summary fields">
    <ResponseField name="classifications[].nice_class" type="integer">Nice class number (1-45).</ResponseField>
    <ResponseField name="classifications[].goods_services_text" type="string | null">Goods/services description, truncated to about 280 characters unless `include=full_goods_services` is requested.</ResponseField>
    <ResponseField name="classifications[].goods_services_text_truncated" type="boolean">`true` when `goods_services_text` was shortened for this row. Request `include=full_goods_services` or fetch the detail record for the untruncated text.</ResponseField>
    <ResponseField name="classifications[].scope" type="string | undefined">`as_filed` on grouped IR rows, where classifications reflect the filed goods and services rather than a territory-effective scope. Absent on non-IR and leg rows.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="nice_classes" type="integer[]">Compact Nice class numbers derived from `classifications[].nice_class`.</ResponseField>

<ResponseField name="owners" type="object[]">
  Summary-tier owner projections. Always present as an array (empty `[]` when the record has no owners on file).

  <Expandable title="Owner summary fields">
    <ResponseField name="owners[].id" type="string">Owner ID (`own_*`).</ResponseField>
    <ResponseField name="owners[].name" type="string">Owner display name.</ResponseField>
    <ResponseField name="owners[].country_code" type="string | null">Owner country code when available.</ResponseField>
    <ResponseField name="owners[].entity_id" type="string | undefined">Resolved cross-office entity ID (`ent_*`) when available, so you can group owners by entity. Filter with `?entity_id=` to retrieve the entity's whole global portfolio.</ResponseField>
    <ResponseField name="owners[].entity_id_type" type="string | undefined">`resolved` when the owner has been linked to a cross-office entity; `derived` when it has not been linked yet (a stable placeholder ID scoped to this owner).</ResponseField>
    <ResponseField name="owners[].companies" type="object[]">Public company records linked to this owner. Omitted when there are no matched companies.</ResponseField>
    <ResponseField name="owners[].companies[].source" type="string">`sec` or `gleif`.</ResponseField>
    <ResponseField name="owners[].companies[].ticker" type="string | null">Ticker symbol for SEC matches.</ResponseField>
    <ResponseField name="owners[].companies[].exchange" type="string | null">Exchange for SEC matches.</ResponseField>
    <ResponseField name="owners[].companies[].lei" type="string | null">Legal Entity Identifier for GLEIF matches.</ResponseField>
    <ResponseField name="owners[].companies[].entity_status" type="string">`active`, `inactive`, or `delisted`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="coverage" type="object | null">
  Per-territory coverage rollup. Present only on grouped Madrid IR-family rows (`international_registrations=grouped`, the default); `null` on record-grain and direct/regional mark-of-one rows. See [Get Trademark Coverage](/api-reference/trademarks/trademark-coverage) for the full per-territory shape.
</ResponseField>

<ResponseField name="source_records" type="object[] | null">
  WIPO/national reconciliation alternates on grouped IR-family rows; `null` on record-grain and direct/regional mark-of-one rows.
</ResponseField>

<ResponseField name="owners_mixed" type="boolean | null">
  `true` when a grouped IR family's territorial owners resolve to more than one distinct holder; `null` on record-grain and direct/regional mark-of-one rows.
</ResponseField>

<ResponseField name="territory_matches" type="object[]">
  Present only when a `jurisdictions` filter is active under the default `territory_match=protection`. Explains **why** the hit satisfied that filter: each entry maps a requested code to the stored territory it matched on and the basis of the match. Absent under `territory_match=direct` and when no `jurisdictions` filter is applied.

  <Expandable title="territory_matches fields">
    <ResponseField name="territory_matches[].requested" type="string">The `jurisdictions` code the caller requested (e.g. `FR`).</ResponseField>
    <ResponseField name="territory_matches[].matched_via" type="string">The stored territory the hit actually matched on (WIPO's `EM` designation code is rendered `EU`). For a EUTM returned against a `FR` request this is `EU`.</ResponseField>
    <ResponseField name="territory_matches[].basis" type="string">`direct` when the hit carries the requested territory as a literal leg; `regional_membership` when it matched through a regional right whose membership includes the request.</ResponseField>
  </Expandable>
</ResponseField>

### Worked example: a EUTM returned for a French request

`GET /v1/trademarks?q=apple&jurisdictions=FR` runs under the default `territory_match=protection`. A EU trade mark (filed at the EUIPO, `jurisdiction_code: "EU"`) is now returned, because a EUTM protects France, and it carries:

```json theme={null}
{
  "id": "tm_…",
  "mark_text": "APPLE",
  "office_code": "EM",
  "jurisdiction_code": "EU",
  "territory_matches": [
    { "requested": "FR", "matched_via": "EU", "basis": "regional_membership" }
  ]
}
```

A French national mark in the same result set instead carries `{ "requested": "FR", "matched_via": "FR", "basis": "direct" }`. Re-run with `&territory_match=direct` and the EUTM drops out, leaving only literal FR legs (French national filings and Madrid designations of France).

<ResponseExample>
  ```json Response theme={null}
  {
    "object": "list",
    "data": [
      {
        "id": "tm_8kLm2nPq",
        "object": "trademark",
        "mark_text": "NIKE",
        "relevance_score": 95,
        "match_explanation": {
          "strategies_matched": ["exact"],
          "boost_factors": [
            { "factor": "status_active", "weight": 1.2 }
          ]
        },
        "primary_image_url": null,
        "status": { "primary": "active", "stage": "registered" },
        "office_code": "US",
        "jurisdiction_code": "US",
        "filing_date": "1971-02-04",
        "registration_date": "1974-04-16",
        "classifications": [
          { "nice_class": 25, "goods_services_text": "Clothing, footwear, headgear", "goods_services_text_truncated": false }
        ],
        "nice_classes": [25],
        "owners": [
          {
            "id": "own_R3jK9mN2",
            "name": "Nike, Inc.",
            "country_code": "US",
            "entity_id": "ent_Wp8qLd4Z",
            "entity_id_type": "resolved",
            "companies": [
              {
                "source": "sec",
                "ticker": "NKE",
                "exchange": "NYSE",
                "lei": null,
                "entity_status": "active"
              },
              {
                "source": "gleif",
                "ticker": null,
                "exchange": null,
                "lei": "787RXPR0UX0O0XUXPZ81",
                "entity_status": "active"
              }
            ]
          }
        ],
        "coverage": null,
        "source_records": null,
        "owners_mixed": null
      }
    ],
    "has_more": true,
    "pagination": {
      "cursor": "eyJpZCI6ImFiYyJ9",
      "total_count": 142,
      "total_count_approximate": false
    },
    "search_meta": {
      "search_id": "srch_abc123",
      "query": "nike",
      "strategies_used": ["exact", "fuzzy"],
      "international_registrations": "grouped",
      "execution_time_ms": 15
    },
    "request_id": "req_xyz789"
  }
  ```
</ResponseExample>

## Code Examples

<CodeGroup>
  ```bash cURL: search by name theme={null}
  curl -G "https://api.signa.so/v1/trademarks" \
    -H "Authorization: Bearer $SIGNA_API_KEY" \
    --data-urlencode "q=nike" \
    --data-urlencode "offices=US"
  ```

  ```bash cURL: filter by office and class theme={null}
  curl -G "https://api.signa.so/v1/trademarks" \
    -H "Authorization: Bearer $SIGNA_API_KEY" \
    --data-urlencode "offices=EM" \
    --data-urlencode "nice_classes=9,42" \
    --data-urlencode "status_stage=registered"
  ```

  ```bash cURL: clearance search (all strategies) theme={null}
  curl -G "https://api.signa.so/v1/trademarks" \
    -H "Authorization: Bearer $SIGNA_API_KEY" \
    --data-urlencode "q=nova" \
    --data-urlencode "strategies=exact,phonetic,fuzzy,prefix" \
    --data-urlencode "offices=US,EM" \
    --data-urlencode "nice_classes=9"
  ```

  ```bash cURL: public-company owner filter theme={null}
  curl -G "https://api.signa.so/v1/trademarks" \
    -H "Authorization: Bearer $SIGNA_API_KEY" \
    --data-urlencode "owner_publicly_traded=true" \
    --data-urlencode "owner_ticker=AAPL" \
    --data-urlencode "status_stage=registered"
  ```

  ```typescript TypeScript (SDK) theme={null}
  import { Signa } from "@signa-so/sdk";

  const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });

  const results = await signa.trademarks.list({
    q: "nike",
    offices: ["US"],
    nice_classes: [25],
    limit: 10,
  });

  for (const mark of results.data) {
    console.log(mark.mark_text, mark.status.stage);
  }
  ```
</CodeGroup>

## Statistics view

Use TMview-style facet breakdowns when building filter UIs, dashboards, or competitive landscape views that compare trademark counts by office, class, status, or party. They provide the counts needed to show the shape of a result set without downloading every matching record.

Aggregations are available on GET with `?aggregations=nice_classes,office_code`, with semantics identical to POST `options.aggregations`. Add `?aggregations_only=true` to return counts without result documents.

The 15 supported dimensions are `status_stage`, `office_code`, `jurisdiction_code`, `nice_classes`, `filing_year`, `mark_feature_type`, `mark_legal_category`, `filing_route`, `right_kind`, `scope_kind`, `firm_id`, `attorney_id`, `owner_country`, `owner_id`, and `entity_id`.

For id-bearing buckets (`owner_id`, `attorney_id`, `firm_id`, and `entity_id`), bucket keys are prefixed public ids. The top-level `aggregation_metadata` object maps each bucket key to a human-readable display name.

```bash theme={null}
curl "https://api.signa.so/v1/trademarks?jurisdictions=US&aggregations=nice_classes,owner_id" \
  -H "Authorization: Bearer $SIGNA_API_KEY"
```

```json theme={null}
{
  "object": "list",
  "data": [
    { "id": "tm_550e8400-e29b-41d4-a716-446655440000", "mark_text": "ACME CLOUD" }
  ],
  "aggregations": {
    "nice_classes": { "9": 1234, "42": 567 },
    "owner_id": { "own_550e8400-e29b-41d4-a716-446655440001": 84 }
  },
  "aggregation_metadata": {
    "owner_id": { "own_550e8400-e29b-41d4-a716-446655440001": "Acme Corporation" }
  }
}
```

## Sort

By default, results are ranked by **relevance** when `q` is provided. For filter-only queries (no `q`), results are returned in index order (fast, unordered).

To explicitly sort results, use the `sort` parameter:

```
?sort=-filing_date          # newest filings first
?sort=expiry_date           # earliest expiry first
?sort=mark_text             # alphabetical mark text
?sort=owner_name            # alphabetical first owner name
?sort=-filing_date,office_code  # multi-field (max 3)
```

Available sort fields: `filing_date`, `registration_date`, `expiry_date`, `renewal_due_date`, `updated_at`, `publication_date`, `termination_date`, `office_code`, `jurisdiction_code`, `mark_text`, `owner_name`.

<Note>
  When `sort` is specified alongside `q`, relevance scoring is bypassed: results are ordered purely by the sort field(s) and `relevance_score` will be `null`.
</Note>

## Advanced: POST with JSON body

For complex queries with aggregations or long filter lists, use `POST /v1/trademarks` with a JSON body. This accepts the same filters and returns the same response shape.

<Note>
  `POST /v1/trademarks` is idempotency-**exempt** (it is a read-shaped
  search): the `Idempotency-Key` header is not required, and if you send one
  it is not enforced or replayed (the value's format is still validated).
  `GET` does not take one.
</Note>

### POST-only features

* `options.aggregations`: an **array of field names** to aggregate. Returns faceted counts for building filter UIs. Valid values: `status_stage`, `office_code`, `jurisdiction_code`, `nice_classes`, `filing_year`, `mark_feature_type`, `mark_legal_category`, `filing_route`, `right_kind`, `scope_kind`, `firm_id`, `attorney_id`, `owner_country`, `owner_id`, `entity_id`.
* `options.aggregations_only`: return only counts, skip result documents

### POST example

```bash cURL theme={null}
curl -X POST "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "nova",
    "strategies": ["exact", "phonetic", "fuzzy", "prefix"],
    "filters": {
      "offices": ["US", "EM"],
      "nice_classes": [9, 42],
      "status_stage": ["registered"],
      "filing_date": { "gte": "2020-01-01" }
    },
    "options": {
      "aggregations": ["office_code", "nice_classes", "status_stage"]
    },
    "limit": 20
  }'
```

<Expandable title="Full POST body reference">
  | Field                       | Type      | Description                                                                                                                                                                                  |
  | --------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `query`                     | string    | Search query text (1-500 chars). Optional.                                                                                                                                                   |
  | `sort`                      | string    | Sort field(s), same as GET `sort` parameter.                                                                                                                                                 |
  | `strategies`                | string\[] | Search strategies. Default: `["exact", "fuzzy"]`. Only valid with `match: "similar"`.                                                                                                        |
  | `match`                     | string    | [Match mode](#match-modes): `similar` (default), `exact`, `starts_with`, `ends_with`, `contains`. Deterministic modes require `query` and disallow `strategies`/`ranking_profile`.           |
  | `mark_text_not_contains`    | string    | Exclude marks whose folded text contains this substring. Composable with any match mode.                                                                                                     |
  | `filters`                   | object    | Same filters as GET, nested in an object. Arrays use JSON arrays, dates use `{"gte": "...", "lt": "..."}`.                                                                                   |
  | `territory_match`           | string    | Top-level field (not nested in `filters`): `protection` (default) or `direct`. Controls how `filters.jurisdictions` matches. See the [`territory_match`](#query-parameters) query parameter. |
  | `options.aggregations`      | string\[] | Faceted counts across all 15 dimensions listed in Statistics view.                                                                                                                           |
  | `options.aggregations_only` | boolean   | Return only counts, no documents. Default: `false`.                                                                                                                                          |
  | `options.include_total`     | boolean   | Include accurate total count (subject to the same 10,000-match cap as `include_total` on `GET`). Default: `false`.                                                                           |
  | `options.highlights`        | boolean   | Include highlight snippets. Default: `false`.                                                                                                                                                |
  | `include`                   | string\[] | Optional row projections. `full_goods_services` returns full `classifications[].goods_services_text`.                                                                                        |
  | `fields`                    | string\[] | Sparse top-level field projection. `id` and `object` are always retained. Unknown names return `400`.                                                                                        |
  | `limit`                     | integer   | Results per page (0-100). Default: `20`.                                                                                                                                                     |
  | `cursor`                    | string    | Pagination cursor from previous response.                                                                                                                                                    |
</Expandable>

## Errors

| Status | Type               | When                                                                                                                   |
| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| 400    | `validation_error` | No filter or query supplied, invalid date range, unknown sort field                                                    |
| 401    | `unauthorized`     | Missing or invalid API key                                                                                             |
| 422    | `entity_too_large` | `entity_id` or `entity_group` resolved to more members than the endpoint supports; see [Errors](/api-reference/errors) |
| 429    | `rate_limited`     | Rate limit exceeded ([details](/api-reference/rate-limits))                                                            |

## Related Endpoints

* [Retrieve Trademark](/api-reference/trademarks/retrieve-trademark): full detail for a single mark
* [Batch Retrieve](/api-reference/trademarks/batch-trademarks): hydrate known IDs
