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.
bashGET /api/v1/docs/:projectId/search?q=QUERY
bashcurl "https://api.holydocs.com/api/v1/docs/$PROJECT_ID/search?q=authentication"
bashholydocs api get "/docs/$PROJECT_ID/search?q=authentication"
tsimport { HolyDocs } from '@holydocs/sdk';const client = new HolyDocs(); // public — no api key neededconst { 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" ] } ] }}
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 |
bashcurl "https://api.holydocs.com/api/v1/docs/proj_abc123/search?q=authentication&limit=5"
javascriptconst 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)})`);});
pythonimport requestsresponse = 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.
bashGET /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
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:
textRRF_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.
bashPOST /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" }}
bashcurl -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"}'
javascriptconst 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 |