Search Worker API
Standalone Cloudflare Worker that provides a server-side search API for zudo-doc, designed for large documentation bases or API consumers.
Overview
The Search Worker is a sub-package at packages/search-worker/ that deploys as a Cloudflare Worker. It uses the same MiniSearch engine and search index as the built-in client-side search, but runs server-side on Cloudflare Workers runtime.
This is useful when:
Your documentation base is too large for comfortable client-side search (the full index must be downloaded to the browser)
You want to provide a search API for external consumers (bots, integrations, CLI tools)
You need server-side search for programmatic access
The Worker fetches search-index.json from your deployed documentation site and caches it in memory with a 5-minute TTL.
Note
The Search Worker is an additive option, not a replacement. The primary search experience remains client-side MiniSearch via the search dialog (Ctrl+K / Cmd+K). See When to Use Each for guidance.
Endpoint
POST /
Content-Type: application/jsonThe Worker responds at its root URL.
Request Body
interface SearchRequest {
query: string;
limit?: number;
}| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search query string. Must be non-empty, max 500 characters. |
limit | number | No | Maximum results to return. Default: 20, max: 100. |
Success Response (200)
interface SearchResponse {
results: SearchResult[];
query: string;
total: number;
}
interface SearchResult {
id: string;
title: string;
url: string;
description: string;
score: number;
}Example:
{
"results": [
{
"id": "guides/adding-pages",
"title": "Adding Pages",
"url": "/docs/guides/adding-pages",
"description": "Learn how to add new documentation pages",
"score": 12.5
}
],
"query": "how to add a page",
"total": 3
}Error Responses
| Status | Condition |
|---|---|
| 400 | Invalid JSON body |
| 400 | query is not a non-empty string |
| 400 | query exceeds 500 character limit |
| 400 | limit is provided but is not a number |
| 404 | Request path is not / |
| 405 | Request method is not POST |
| 415 | Content-Type is not application/json |
| 429 | Rate limit exceeded (includes Retry-After header) |
| 500 | Internal server error (index fetch failed, etc.) |
All error responses use the format { "error": string }.
Note
The Search Worker uses wildcard CORS — every response sends Access-Control-Allow-Origin: *, so any origin can call it. This is intentionally different from the AI Assistant API, which uses a per-origin allowlist (aiChatAllowedOrigins). The Search Worker is an unmetered, opt-in service with no per-call cost to gate, so it does not restrict origins. Do not assume the two endpoints share a CORS policy.
Environment Setup
Variables
Set DOCS_SITE_URL in wrangler.toml to point at your deployed documentation site:
[vars]
DOCS_SITE_URL = "https://your-docs-site.example.com"
RATE_LIMIT_PER_MINUTE = "60"
RATE_LIMIT_PER_DAY = "1000"| Variable | Default | Description |
|---|---|---|
DOCS_SITE_URL | -- | Your deployed documentation site URL |
RATE_LIMIT_PER_MINUTE | 60 | Max requests per IP per minute |
RATE_LIMIT_PER_DAY | 1000 | Max requests per IP per day |
The Worker fetches ${DOCS_ to load the search index.
KV Namespace
Rate limiting uses a Cloudflare KV namespace. Create it before deploying:
cd packages/search-worker
npx wrangler kv namespace create RATE_LIMITUpdate the id in wrangler.toml [[kv_namespaces]] with the returned namespace ID.
Rate Limiting Behavior
The Worker enforces per-IP rate limits using the cf-connecting-ip header provided by Cloudflare.
Best-effort enforcement -- KV reads and writes are not atomic, so concurrent requests from the same IP may slightly exceed the configured limits
Fail-open -- if KV is unavailable (outage, misconfiguration), requests are allowed through. Search availability takes priority over strict rate enforcement
Invalid config -- non-numeric values for
RATE_LIMIT_PER_MINUTEorRATE_LIMIT_PER_DAYfall back to the defaults (60/min, 1000/day)429 response -- includes a
Retry-Afterheader (seconds until the current window resets), exposed via CORS for browser access
Deployment
Manual
cd packages/search-worker
pnpm install
pnpm run deployCI/CD
You can add a GitHub Actions workflow similar to the AI Chat Worker deployment. The workflow should deploy the Worker on push to main when files in packages/search-worker/ change.
Required GitHub secrets:
CLOUDFLARE_API_TOKEN-- Cloudflare API token with Workers write permissionCLOUDFLARE_ACCOUNT_ID-- Your Cloudflare account ID
When to Use Each
| Feature | Client-Side Search (built-in) | Search Worker |
|---|---|---|
| Runtime | Browser (in-memory) | Cloudflare Workers |
| Deployment | Part of the docs site | Independent service |
| Index download | Full index sent to browser | Index stays server-side |
| Best for | Small-to-medium doc bases | Large doc bases, API consumers |
| Setup required | None (enabled by default) | Cloudflare account + KV |
| Latency | Instant (local) | Network round-trip |
| Search config | prefix: true, fuzzy: 0.2, boost: { title: 3, description: 2 } | Same |
Both use the same search index (search-index.json) and the same MiniSearch configuration, so results are consistent.
Request Flow
CORS preflight handling
Method check (POST only) and path check (
/only)JSON parse and query validation (required, max 500 chars)
Client IP hashed with SHA-256 via Web Crypto API
Rate limit check against KV
Fetch
search-index.jsonfrom docs site (cached with 5-minute TTL)MiniSearch query with prefix, fuzzy, and boost settings
Return results
Sub-Package Location
packages/ search- worker/
├── src/
│ ├── index. ts # Worker entry point — routing, validation, CORS
│ ├── cors. ts # CORS header handling
│ ├── rate- limit. ts # Per- IP rate limiting via KV
│ ├── search. ts # MiniSearch index loader + search logic
│ └── types. ts # Type definitions
├── wrangler. toml # Cloudflare Worker configuration
├── package. json
├── tsconfig. json
└── README. md