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

# Searching

> How filters combine, anchored and broad searches, every search parameter, sorting, totals, and why a search can be refused.

[`GET /v1/trademarks`](https://docs.pinakelai.com/api-reference/trademarks/search) searches every available register with structured filters and returns a list of [summaries](https://docs.pinakelai.com/concepts/trademark-record.md#the-summary).

## How filters combine

* **Different parameters are combined with AND.** `jurisdiction=AD&status=registered` returns registered marks in Andorra.
* **Several values of one parameter are combined with OR.** Repeat the parameter (`status=registered&status=pending`) or, for codes, enumerations and classes, separate values with commas (`status=registered,pending`).
* **A name filter and its key twin are one filter.** `owner_name` and `owner_key` (likewise `primary_owner_*`, `representative_*` and `previous_owner_*`) are two spellings of the same filter, so their values are combined with OR, up to 20 together.
* **At least one filter is required.** `jurisdiction` counts, so `jurisdiction=AD` alone lists the whole Andorran register. A request with only `sort`, `limit` or other control parameters gets 400 with the error code `missing-filter`.
* **No `jurisdiction` means every available register.** Registers that are reloading are skipped and listed in `unavailable_jurisdictions`.
* **Unknown parameters are rejected, not ignored.** A misspelt filter gets 400 with a did-you-mean hint, so a typo never silently widens your search.

## Choosing filters

| You have | Use |
| - | - |
| An application or registration number | `number` (or `application_number`, `registration_number`) with `jurisdiction` |
| A Madrid international registration number | `international_registration_number` |
| A priority claim number | `priority_number` |
| The exact mark text | `mark_text` |
| A word or words in the mark | `mark_text_phrase` |
| A company, all registers | `primary_owner_name` or `primary_owner_key` |
| A company, complete within a few registers | `owner_name` or `owner_key` with up to 5 `jurisdiction` codes |
| A law firm or agent | `representative_name` or `representative_key` with up to 5 `jurisdiction` codes |
| A previous owner, for chain of title | `previous_owner_name` or `previous_owner_key` with up to 5 `jurisdiction` codes |
| Goods or services | `nice_class`, and `similar_group_code` in China and Korea |
| A figurative element | `vienna_code` |
| A deadline window | `expiry_date_from` and `expiry_date_to`, `opposition_period_end_date_from` and `_to`, and so on |

## Anchored and broad searches

Some filters identify a small set of records directly. These are **anchor filters**: every number filter, every name and key filter, and every phrase filter. A search with at least one anchor is **anchored**:

* it reads only the matching records, so it is fast at any depth;
* it can sort by registration date or expiry date;
* it can report an exact total.

A search without an anchor (for example `jurisdiction=US&status=registered&nice_class=25`) is **broad**. It scans the selected registers by application date, a window at a time, and returns what each window contains. Broad searches always work, but:

* a page can be short, or even empty, while `has_more` is still `true`. Keep following `next_cursor`; empty continuation pages are not billed;
* the response includes `search_progress.application_date_reached`, the application date the scan has reached. It is `null` once the scan has moved past dated records to those without a usable application date;
* a broad search that examines a very large part of the corpus and finds almost nothing (fewer than 100 matches after about 100 million records examined) stops with 422 [`query-too-broad`](https://docs.pinakelai.com/errors/query-too-broad).

Add an anchor or narrow `jurisdiction` and the date range whenever you can.

## Parameters

### Number and identifier anchors

| Parameter | Matches | Values |
| - | - | - |
| `application_number` | The office's application number | Up to 100 |
| `registration_number` | The office's registration number | Up to 100 |
| `number` | Either number | Up to 100 |
| `international_registration_number` | Records linked to a Madrid international registration, in any register | Up to 20; at most 1,000 matching records |
| `priority_number` | Records claiming this priority number | Up to 20; at most 1,000 matching records |

Numbers are matched exactly in three forms: as you send them, upper-cased, and upper-cased with spaces removed. Letter case and extra spaces do not matter, but send the office's own spacing and punctuation (`VA 2026 00923`, `AL/T/2026/001305`). See [Identifiers](https://docs.pinakelai.com/concepts/identifiers.md).

### Name anchors

| Parameter | Matches | Values |
| - | - | - |
| `mark_text` | Marks whose whole text equals the value, after normalization | Up to 20 |
| `primary_owner_name` | Records whose first owner's name key equals the normalized value | Up to 20 |
| `primary_owner_key` | Records whose first owner has exactly this name key | Up to 20 |
| `owner_name`, `owner_key` | Records where any current owner, holder or applicant matches | Up to 20 together; needs `jurisdiction`, up to 5 codes |
| `representative_name`, `representative_key` | Records where any current representative or correspondent matches | Up to 20 together; needs `jurisdiction`, up to 5 codes |
| `previous_owner_name`, `previous_owner_key` | Records where any previous owner or holder matches | Up to 20 together; needs `jurisdiction`, up to 5 codes |

`*_name` values are normalized like stored names; `*_key` values are compared verbatim. A name parameter and its `*_key` twin are one filter: a record matches if it matches any of their values. See [Parties and name keys](https://docs.pinakelai.com/concepts/parties.md).

### Phrase anchors

| Parameter | Matches | Values |
| - | - | - |
| `mark_text_phrase` | Words in order in the mark text, translation, transliteration or keywords | One value, 1 to 200 characters |
| `owner_name_phrase` | Words in order in a current owner's, holder's or applicant's name | One value; needs `jurisdiction`, up to 5 codes |
| `representative_name_phrase` | Words in order in a current representative's or correspondent's name | One value; needs `jurisdiction`, up to 5 codes |

A phrase must contain at least one letter or digit. See [Text matching](https://docs.pinakelai.com/concepts/text-matching.md) for how words and Chinese characters are matched. `goods_services_phrase` is reserved: it returns 400 with the error code `not-yet-available` in v1.

### Scope and record filters

| Parameter | Matches | Values |
| - | - | - |
| `jurisdiction` | Register codes | Up to 20 (up to 5 with party filters) |
| `office` | The registering office | Up to 20 codes |
| `origin_office` | The office of origin, in the `WO` register | Up to 20 codes |
| `status` | `registered`, `pending`, `abandoned`, `cancelled`, `expired`, `unknown`, `other`, `none` | Any of these |
| `status_category` | `live`, `dead`, `unknown`, `none` | Any of these |
| `mark_feature` | A [feature value](https://docs.pinakelai.com/concepts/trademark-record.md#enumerations), `other` or `none` | Any of these |
| `mark_kind` | A [kind value](https://docs.pinakelai.com/concepts/trademark-record.md#enumerations), `other` or `none` | Any of these |
| `has_image` | Whether the office published an image | `true` or `false` |
| `primary_owner_country` | The first owner's country, ISO 3166-1 alpha-2 | Up to 20 |
| `designated_country` | A Madrid designation, ISO 3166-1 alpha-2 | Up to 20 |

`none` matches records where the office stated no value; `other` matches values without a mapping. See [Status](https://docs.pinakelai.com/concepts/status.md#null-other-and-unknown).

### Classification

| Parameter | Matches | Values |
| - | - | - |
| `nice_class` | Records in any of the Nice classes | 1 to 45, up to 20 |
| `similar_group_code` | Records with any of the similar-group codes | Up to 20 |
| `vienna_code` | A full Vienna code, or every code under a one- or two-part prefix | Up to 20. A prefix needs `jurisdiction`, `nice_class` or `primary_owner_country` too. |

### Dates

Every date has a range pair, `<field>_date_from` and `<field>_date_to`, inclusive, in `YYYY-MM-DD` format:

`application_date_*`, `registration_date_*`, `publication_date_*`, `expiry_date_*`, `renewal_date_*`, `termination_date_*`, `opposition_period_end_date_*` and `status_date_*`.

Either bound may be left out. `from` must not be after `to`. See [Dates](https://docs.pinakelai.com/concepts/dates.md).

### Paging and output

| Parameter | Meaning | Values |
| - | - | - |
| `sort` | Sort order (see below) | Default `-application_date` |
| `limit` | Records per page | 1 to 100, default 25; at most 25 with `include=full` |
| `cursor` | Continue from a previous page's `next_cursor` | Opaque string |
| `include` | `full` returns full `trademark` objects instead of summaries | `full` |
| `include_total` | Adds a `total` count | `true` or `false` |

When you send `cursor`, send the same filters and `sort` again. Only `limit`, `include` and `include_total` may change between pages. See [Pagination](https://docs.pinakelai.com/guides/pagination.md).

## Sorting

| `sort` | Order | Allowed |
| - | - | - |
| `-application_date` | Newest filing first (default) | Always |
| `application_date` | Oldest filing first | Always |
| `-registration_date`, `registration_date` | By registration date | Anchored searches only |
| `-expiry_date`, `expiry_date` | By expiry date | Anchored searches only |

Records with no usable date for the sort field come last in both directions. Ties are broken by jurisdiction and id, so the order is stable. There is no sort by `updated_at`; use the [changes feed](https://docs.pinakelai.com/guides/syncing.md).

## Totals

Add `include_total=true` to get a count with the first page:

```json theme={null}
"total": { "value": 1843, "relation": "eq" }
```

* `"relation": "eq"`: the exact number of matches. Anchored searches return exact totals, and so do searches by jurisdiction alone, jurisdiction plus status or status category, and whole application years.
* `"relation": "gte"`: at least `value` matches; counting stopped early. Broad searches with other filters are counted up to about 10,000.
* `"total": null`: counting did not finish in time. The page itself is still complete.

The total is computed once, for the first page, and repeated on later pages.

## Full records in results

`include=full` returns full `trademark` objects in `data`, with `limit` of at most 25. It saves a round trip when you need every record in full, for example when you copy a small register. When you need full records for only some results, fetch them with [batch retrieve](https://docs.pinakelai.com/api-reference/trademarks/batch.md), which takes 100 ids per call.

## Concurrency

Run at most two searches at the same time per key. A third concurrent search waits up to a second for a free slot, then gets 429 [`rate-limited`](https://docs.pinakelai.com/errors/rate-limited) with `"violated_policies": ["concurrency"]`.

## When a search is refused

All problems with a request are reported together, in one 400 response:

```json theme={null}
{
  "type": "https://docs.pinakelai.com/errors/invalid-request",
  "title": "Invalid request",
  "status": 400,
  "detail": "The request has invalid parameters; see errors.",
  "request_id": "req_01M7Q3ZK8W4N2E6R9T5V1X3B7C",
  "errors": [
    {
      "code": "unknown-parameter",
      "detail": "Unknown parameter mark_name. Did you mean mark_text?",
      "parameter": "mark_name"
    },
    {
      "code": "invalid-parameter",
      "detail": "owner_key needs jurisdiction with at most 5 codes.",
      "parameter": "owner_key"
    }
  ]
}
```

| `errors[].code` | Cause |
| - | - |
| `unknown-parameter` | A parameter that does not exist, with a did-you-mean hint |
| `reserved-parameter` | `q`, which is reserved; use `mark_text_phrase` |
| `missing-filter` | No filter at all |
| `invalid-parameter` | A bad value, too many values, a party filter without `jurisdiction`, a sort that needs an anchor, and similar |
| `query-too-long` | A cursor longer than 6,000 characters, or a query string longer than 32 KB |
| `not-yet-available` | A parameter reserved for a later version, such as `goods_services_phrase` |

Other refusals:

| Response | Cause | What to do |
| - | - | - |
| 422 [`query-too-broad`](https://docs.pinakelai.com/errors/query-too-broad) | A phrase or name matches more than 100,000 records (1,000,000 for exact filters), or a broad search finds almost nothing in a very large scan | Narrow the search; do not retry unchanged |
| 422 [`query-timeout`](https://docs.pinakelai.com/errors/query-timeout) | The search took too long | Narrow the search; do not retry unchanged |
| 503 [`search-index-unavailable`](https://docs.pinakelai.com/errors/search-index-unavailable) | A phrase filter's index is being rebuilt | Retry later, or search without phrase filters |
| 503 [`jurisdiction-unavailable`](https://docs.pinakelai.com/errors/jurisdiction-unavailable) | Every register you asked for is reloading | Retry after `Retry-After` |

## Renamed parameters

If you used Pinakel's earlier record API, three parameters were renamed, and the error hint names the new one: `mark_name` is now `mark_text`, `kind_of_mark` is now `mark_kind`, and `expand` is now `include`. Substring filters (`*_contains`, `*_startswith`) have no v1 equivalent; use the phrase filters.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.