<!-- source: https://docs.redpine.ai/docs/search -->
# Search

> The core search endpoint: parameters, response shape, and examples.

## Search documents

`POST /api/v1/search/query`

Search documents in a collection and return ranked results. The retrieval mode (dense, sparse, or hybrid: dense + sparse vectors) is determined by the collection's configuration and cannot be overridden by the caller.

A question about the catalogue itself, such as which data sources, publishers or journals your key can reach, sent without `filters`, is answered from the catalogue instead of searched: `results` is empty, the answer is in `discovery`, and nothing is charged. See [Catalogue questions](/docs/collections#catalogue-questions).

### Request body

| Parameter          | Type           | Required                          | Description                                                                                                               |
| ------------------ | -------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `collection`       | string         | One of `collection`/`collections` | Collection name to search. List valid names via [Collections](/docs/collections)                                          |
| `collections`      | string\[]      | One of `collection`/`collections` | Collections to search together (max 5), merged into one ranked list                                                       |
| `query`            | string         | Yes                               | Search query text (max 1000 characters)                                                                                   |
| `limit`            | integer        | No                                | Max results to return (default 10, max 30)                                                                                |
| `filters`          | object \| null | No                                | Metadata filters. A filter that cannot be applied is rejected with `422 INVALID_FILTER`. See [Filtering](/docs/filtering) |
| `include_metadata` | boolean        | No                                | Include metadata in results (default true)                                                                                |
| `include_figures`  | boolean        | No                                | Fetch and include figure images as base64 in metadata.figures\[].image\_data (default false, adds latency)                |
| `image_max_width`  | integer        | No                                | Maximum image width in pixels (default 800, 100-1920)                                                                     |
| `image_max_height` | integer        | No                                | Maximum image height in pixels (default 600, 100-1080)                                                                    |
| `image_quality`    | integer        | No                                | JPEG quality for fetched images (default 75, 1-100)                                                                       |

### Searching multiple collections

Pass `collections` instead of `collection` to search several collections in one request.

<Callout title="Results are merged, not grouped">
  You get back one relevance-ranked list spanning every collection, not one list per collection. Each result's `collection` field reports which one it came from, so sort or split client-side if you need them apart.
</Callout>

<CodeBlockTabs defaultValue="cURL" groupId="lang">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Go">
      Go
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash  
    curl -X POST "https://api.redpine.ai/api/v1/search/query" \
      -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"collections": ["my-collection", "another-collection"], "query": "your search query", "limit": 10}'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python  
    r = client.search(
        "your search query",
        collections=["my-collection", "another-collection"],
        limit=10,
    )
    for hit in r.results:
        print(hit.collection, hit.text[:80])
    ```
  </CodeBlockTab>

  <CodeBlockTab value="TypeScript">
    ```typescript  
    const r = await client.search("your search query", {
      collections: ["my-collection", "another-collection"],
      limit: 10,
    });
    for (const hit of r.results) {
      console.log(hit.collection, hit.text.slice(0, 80));
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Go">
    ```go  
    r, err := client.Search(ctx, "your search query", redpine.SearchOptions{
        Collections: []string{"my-collection", "another-collection"},
        Limit:       10,
    })
    if err != nil {
        return err
    }
    for _, hit := range r.Results {
        fmt.Println(*hit.Collection, hit.Text)
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

### Response fields

| Field                     | Type           | Description                                                                                                                                                        |
| ------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `results`                 | array          | Array of search results with id, text, metadata, and collection                                                                                                    |
| `queryId`                 | string         | Unique query identifier. Use to re-fetch results for free within 7 days via `GET /api/v1/search/results/{queryId}`                                                 |
| `latencyMs`               | integer        | Search latency in milliseconds                                                                                                                                     |
| `filterWarnings`          | array \| null  | Advisory warnings for filter fields with no payload index; omitted when there are none                                                                             |
| `journalMetricExpansions` | array \| null  | How each journal-metric filter condition resolved to ISSNs; omitted when no metric filter was used                                                                 |
| `filtersApplied`          | object \| null | The filters you sent, after validation; omitted when no filters were sent                                                                                          |
| `discovery`               | object \| null | A catalogue answer, set only when the query asked about the catalogue rather than for literature. See [Catalogue questions](/docs/collections#catalogue-questions) |

### Result object

| Field        | Type           | Description                                                                                                                                                         |
| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`         | string         | Chunk/point ID                                                                                                                                                      |
| `text`       | string         | Chunk text content                                                                                                                                                  |
| `metadata`   | object \| null | Chunk metadata (if `include_metadata=true`), including `chunk_index`, the chunk's 0-based position in its document, for ordering or stitching neighbouring passages |
| `collection` | string \| null | Origin collection of this result, as requested. Always populated                                                                                                    |
| `relevance`  | number \| null | How well this passage answers the query, 0 to 1, rounded to three decimals. `null` when the result has no score, including rows cached before this field existed    |

### Example request

<CodeBlockTabs defaultValue="cURL" groupId="lang">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Go">
      Go
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash  
    curl -X POST "https://api.redpine.ai/api/v1/search/query" \
      -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "collection": "my-collection",
        "query": "What are the symptoms of diabetes?",
        "limit": 10,
        "include_metadata": true,
        "include_figures": true,
        "filters": {
          "and": [
            {"field": "publication_date", "gte": "2020-01-01"},
            {"field": "publisher", "eq": "Example Publishing"}
          ]
        }
      }'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python  
    from redpine import Redpine, F

    client = Redpine()  # reads REDPINE_API_KEY

    r = client.search(
        "What are the symptoms of diabetes?",
        collection="my-collection",
        limit=10,
        include_figures=True,
        filters=F("publication_date").gte("2020-01-01") & F("publisher").eq("Example Publishing"),
    )
    for hit in r.results:
        print(hit.metadata["title"], hit.text[:200])
    print(r.query_id, r.latency_ms)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="TypeScript">
    ```typescript  
    import { Redpine, F } from "@redpine-ai/sdk";

    const client = new Redpine(); // reads REDPINE_API_KEY

    const r = await client.search("What are the symptoms of diabetes?", {
      collection: "my-collection",
      limit: 10,
      includeFigures: true,
      filters: F("publication_date").gte("2020-01-01").and(F("publisher").eq("Example Publishing")),
    });
    for (const hit of r.results) {
      console.log(hit.metadata?.title, hit.text.slice(0, 200));
    }
    console.log(r.queryId, r.latencyMs);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Go">
    ```go  
    client, err := redpine.New() // reads REDPINE_API_KEY
    if err != nil {
        return err
    }

    figures := true
    r, err := client.Search(ctx, "What are the symptoms of diabetes?", redpine.SearchOptions{
        Collection:     "my-collection",
        Limit:          10,
        IncludeFigures: &figures,
        Filters: redpine.F("publication_date").Gte("2020-01-01").
            And(redpine.F("publisher").Eq("Example Publishing")),
    })
    if err != nil {
        return err
    }
    for _, hit := range r.Results {
        fmt.Println((*hit.Metadata)["title"], hit.Text)
    }
    fmt.Println(r.QueryId, r.LatencyMs)
    ```
  </CodeBlockTab>
</CodeBlockTabs>

The SDKs accept a raw filter object too, in either format from [Filtering](/docs/filtering), if you already have one built.

### Example response

```json
{
  "results": [
    {
      "id": "abc123",
      "text": "Type 2 diabetes symptoms include increased thirst, frequent urination...",
      "metadata": {
        "title": "Diabetes Overview",
        "chunk_index": 3,
        "figures": [
          {
            "id": "fig1",
            "label": "Figure 1",
            "caption": "Glucose metabolism pathway",
            "image_data": "<base64>"
          }
        ]
      },
      "collection": "my-collection",
      "relevance": 0.812
    }
  ],
  "queryId": "qry_a1b2c3d4e5f6",
  "latencyMs": 42,
  "filtersApplied": {
    "and": [
      {"field": "publication_date", "gte": "2020-01-01"},
      {"field": "publisher", "eq": "Example Publishing"}
    ]
  }
}
```

## Re-fetch results

Every search response includes a `queryId`. Use it to retrieve the same results again without being charged, for up to 7 days after the original search.

`GET /api/v1/search/results/{queryId}`

The request must use the same API key that performed the original search.

**Response:** Identical to the original search response, with `latencyMs: 0`. Includes `X-Cache: hit` and `X-Cache-Expires` headers.

**Errors:**

* `404`: Query ID not found or belongs to a different API key
* `410`: Cached result has expired (past 7-day window)

### Example

<CodeBlockTabs defaultValue="cURL" groupId="lang">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Go">
      Go
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash  
    curl "https://api.redpine.ai/api/v1/search/results/qry_a1b2c3d4e5f6" \
      -H "Authorization: Bearer sk_live_YOUR_API_KEY"
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python  
    again = client.get_results(r.query_id)
    assert again.latency_ms == 0
    ```
  </CodeBlockTab>

  <CodeBlockTab value="TypeScript">
    ```typescript  
    const again = await client.getResults(r.queryId);
    console.log(again.latencyMs); // 0
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Go">
    ```go  
    again, err := client.GetResults(ctx, r.QueryId)
    if err != nil {
        return err
    }
    fmt.Println(again.LatencyMs) // 0
    ```
  </CodeBlockTab>
</CodeBlockTabs>

The re-fetched rows carry the [preview and unlock](/docs/preview-unlock) shape: `locked` is false on rows you have paid for, and the SDKs type the result accordingly. An unknown id raises `NotFound` (Go: `*NotFoundError`); an expired one raises `Expired` (Go: `*ExpiredError`). Neither is retried.

## Filtering

Use the `filters` parameter to narrow search results by metadata fields. Indexed on every collection: `journal`, `publisher`, `keywords`, `publication_date`, `doi`, `issn`, `article_type`, `section`, `isbn`, `open_access`, `chapter_number`, `chapter_title`, and `chapter_authors`. Other result metadata fields have no index: they are matched by scanning where the collection supports it, and rejected with `422 INVALID_FILTER` where it does not. A field that is not a result metadata field, `doc_id` and `license` are rejected with `422 INVALID_FILTER`. `journal_metric.*` thresholds are resolved to the matching ISSNs; see [Filtering](/docs/filtering#journal-metrics).

Full operator list, both request formats, and worked examples: see [Filtering](/docs/filtering).