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

# Filters Overview

> How Pubrio search filters work — the unified filter engine, AND/OR semantics, and when to use which.

Pubrio's search endpoints (`/companies/search`, `/people/search`, `/companies/advertisements/search`) share a single filter engine. You compose a request body once and the same rules apply across endpoints — including the way multi-value filters combine, how locations are matched, and how you override the default operator with `filter_conditions`.

## Why a unified filter engine?

<CardGroup cols={2}>
  <Card title="One schema, three endpoints" icon="arrows-rotate">
    Company-level filters like `technologies`, `verticals`, and `founded_dates` work identically on `/companies/search`, `/people/search`, and inside Monitor `company_filters` — you learn them once.
  </Card>

  <Card title="Per-filter AND/OR" icon="code-merge">
    The default is OR (match any). Promote individual filters to AND (match all) by adding one entry to `filter_conditions` — without touching the rest of the body.
  </Card>

  <Card title="Postgres-native operators" icon="database">
    Array filters compile to native Postgres operators — `&&` (overlap) for OR, `@>` (contains) for AND. Index-friendly, no application-side post-filtering.
  </Card>

  <Card title="Same filters in Monitors" icon="bell">
    The `company_filters` block in [Monitors](/en/developer-guides/introduction) accepts the same shape, so a working search payload is also a working monitor payload.
  </Card>
</CardGroup>

***

## Anatomy of a search request

Every search request is built from three layers in the same JSON body:

| Layer              | Where it lives                                                                    | Examples                                                                       |
| ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| People filters     | top-level keys                                                                    | `people_titles`, `management_levels`, `departments`, `people_locations`        |
| Company filters    | nested under `company_filters: {...}` (recommended) — also accepted at top level  | `technologies`, `verticals`, `founded_dates`, `employees`, `company_locations` |
| Operator overrides | `filter_conditions` array (inside `company_filters` when overriding company keys) | `[{ "key": "technologies", "operator": "and" }]`                               |

A minimal `/people/search` request that uses all three layers:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.pubrio.com/people/search \
    -H "Content-Type: application/json" \
    -H "pubrio-api-key: YOUR_API_KEY" \
    -d '{
      "people_titles": ["VP of Engineering", "CTO"],
      "company_filters": {
        "technologies": ["Kubernetes", "Docker"],
        "is_enable_similarity_search": true,
        "company_locations": ["US"]
      },
      "per_page": 25,
      "page": 1
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.pubrio.com/people/search",
      headers={
          "Content-Type": "application/json",
          "pubrio-api-key": "YOUR_API_KEY",
      },
      json={
          "people_titles": ["VP of Engineering", "CTO"],
          "company_filters": {
              "technologies": ["Kubernetes", "Docker"],
              "is_enable_similarity_search": True,
              "company_locations": ["US"],
          },
          "per_page": 25,
          "page": 1,
      },
  )
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.pubrio.com/people/search", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "pubrio-api-key": "YOUR_API_KEY",
    },
    body: JSON.stringify({
      people_titles: ["VP of Engineering", "CTO"],
      company_filters: {
        technologies: ["Kubernetes", "Docker"],
        is_enable_similarity_search: true,
        company_locations: ["US"],
      },
      per_page: 25,
      page: 1,
    }),
  });
  console.log(await response.json());
  ```
</CodeGroup>

***

## `company_filters`: keep company-level keys grouped

The `company_filters: {...}` wrapper object is the recommended way to send company-level filters — it visually separates which keys filter the *person* from which filter the *company*, and matches the shape [Monitors](/en/developer-guides/introduction) already use, so payloads transfer cleanly between search and monitor configurations.

Both styles work; the engine flattens the wrapped form to the top level before processing, and **top-level keys win on conflict**:

<CodeGroup>
  ```json Wrapped (recommended) theme={null}
  {
    "people_titles": ["VP of Engineering"],
    "company_filters": {
      "technologies": [37, 152],
      "founded_dates": [2015, 2023],
      "company_locations": ["US"]
    }
  }
  ```

  ```json Flat (also works) theme={null}
  {
    "people_titles": ["VP of Engineering"],
    "technologies": [37, 152],
    "founded_dates": [2015, 2023],
    "company_locations": ["US"]
  }
  ```
</CodeGroup>

When you add a `filter_conditions` override for a company-level key, put it **inside** `company_filters` so it travels with the keys it overrides.

### Same shape on the `/search/similar` variants

`POST /companies/search/similar` and `POST /people/search/similar` accept the **same filter body** as their non-similar counterparts (including the `company_filters` wrapper and `filter_conditions`). Each one adds a similarity step on top:

|                             | What they need extra                                                                                                 | What you get extra                                                                               |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `/companies/search/similar` | A reference company — `domain_search_id`, `domain`, `linkedin_url`, or `domains`                                     | Each result gets a `similarity_score` (float, 0-1) and rows are ordered by similarity descending |
| `/people/search/similar`    | A reference person/title — one of `people_titles`, `people_search_id`, `linkedin_url`, `linkedin_urls`, or `peoples` | Same — `similarity_score` per row, ordered by similarity                                         |

The response envelope is otherwise identical to the standard `search` endpoint. Filters narrow the candidate pool *before* similarity ranking is applied — so combining `company_locations: ["US"]` with `/people/search/similar` returns the closest US-based people to your reference titles, which is the "find more people like X within these constraints" pattern.

<Note>
  Unlike the standard `/search` endpoints, `/search/similar` does **not** return an exact `pagination.total_entries` — the value is capped because similar search ranks results by relevance and only surfaces the top matches. Use similar search to find the *best* matches, not to enumerate every one.
</Note>

***

## AND vs OR — the one decision you make per filter

Multi-value filters (`technologies`, `verticals`, `keywords`, `categories`, …) accept an array. The operator decides what "match" means:

<Tabs>
  <Tab title="OR (default)">
    **Match any value.** Returns rows whose array overlaps with the input.

    ```json theme={null}
    {
      "technologies": ["Python", "PostgreSQL", "Kubernetes"],
      "is_enable_similarity_search": true
    }
    ```

    A company is included if its tech stack contains **at least one** of `Python`, `PostgreSQL`, or `Kubernetes`. Compiles to Postgres `column && ARRAY[...]`.

    Use when: you want broad reach — "interested in *any* of these", "located in *any* of these countries".
  </Tab>

  <Tab title="AND">
    **Match every value.** Returns rows whose array contains every input value.

    ```json theme={null}
    {
      "technologies": [37, 152, 408],
      "filter_conditions": [
        { "key": "technologies", "operator": "and" }
      ]
    }
    ```

    Numeric tag IDs come from `GET /technologies?search_term=python` (and similar). **Don't combine `is_enable_similarity_search: true` with AND on the same key** — similarity expands each free-text term into many tag IDs and `@>` then requires the row to contain all of them, which almost always returns zero.

    A company is included only if its tech stack contains **all of** `[37, 152, 408]`. Compiles to Postgres `column @> ARRAY[...]`.

    Use when: you want precision — "uses *all of* these technologies together", "tagged with *all* of these verticals".
  </Tab>
</Tabs>

<Note>
  Filters not listed in `filter_conditions` use the default operator (OR within an array, AND across distinct filter keys). You only declare the overrides — never the defaults.
</Note>

***

## What you can override

Each endpoint accepts overrides for a different set of keys. The keys come from the OpenAPI enum on each `*_filter_conditions` schema:

<CardGroup cols={3}>
  <Card title="Company endpoint" icon="building" href="/en/api-reference/endpoint/companies/search">
    `company_filter_conditions` keys: `keywords`, `verticals`, `vertical_categories`, `vertical_sub_categories`, `technologies`, `categories`, `advertisement_target_locations`, `advertisement_exclude_target_locations`, `advertisement_search_terms`, `places`, `exclude_places`, `job_exclude_locations`.
  </Card>

  <Card title="People endpoint" icon="user" href="/en/api-reference/endpoint/people/search">
    `people_filter_conditions` keys (delegate to the company engine): `keywords`, `verticals`, `vertical_categories`, `vertical_sub_categories`, `technologies`, `categories`, `places`, `exclude_places`, plus `social_media`.
  </Card>

  <Card title="Ads endpoint" icon="bullhorn" href="/en/api-reference/endpoint/companies/advertisements_search">
    `ads_filter_conditions` keys: `target_locations`, `exclude_target_locations`. Smaller set because ads only filter by impression country.
  </Card>
</CardGroup>

<Tip>
  When using `/people/search`, the `filter_conditions[].key` for company-level locations uses the **bare** name from the company engine — `places`, `exclude_places` — not the prefixed people-API name (`company_places`). See [People + Company Filters](/en/developer-guides/filters/people-with-company-filters#key-remap-reference).
</Tip>

***

## Performance tips

<AccordionGroup>
  <Accordion title="Filter early on indexed columns" icon="bolt">
    Locations, employee buckets, and `founded_dates` are indexed and reduce the candidate set faster than free-text or vertical filters. Combine them with one or two precise filters before reaching for similarity search.
  </Accordion>

  <Accordion title="Don't over-AND large arrays" icon="triangle-exclamation">
    `column @> ARRAY[a, b, c, …]` requires every value to be present. Cardinality grows fast — a 10-tech AND on a category with average 3 tech tags returns near-zero rows and forces a full scan. Prefer 2-4 values per AND filter; switch to OR for exploratory queries.
  </Accordion>

  <Accordion title="Use is_enable_similarity_search for free-text input" icon="wand-magic-sparkles">
    If you can't supply slug IDs (verticals, technologies, categories) and only have free-text strings, set `is_enable_similarity_search: true` and `similarity_score: 0.7`. The engine resolves matches before applying the filter — much cheaper than scanning text.
  </Accordion>

  <Accordion title="Prefer ranges over enum lists for size and revenue" icon="arrows-left-right">
    `employees: [[201, 500], [501, 1000]]` (an array of buckets) and `revenues: [1000000, 5000000]` (a single min/max range) are faster and more idiomatic than long ID lists.
  </Accordion>
</AccordionGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="filter_conditions" icon="code-merge" href="/en/developer-guides/filters/filter-conditions">
    Reference page — every supported key, every default, and copyable AND/OR recipes.
  </Card>

  <Card title="People + Company Filters" icon="users-rectangle" href="/en/developer-guides/filters/people-with-company-filters">
    Use any company filter inside `/people/search`. The headline new feature of the unified engine.
  </Card>

  <Card title="Company Search reference" icon="building" href="/en/api-reference/endpoint/companies/search">
    Full request/response schema for `/companies/search`.
  </Card>

  <Card title="People Search reference" icon="user" href="/en/api-reference/endpoint/people/search">
    Full request/response schema for `/people/search`.
  </Card>
</CardGroup>

<Note>
  Looking for the dashboard-side filtering walkthrough? See [Filtering & Exporting Contacts](/en/knowledge-base/concepts/search-filters) in the Knowledge Base.
</Note>
