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

# Search API

REST API endpoints for keyword and semantic search across your documentation.

## Overview

The Search API enables full-text and semantic search across your documentation content. Keyword search uses an inverted index stored in KV for fast lookups. Semantic search uses Vectorize embeddings for meaning-based retrieval. Both can be combined via hybrid search using Reciprocal Rank Fusion (RRF).

**Base path:** `https://api.holydocs.com/api/v1/docs/:projectId/search`

The keyword and semantic search endpoints are **public** and do not require authentication. This allows you to embed search directly in your frontend without exposing API keys. Rate limiting is applied per visitor IP.

## Keyword Search

Search documentation pages using full-text keyword matching. Results are ranked by TF-IDF relevance with boosting for title matches, heading matches, and API endpoint paths.

```bash
GET /api/v1/docs/:projectId/search?q=QUERY
```

Copy as context

**cURL**

```bash
    curl "https://api.holydocs.com/api/v1/docs/$PROJECT_ID/search?q=authentication"
    ```
  **CLI**

```bash
    holydocs api get "/docs/$PROJECT_ID/search?q=authentication"
    ```
  **SDK**

```ts
    import { HolyDocs } from '@holydocs/sdk';
    const client = new HolyDocs(); // public — no api key needed
    const { data } = await client.search.keyword(projectId, { q: 'authentication' });
    ```

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | Project ID or project slug |

### Query Parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `q` | string | Yes | Search query (1-200 characters) |
| `limit` | number | No | Maximum results to return (default: 10, max: 50) |
| `page` | number | No | Page number for paginated results (default: 1) |
| `section` | string | No | Filter results to a specific navigation section |
| `type` | `page` \| `api` \| `changelog` | No | Filter by content type |

### Response

```json
{
  "data": {
    "query": "authentication api key",
    "totalResults": 8,
    "results": [
      {
        "path": "/api/authentication",
        "title": "API Authentication",
        "snippet": "API keys are the recommended method for CLI tools, CI/CD pipelines, and server-to-server integrations. Include the <mark>API key</mark> in the Authorization header...",
        "score": 0.94,
        "section": "API Reference",
        "type": "page",
        "headings": [
          "API Key Authentication",
          "Creating an API Key",
          "Key Scopes"
        ]
      },
      {
        "path": "/quickstart",
        "title": "Quickstart Guide",
        "snippet": "Generate an <mark>API key</mark> from Settings to <mark>authenticate</mark> CLI commands and CI/CD pipelines...",
        "score": 0.71,
        "section": "Getting Started",
        "type": "page",
        "headings": [
          "Authentication Setup"
        ]
      }
    ]
  }
}
```

**Field reference**

| Field | Type | Description |
|---|---|---|
| `query` | string | The original search query |
| `totalResults` | number | Total matching results (before pagination) |
| `results[].path` | string | Page path relative to docs root |
| `results[].title` | string | Page title |
| `results[].snippet` | string | Relevant text excerpt with `<mark>` tags around matched terms |
| `results[].score` | number | Relevance score (0.0 - 1.0) |
| `results[].section` | string | Navigation section the page belongs to |
| `results[].type` | string | Content type: `page`, `api`, or `changelog` |
| `results[].headings` | array | Matched heading anchors within the page |

### Score Boosting

Keyword search applies these relevance boosts:

| Match Location | Boost |
|---|---|
| Page title | 3.0x |
| Heading (h1-h3) | 2.0x |
| API endpoint path (e.g., `GET /users`) | 2.5x |
| OpenAPI operation summary | 2.0x |
| Body text | 1.0x |
| Code block content | 0.5x |

**cURL**

```bash
    curl "https://api.holydocs.com/api/v1/docs/proj_abc123/search?q=authentication&limit=5"
    ```
  **JavaScript**

```javascript
    const projectId = 'proj_abc123';
    const query = encodeURIComponent('authentication api key');

    const response = await fetch(
      `https://api.holydocs.com/api/v1/docs/${projectId}/search?q=${query}&limit=5`
    );
    const { data } = await response.json();

    data.results.forEach(result => {
      console.log(`${result.title} (${result.score.toFixed(2)})`);
    });
    ```
  **Python**

```python
    import requests

    response = requests.get(
        "https://api.holydocs.com/api/v1/docs/proj_abc123/search",
        params={"q": "authentication api key", "limit": 5}
    )
    results = response.json()["data"]["results"]

    for result in results:
        print(f"{result['title']} ({result['score']:.2f})")
    ```

## Semantic Search

Search using natural language meaning rather than exact keyword matches. Queries are embedded using Cloudflare Vectorize and compared against pre-computed page embeddings using cosine similarity.

```bash
GET /api/v1/docs/:projectId/search/semantic?q=QUERY
```

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | Project ID or project slug |

### Query Parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `q` | string | Yes | Natural language search query (1-500 characters) |
| `limit` | number | No | Maximum results (default: 10, max: 30) |
| `threshold` | number | No | Minimum similarity score to include (default: 0.5, range: 0.0-1.0) |

### Response

```json
{
  "data": {
    "query": "how do I protect my docs with a password",
    "totalResults": 5,
    "results": [
      {
        "path": "/sso",
        "title": "Single Sign-On",
        "snippet": "Restrict access to your documentation with SSO. Readers authenticate through your identity provider before viewing any page...",
        "score": 0.89,
        "section": "Enterprise",
        "type": "page"
      },
      {
        "path": "/api/authentication",
        "title": "API Authentication",
        "snippet": "The HolyDocs REST API supports two authentication methods: API keys for programmatic access and Better Auth session tokens for dashboard sessions...",
        "score": 0.76,
        "section": "API Reference",
        "type": "page"
      },
      {
        "path": "/rbac",
        "title": "Role-Based Access Control",
        "snippet": "Define granular permissions for who can view, edit, and manage your documentation project...",
        "score": 0.72,
        "section": "Enterprise",
        "type": "page"
      }
    ]
  }
}
```

Semantic search understands intent, not just keywords. The query "how do I protect my docs with a password" returns SSO and RBAC pages even though neither contains the word "password." This makes it ideal for conversational queries and the AI assistant.

### When to Use Each

**Keyword Search**

Best for exact terms, API endpoint names, error codes, and configuration keys. Fast, deterministic results.
  **Semantic Search**

Best for natural language questions, conceptual queries, and when the reader doesn't know the exact terminology.

## Hybrid Search Scoring

The AI assistant and HolyDocs search UI combine both search methods using **Reciprocal Rank Fusion (RRF)**. Understanding this algorithm helps you optimize your content for maximum discoverability.

### How RRF Works

RRF merges two ranked result lists into a single ranking without requiring comparable scores. For each result appearing in either list, the combined score is:

```
RRF_score = SUM( 1 / (k + rank_i) )
```

Where `k` is a constant (default: 60) and `rank_i` is the result's position in each source list. Results appearing in both lists receive scores from both, naturally boosting pages that are relevant by both keyword match and semantic similarity.

### Example

Consider a query "setting up webhooks":

| Page | Keyword Rank | Semantic Rank | RRF Score |
|---|---|---|---|
| /webhooks | 1 | 2 | 1/61 + 1/62 = 0.0326 |
| /api/authentication | 3 | 8 | 1/63 + 1/68 = 0.0306 |
| /integrations | -- | 1 | 0 + 1/61 = 0.0164 |
| /deployments | 2 | -- | 1/62 + 0 = 0.0161 |

The `/webhooks` page ranks first because it scores well in both keyword and semantic search. Pages appearing in only one list receive a lower combined score.

To maximize discoverability, ensure your pages have both precise keywords (for keyword search) and clear explanatory prose (for semantic search). A page with only code snippets may rank well for keywords but poorly for semantic queries.

## Trigger Reindex

Manually trigger a search index rebuild for your project. This re-processes all pages, updates the keyword index in KV, and regenerates Vectorize embeddings.

```bash
POST /api/v1/docs/:projectId/reindex
```

This endpoint requires authentication with the `projects:write` scope. Reindexing is computationally expensive and subject to plan limits.

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `projectId` | string | Project ID |

### Request Body

```json
{
  "scope": "all"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `scope` | `all` \| `keyword` \| `semantic` | No | Which index to rebuild (default: `all`) |

### Response

```json
{
  "data": {
    "jobId": "idx_abc123",
    "status": "queued",
    "scope": "all",
    "estimatedDuration": 45,
    "createdAt": "2026-04-11T10:00:00Z"
  }
}
```

**cURL**

```bash
    curl -X POST "https://api.holydocs.com/api/v1/docs/proj_abc123/reindex" \
      -H "Authorization: Bearer hd_a1b2c3d4e5f67890a1b2c3d4e5f67890" \
      -H "Content-Type: application/json" \
      -d '{"scope": "all"}'
    ```
  **JavaScript**

```javascript
    const response = await fetch(
      'https://api.holydocs.com/api/v1/docs/proj_abc123/reindex',
      {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer hd_a1b2c3d4e5f67890a1b2c3d4e5f67890',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ scope: 'all' })
      }
    );
    const { data } = await response.json();
    console.log(`Reindex job ${data.jobId} queued (est. ${data.estimatedDuration}s)`);
    ```

### Reindex Limits

| Plan | Manual Reindexes per Day |
|---|---|
| Free | 2 |
| Starter | 5 |
| Pro | 20 |
| Business | 100 |
| Enterprise | Unlimited |

Reindexing happens automatically after every deployment. You only need to trigger manual reindexing if you've made changes via the dashboard editor or D1 config overrides that aren't tied to a deployment.

## Rate Limits

Public search endpoints use IP-based rate limiting:

| Endpoint | Rate Limit |
|---|---|
| Keyword search | 30 requests/minute per IP |
| Semantic search | 20 requests/minute per IP |
| Reindex (authenticated) | Subject to plan limits |

## Error Codes

| Code | Status | Description |
|---|---|---|
| `NOT_FOUND` | 404 | Project not found or has no published content |
| `VALIDATION_ERROR` | 400 | Invalid query parameters (empty query, limit out of range) |
| `LIMIT_EXCEEDED` | 429 | Rate limit or daily reindex limit exceeded |
| `INDEX_NOT_READY` | 503 | Search index is still building after a recent deployment |