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

# Search companies, deals, people, documents, and activities

> Search Metal resources with hybrid, fuzzy, and full-text queries, structured filters, sorting, and paginated results across every core endpoint.

Search returns the most relevant records for a query, ranked by relevance rather than recency. Use it when you want to find records by meaning, keyword, or structured filter, instead of paging through an entire collection.

Each core resource exposes a search endpoint that accepts a `POST` body:

| Resource   | Endpoint                     |
| ---------- | ---------------------------- |
| Companies  | `POST /v1/companies/search`  |
| Deals      | `POST /v1/deals/search`      |
| People     | `POST /v1/people/search`     |
| Documents  | `POST /v1/documents/search`  |
| Activities | `POST /v1/activities/search` |

These endpoints share the same request body shape and the same `{ data, metadata }` response envelope. They differ in the fields and search modes they support, and in the resource type returned in `data`.

The API also exposes `POST /v1/funds/search`, `POST /v1/limited-partners/search`, and `POST /v1/fundraising-processes/search` with the same request body shape. This guide focuses on the core resource endpoints above.

## Making a search request

Send a JSON body with your text, filters, and sort. Pass pagination as query parameters. Results come back under `data`, with pagination details in `metadata`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.metal.ai/v1/companies/search?page=1&limit=20" \
  -H "x-metal-client-id: $METAL_CLIENT_ID" \
  -H "x-metal-api-key: $METAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "industrial automation suppliers",
    "filters": {
      "and": [
        { "field": "company_sector", "operator": "eq", "value": "Industrials" }
      ]
    },
    "sort": [
      { "field": "company_createdAt", "order": "desc" }
    ]
  }'
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": [
    {
      "id": "665f1c2a9b1e4a0012a3b4c5",
      "canonicalName": "Acme Industrials",
      "sector": "Industrials"
    }
  ],
  "metadata": {
    "page": 1,
    "limit": 20,
    "totalCount": 134,
    "totalPages": 7
  }
}
```

## Request body

| Field      | Type    | Description                                                                                                                                                                                                                                                |
| ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`     | string  | Free-text query. Ranked against a resource's searchable text fields.                                                                                                                                                                                       |
| `filters`  | object  | Structured filters. Combine with `and` / `or` arrays of filter clauses.                                                                                                                                                                                    |
| `sort`     | array   | Ordered list of `{ "field": "<name>", "order": "asc" \| "desc" }` entries.                                                                                                                                                                                 |
| `hybrid`   | boolean | Blend semantic and keyword ranking for company search. People and activities ignore this flag. Deals ignore it when `text` is present, but must keep it `false` when `text` is empty. Document search enables hybrid automatically when `text` is present. |
| `fullText` | boolean | Enable word-level matching on endpoints that support it, including activity search. Document search ignores this flag because `text` already uses its hybrid query path.                                                                                   |
| `fuzzy`    | boolean | Enable approximate text matching on supported keyword-search paths. Document search and company hybrid search ignore this flag; activity search uses it only when `fullText` is `false`.                                                                   |

For deterministic, filter-only queries, leave `text` empty and `hybrid`, `fuzzy`, and `fullText` set to `false`.

### Filters

Filters are structured predicates over a resource's indexed fields. Each clause names a `field`, an `operator`, and a `value`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "filters": {
    "and": [
      { "field": "activity_type", "operator": "eq", "value": "Meeting" },
      { "field": "activity_startedAt", "operator": "gte", "value": "2026-01-01T00:00:00Z" }
    ],
    "or": [
      { "field": "activity_participantScale", "operator": "in", "value": ["one_on_one", "group"] }
    ]
  }
}
```

Nest additional `and` / `or` objects inside a clause to build compound expressions.

### Operators

| Field kind             | Operators                                          | Value                                              |
| ---------------------- | -------------------------------------------------- | -------------------------------------------------- |
| String                 | `eq`, `neq`, `match`, `in`, `nin`                  | String; `in` and `nin` take an array               |
| String array           | `eq`, `neq`, `match`, `in`, `nin`                  | `eq`, `neq`, and `match` test individual members   |
| Date                   | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` | RFC3339 timestamp such as `"2026-01-01T00:00:00Z"` |
| String or string-array | `exists`, `missing`                                | Omit `value`                                       |

<Warning>
  `eq` on a string field is **whole-value equality**, not a substring or contains match. `{ "field": "activity_subject", "operator": "eq", "value": "diligence" }` matches only an activity whose subject is exactly `diligence`, and returns nothing when the word appears inside a longer subject.

  For "mentions this word" queries, put the word in `text` and set `fullText: true`. Use `match` for regular expressions. `contains` is not a supported operator.
</Warning>

### Sort

Sorting is stable only when you pass an explicit `sort`. Without one, the search backend returns results in whatever order it produces, which can vary between calls to the same query.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "sort": [
    { "field": "activity_startedAt", "order": "desc" },
    { "field": "activity_id", "order": "asc" }
  ]
}
```

When paging through a large result set, a single sort field is rarely enough — rows that tie on the primary field can shift between pages. Add a stable secondary field such as the resource's `id` to break every tie, and keep the same `sort` on every page.

## Searching activities

`POST /v1/activities/search` returns meetings, calls, emails, and notes filtered server-side. Prefer it over paging through the per-resource activity feeds (for example `GET /v1/companies/{id}/activities`) whenever the question carries a type, date window, participant, or linked resource.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.metal.ai/v1/activities/search?page=1&limit=100" \
  -H "x-metal-client-id: $METAL_CLIENT_ID" \
  -H "x-metal-api-key: $METAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "and": [
        { "field": "activity_type", "operator": "eq", "value": "Meeting" },
        {
          "field": "activity_relatedResources",
          "operator": "eq",
          "value": "fund:665f1c2a9b1e4a0012a3b4c5"
        },
        {
          "field": "activity_startedAt",
          "operator": "gte",
          "value": "2026-01-01T00:00:00Z"
        }
      ]
    },
    "sort": [
      { "field": "activity_startedAt", "order": "desc" },
      { "field": "activity_id", "order": "asc" }
    ]
  }'
```

A few activity-specific rules that trip up first calls:

* `activity_type` is title-cased with spaces. Common built-in values are `"Meeting"`, `"Phone Call"`, `"Expert Call"`, `"Email"`, `"Note"`, and `"Other"`; connected systems may store additional labels. Comparisons are exact, so `"meeting"` will not match `"Meeting"`.
* IR links and person links in `activity_relatedResources` use `<type>:<id>` tokens. Supported public linkage types are `fund`, `limited_partner`, `fundraising_process`, `deal_investor_participation`, and `person`. A fund link is `"fund:665f1c2a9b1e4a0012a3b4c5"`, not a bare ObjectID.
* Deal links use `activity_deals` with a bare deal ID, not `activity_relatedResources`.
* Date fields require a full RFC3339 timestamp such as `"2026-01-01T00:00:00Z"`. A bare date like `"2026-01-01"` fails at query time.
* Word-level subject matching belongs in `text` with `fullText: true`. An `activity_subject` `eq` filter matches the whole subject and silently returns nothing when used as a keyword search.
* `activity_relatedResources` is not a sortable field.

## Searching documents

Document search is the way to query the content your firm has ingested into Metal. A non-empty `text` value automatically uses the hybrid query path across parsed document text.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.metal.ai/v1/documents/search?page=1&limit=10" \
  -H "x-metal-client-id: $METAL_CLIENT_ID" \
  -H "x-metal-api-key: $METAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "text": "customer concentration risk" }'
```

## Search vs. list

<CardGroup cols={2}>
  <Card title="Use search" icon="magnifying-glass">
    When you want relevance or a structured filter: finding records that match a concept, phrase, keyword, or field predicate.
  </Card>

  <Card title="Use list" icon="list">
    When you want completeness: iterating over every record in order. See [Pagination](/guides/pagination).
  </Card>
</CardGroup>

## Pagination in search

Pass `page` and `limit` as query-string parameters. Search responses include a `metadata` object with `page`, `limit`, `totalCount`, and `totalPages`.

`page` defaults to `1` and `limit` defaults to `100`. The supported maximum page size is `400` across these search endpoints. Deal and activity search clamp larger requests to `400`; company, people, and document search reject requests above `400`. The response's `metadata.limit` reports the applied page size, so always page using the metadata values rather than the values you sent.

Deep pagination is capped: `(page - 1) * limit` cannot exceed 10,000. To reach records beyond that offset, narrow the filters into smaller result sets rather than paging further.

### Paging through a full result set

Hold `limit`, the request body, and the explicit `sort` constant across pages, append each response's `data`, and stop once `metadata.page` reaches `metadata.totalPages`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import os
import requests

headers = {
    "x-metal-client-id": os.environ["METAL_CLIENT_ID"],
    "x-metal-api-key": os.environ["METAL_API_KEY"],
    "Content-Type": "application/json",
}

body = {
    "filters": {
        "and": [
            {"field": "activity_type", "operator": "eq", "value": "Meeting"}
        ]
    },
    "sort": [
        {"field": "activity_startedAt", "order": "desc"},
        {"field": "activity_id", "order": "asc"}
    ]
}

page = 1
limit = 400
activities = []

while True:
    res = requests.post(
        "https://api.metal.ai/v1/activities/search",
        headers=headers,
        params={"page": page, "limit": limit},
        json=body,
    )
    res.raise_for_status()
    result = res.json()
    activities.extend(result["data"])
    metadata = result["metadata"]
    if metadata["page"] >= metadata["totalPages"]:
        break

    next_page = metadata["page"] + 1
    if (next_page - 1) * metadata["limit"] > 10_000:
        raise RuntimeError(
            "The result set exceeds the deep-pagination limit; "
            "narrow the filters and retry."
        )
    page = next_page

print(f"Fetched {len(activities)} activities")
```


## Related topics

- [MCP tools reference](/mcp/tools-reference.md)
- [Metal MCP server](/mcp/overview.md)
- [Search activities](/api-reference/activities/search-activities.md)
- [Search people](/api-reference/people/search-people.md)
- [Navigating Metal](/help/navigating.md)
