What is Headband?#
Headband is the retrieval control plane — the layer between your data, the search engines that index it, and the AI agents that consume it. One API across Meilisearch, Typesense, and Elasticsearch. Pick your engine per index, not per project. Bring your own infra. We don't store your data.
Every project gets automatic API keys, built-in CORS support for browser-side search, and a dashboard for managing indexes, documents, and settings. Run your catalog on Meilisearch, your logs on Elasticsearch, and your autocomplete on Typesense — all under the same project, same API key.
Per-index engines
Mix Meilisearch, Typesense, and Elasticsearch in one project.
Bring your own engine
Point any index at your own cluster. We never store your data.
Built for agents
llms.txt, RAG endpoints, and MCP server -- all shipping today.
Quick Start#
Get up and running in five steps. Replace the example URL with your Headband instance.
Create a project in the dashboard
You'll receive an admin key (hb_adm_) and a search key (hb_src_) automatically.
Create an index
curl -X POST https://your-instance.com/v1/indexes \
-H "Authorization: Bearer hb_adm_YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"uid": "products", "primaryKey": "id"}'Push your documents
curl -X POST https://your-instance.com/v1/documents?index=products&primaryKey=id \
-H "Authorization: Bearer hb_adm_YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '[
{"id": 1, "title": "Widget", "price": 9.99},
{"id": 2, "title": "Gadget", "price": 24.99}
]'Search
curl -X POST https://your-instance.com/v1/search \
-H "Authorization: Bearer hb_src_YOUR_SEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"index": "products", "q": "widget"}'Check task status
curl https://your-instance.com/v1/tasks/2 \
-H "Authorization: Bearer hb_adm_YOUR_ADMIN_KEY"Authentication#
Bearer Header
Include your API key as a Bearer token in the Authorization header on every request:
Authorization: Bearer hb_adm_xxxxx...API Key Types
Headband issues two types of API keys per project. Each key automatically scopes all operations to the project it belongs to.
Admin Key
Prefix: hb_adm_
Full access -- create indexes, push and delete documents, manage settings, and search. Keep this key on your server; never expose it to clients.
Search Key
Prefix: hb_src_
Read-only -- search, list indexes, view settings and stats. Safe for client-side and frontend use.
Permissions
Indexes#
Indexes hold your documents and search configuration. Each index has a unique identifier (uid) and an optional primary key that identifies each document. Indexes are automatically scoped to your project.
/v1/indexesList all indexes in your project.
Auth: admin or search key
Returns: 200
{
"results": [
{
"uid": "products",
"primaryKey": "id",
"createdAt": "2025-01-15T08:30:00Z",
"updatedAt": "2025-01-15T09:12:00Z"
}
]
}/v1/indexesCreate a new index.
Auth: admin key only
Returns: 202
{
"uid": "products",
"primaryKey": "id" // optional
}{
"taskUid": 1,
"indexUid": "products",
"status": "enqueued"
}/v1/indexes/:uidGet information about a single index.
Auth: admin or search key
Returns: 200
{
"uid": "products",
"primaryKey": "id",
"createdAt": "2025-01-15T08:30:00Z",
"updatedAt": "2025-01-15T09:12:00Z"
}/v1/indexes/:uidDelete an index and all its documents.
Auth: admin key only
Returns: 202
{
"taskUid": 4,
"indexUid": "products",
"status": "enqueued"
}Index Settings#
Configure search behavior for an index. Changes to settings are processed asynchronously and may trigger a re-index of your documents.
/v1/indexes/:uid/settingsGet all settings for an index.
Auth: admin or search key
Returns: 200
{
"searchableAttributes": ["*"],
"filterableAttributes": [],
"sortableAttributes": [],
"rankingRules": [
"words", "typo", "proximity",
"attribute", "sort", "exactness"
],
"synonyms": {},
"stopWords": [],
"displayedAttributes": ["*"],
"distinctAttribute": null,
"typoTolerance": {
"enabled": true,
"minWordSizeForTypos": {
"oneTypo": 5,
"twoTypos": 9
}
},
"faceting": {
"maxValuesPerFacet": 100
},
"pagination": {
"maxTotalHits": 1000
}
}/v1/indexes/:uid/settingsUpdate settings for an index. Only include the settings you want to change.
Auth: admin key only
Returns: 202
{
"filterableAttributes": ["category", "price"],
"sortableAttributes": ["price", "createdAt"],
"searchableAttributes": ["title", "description"]
}{
"taskUid": 5,
"indexUid": "products",
"status": "enqueued"
}Available Settings
searchableAttributesstring[]Attributes used for search. Order determines relevance ranking.filterableAttributesstring[]Attributes that can be used in filter expressions.sortableAttributesstring[]Attributes that can be used in sort expressions.rankingRulesstring[]Ordered list of ranking rules applied to search results.synonymsobjectMap of words to their synonyms, e.g. { "phone": ["mobile", "cell"] }.stopWordsstring[]Words ignored during search (e.g. "the", "a", "is").displayedAttributesstring[]Attributes returned in search results. Defaults to all.distinctAttributestring|nullDe-duplicate results by this attribute.typoToleranceobjectConfigure typo tolerance behavior and thresholds.facetingobjectConfigure facet behavior (e.g. maxValuesPerFacet).paginationobjectConfigure pagination limits (e.g. maxTotalHits).embeddersobjectConfigure vector embedders for hybrid/semantic search.Documents#
Documents are JSON objects stored in an index. Headband auto-detects schema from the document structure -- no upfront schema definition required. You can send thousands of documents in a single request for bulk ingestion.
/v1/documents?index=productsList documents in an index with pagination.
Supports limit and offset query parameters for pagination.
Auth: admin or search key
Returns: 200
{
"results": [
{ "id": 1, "title": "MacBook Pro", "price": 1999 },
{ "id": 2, "title": "iPhone 16", "price": 999 }
],
"total": 19547,
"limit": 20,
"offset": 0
}/v1/documents/:id?index=productsGet a single document by its primary key.
Auth: admin or search key
Returns: 200
{
"id": 1,
"title": "MacBook Pro",
"price": 1999,
"category": "laptops"
}/v1/documents?index=products&primaryKey=idAdd or replace documents in an index. If a document with the same primary key already exists, it will be replaced.
Supports bulk upload -- send thousands of documents in one request.
Auth: admin key only
Returns: 202
[
{ "id": 1, "title": "MacBook Pro", "price": 1999, "category": "laptops" },
{ "id": 2, "title": "iPhone 16", "price": 999, "category": "phones" }
]{
"taskUid": 2,
"indexUid": "products",
"status": "enqueued",
"enqueuedAt": "2025-01-15T09:00:00Z"
}/v1/documents?index=productsDelete documents from an index by IDs or by filter.
Auth: admin key only
Returns: 202
// Delete by IDs:
{ "ids": [1, 2] }
// Or delete by filter:
{ "filter": "price > 1000" }{
"taskUid": 3,
"indexUid": "products",
"status": "enqueued"
}Bulk Import
Import large volumes of documents efficiently. The bulk endpoint automatically splits your documents into optimized batches and sends them in parallel, handling payloads of up to 500K documents in a single request.
/v1/bulkImport documents in optimized parallel batches.
Auth: admin key only
Returns: 202
{
"index": "products",
"primaryKey": "id",
"documents": [
{ "id": 1, "title": "Widget", "price": 9.99 },
{ "id": 2, "title": "Gadget", "price": 24.99 }
// ... up to 500K documents
]
}{
"tasks": [
{ "taskUid": 10, "indexUid": "products", "status": "enqueued", "batchNumber": 1, "documentsInBatch": 10000 },
{ "taskUid": 11, "indexUid": "products", "status": "enqueued", "batchNumber": 2, "documentsInBatch": 10000 }
],
"totalDocuments": 20000,
"totalBatches": 2,
"batchSize": 10000
}Parameters
batchSizenumber10000Documents per batch (min 100, max 50000).Streaming Progress
/v1/bulk/streamImport documents with real-time Server-Sent Events progress. Same request body as /v1/bulk.
Use the streaming endpoint for large imports to get real-time progress feedback. Combine with the Tasks API to poll individual batch status after import.
Auth: admin key only
Returns: 200 (SSE stream)
{
"index": "products",
"primaryKey": "id",
"documents": [
{ "id": 1, "title": "Widget", "price": 9.99 },
{ "id": 2, "title": "Gadget", "price": 24.99 }
// ... up to 500K documents
]
}data: {"event":"batch_complete","batchNumber":1,"taskUid":10,"documentsInBatch":10000}
data: {"event":"batch_complete","batchNumber":2,"taskUid":11,"documentsInBatch":10000}
data: {"event":"complete","totalDocuments":20000,"totalBatches":2}Search#
Full-text search with support for filters, faceted search, sorting, highlighting, and hybrid vector search.
/v1/searchSearch an index.
Auth: admin or search key
Returns: 200
{
"index": "products",
"q": "macbook",
"limit": 10,
"offset": 0,
"filter": "price < 2000",
"sort": ["price:asc"],
"facets": ["category"],
"attributesToHighlight": ["title"],
"attributesToRetrieve": ["id", "title", "price"]
}{
"hits": [
{
"id": 1,
"title": "MacBook Pro",
"price": 1999,
"_formatted": {
"title": "<em>MacBook</em> Pro"
}
}
],
"query": "macbook",
"processingTimeMs": 2,
"estimatedTotalHits": 42,
"facetDistribution": {
"category": { "laptops": 12, "phones": 30 }
}
}Parameters
indexstringRequiredThe index uid to search.qstring""The search query string. Empty string returns all documents.limitnumber10Maximum number of hits to return.offsetnumber0Number of hits to skip (for pagination).filterstring""Filter expression, e.g. "price < 2000 AND category = laptops".sortstring[][]Sort order, e.g. ["price:asc", "title:desc"].facetsstring[][]Attributes to compute facet distributions for.attributesToHighlightstring[][]Attributes to wrap matches with <em> tags.attributesToRetrievestring[]["*"]Attributes to include in each hit. Defaults to all.attributesToCropstring[][]Attributes whose values will be cropped around matches.cropLengthnumber10Number of words around a match when cropping.showMatchesPositionbooleanfalseInclude match position information in each hit.showRankingScorebooleanfalseInclude the ranking score in each hit.matchingStrategystring"last""last", "all", or "frequency".hybridobjectnullEnable hybrid search. Example: { semanticRatio: 0.5, embedder: "default" }.vectornumber[]nullVector for nearest-neighbor search (used with hybrid or standalone).Filters
Filter expressions let you narrow search results. Attributes must be listed in filterableAttributes in your index settings before they can be used in filters.
// Simple comparison
"price < 2000"
// Equality
"category = laptops"
// Combined with AND / OR
"price < 2000 AND category = laptops"
"category = laptops OR category = phones"
// IN operator
"category IN [laptops, phones, tablets]"
// Negation
"NOT category = accessories"
// Nested with parentheses
"(price < 500 OR price > 2000) AND category = laptops"Facets
Pass attribute names in the facets array to receive a distribution of values for each attribute. Useful for building filter UIs with counts.
"facetDistribution": {
"category": {
"laptops": 12,
"phones": 30,
"tablets": 8
}
}Sorting
Sort results by one or more attributes. Attributes must be listed in sortableAttributes. Use the format attribute:direction where direction is asc or desc.
"sort": ["price:asc", "title:desc"]Highlighting
Request highlighted versions of attributes to show which parts of the text matched the query. Matched text is wrapped in <em> tags.
"_formatted": {
"title": "<em>MacBook</em> Pro 16-inch"
}Multi-Search#
Execute multiple search queries in a single request. Each query targets an index and accepts the same parameters as /v1/search, using indexUid instead of index.
/v1/multi-searchSearch multiple indexes in a single request.
Each query in the queries array accepts all parameters from the search endpoint, using indexUid instead of index.
Auth: admin or search key
Returns: 200
{
"queries": [
{
"indexUid": "products",
"q": "macbook",
"limit": 5
},
{
"indexUid": "articles",
"q": "macbook review",
"limit": 3,
"attributesToHighlight": ["title"]
}
]
}{
"results": [
{
"indexUid": "products",
"hits": [ ... ],
"query": "macbook",
"processingTimeMs": 1,
"estimatedTotalHits": 12
},
{
"indexUid": "articles",
"hits": [ ... ],
"query": "macbook review",
"processingTimeMs": 2,
"estimatedTotalHits": 5
}
]
}Tasks#
Many operations (index creation, document ingestion, settings updates) are processed asynchronously. These operations return a taskUid you can poll to track progress.
/v1/tasksList all tasks for your project.
Auth: admin or search key
Returns: 200
{
"results": [
{
"uid": 1,
"indexUid": "products",
"status": "succeeded",
"type": "indexCreation",
"enqueuedAt": "2025-01-15T08:30:00Z",
"startedAt": "2025-01-15T08:30:01Z",
"finishedAt": "2025-01-15T08:30:01Z"
},
{
"uid": 2,
"indexUid": "products",
"status": "processing",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2025-01-15T09:00:00Z",
"startedAt": "2025-01-15T09:00:01Z"
}
]
}Parameters
limitnumber20Number of tasks to return.fromnumber--Task UID to start from (for pagination).statusesstring--Comma-separated list of statuses to filter (e.g. "succeeded,failed").typesstring--Comma-separated list of task types to filter.indexUidsstring--Comma-separated list of index UIDs to filter./v1/tasks/:taskIdGet the status and details of a single task.
Task statuses: enqueued, processing, succeeded, failed.
Auth: admin or search key
Returns: 200
{
"uid": 2,
"indexUid": "products",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"details": {
"receivedDocuments": 1500,
"indexedDocuments": 1500
},
"enqueuedAt": "2025-01-15T09:00:00Z",
"startedAt": "2025-01-15T09:00:01Z",
"finishedAt": "2025-01-15T09:00:03Z"
}AI Search (RAG)#
The AI search endpoint returns results pre-formatted for RAG pipelines. Instead of raw engine hits, you get clean text chunks with structured metadata — ready to inject directly into an LLM context window.
AI Search Endpoint
/v1/ai/searchSearch an index and return RAG-formatted results with clean text, metadata, and source attribution.
Auth: admin or search key
Returns: 200
{
"index": "articles",
"q": "how to deploy next.js",
"limit": 10,
"filter": "category = 'tutorials'"
}{
"results": [
{
"content": "Deploy your Next.js application to Vercel...",
"metadata": { "publishedAt": 1715000000, "author": "Jane" },
"source": { "index": "articles", "documentId": "doc-42" },
"relevanceScore": 0.98
}
],
"query": "how to deploy next.js",
"processingTimeMs": 12,
"totalResults": 42
}Parameters
indexstringrequiredIndex UID to searchqstring""Search querylimitnumber20Maximum number of results to returnfilterstring—Optional filter expressionResponse Format
Each result is a structured object designed for LLM consumption:
contentClean text with HTML stripped, whitespace normalized — all string fields from the document concatenatedmetadataNon-string fields (numbers, booleans, nested objects) preserved as structured datasource.indexThe index UID this result came fromsource.documentIdThe document's primary key valuerelevanceScoreEngine ranking score (0-1) when availableThe AI search endpoint counts against your search quota. No LLM calls are made — this is pure retrieval with formatting.
Hybrid Search#
Combine keyword search with vector similarity in a single request. Pass a pre-computed embedding vector alongside your text query, and the engine blends both signals. Works identically across Meilisearch, Typesense, and Elasticsearch.
/v1/hybrid-searchUnified keyword + vector search. Falls back to keyword-only when no vector is provided.
Auth: admin or search key
Returns: 200
{
"index": "products",
"q": "wireless headphones",
"vector": [0.12, -0.34, 0.56, ...],
"semanticRatio": 0.7,
"limit": 20,
"filter": "price < 200"
}{
"hits": [...],
"query": "wireless headphones",
"processingTimeMs": 18,
"estimatedTotalHits": 156
}Parameters
indexstringrequiredIndex UID to searchqstring""Keyword query stringvectornumber[]—Pre-computed embedding vector. Omit for keyword-only searchsemanticRationumber—Balance between keyword (0) and vector (1) results. Must be 0-1limitnumber20Maximum hits to returnfilterstring—Filter expression in engine syntaxNote: Headband does not generate embeddings — you compute the vector using your own model (OpenAI, Cohere, etc.) and pass it in. This keeps the architecture simple and avoids vendor lock-in on embedding models.
Chat#
RAG-grounded conversational search. Send a conversation history, and Headband retrieves relevant passages from your index, builds a grounded system prompt, and dispatches to your LLM provider. Bring Your Own Model — Headband never holds your LLM keys.
Chat Endpoint
/v1/chatRAG-grounded conversational endpoint. Searches your index and generates an answer using your LLM provider.
Requires X-Headband-Provider-Key header with your LLM API key.
Auth: admin or search key
Returns: 200
{
"index": "docs",
"messages": [
{ "role": "user", "content": "How do I create an index?" }
],
"provider": "openai",
"model": "gpt-4o",
"limit": 8
}{
"answer": "To create an index, send a POST request to /v1/indexes...",
"sources": [
{
"index": "docs",
"id": "getting-started",
"score": 0.95,
"snippet": "Create an index by sending a POST request..."
}
],
"usage": {
"promptTokens": 1240,
"completionTokens": 180,
"totalTokens": 1420
},
"model": "gpt-4o-2025-04-09",
"processingTimeMs": 2340
}Providers
Pass your LLM API key via the X-Headband-Provider-Key header. Headband supports two providers:
openaiOpenAI API key (sk-...)gpt-4o, gpt-4o-mini, gpt-3.5-turbo, etc.anthropicAnthropic API key (sk-ant-...)claude-sonnet-4-20250514, claude-haiku, etc.curl -X POST https://headband.dev/v1/chat \
-H "Authorization: Bearer hb_your_key" \
-H "X-Headband-Provider-Key: sk-your-openai-key" \
-H "Content-Type: application/json" \
-d '{
"index": "docs",
"messages": [{"role": "user", "content": "What is Headband?"}],
"provider": "openai"
}'Conversations
Pass multi-turn conversation history via the messages array. Headband uses the latest user message as the retrieval query and injects retrieved passages into a system prompt. Caller-supplied system messages are stripped to preserve grounding integrity.
Parameters
indexstringrequiredIndex UID to search for grounding contextmessagesarrayrequiredConversation history: [{role, content}]providerstring"openai"'openai' or 'anthropic'modelstring—Model ID (e.g. 'gpt-4o'). Defaults to provider's latestlimitnumber8Max passages to retrieve for groundingMCP Server#
Headband exposes an MCP (Model Context Protocol) server over HTTP. Connect Claude Desktop, Cursor, or any MCP client to give your AI assistant direct access to your search indexes. Auth uses the same API keys as the REST API.
Setup
The MCP server is available at /api/mcp and speaks JSON-RPC 2.0 over HTTP — no local proxy needed. Add this to your MCP client config:
{
"mcpServers": {
"headband": {
"url": "https://headband.dev/api/mcp",
"headers": {
"Authorization": "Bearer hb_your_api_key"
}
}
}
}/api/mcpReturns MCP server capability descriptor.
Auth: admin or search key
Returns: 200
{
"name": "headband",
"transport": "http",
"protocol": "json-rpc-2.0",
"mcpProtocolVersion": "2025-06-18",
"hint": "POST a JSON-RPC 2.0 request with Authorization: Bearer hb_<key>"
}Available Tools
The MCP server exposes 5 tools. All are engine-agnostic — the AI assistant never needs to know whether an index is backed by Meilisearch, Typesense, or Elasticsearch.
search_orgSearch across all indexes visible to the API key, merge results with reciprocal rank fusion, and return a single ranked listsearch_indexFull-text search a single index by UID. Returns ranked hits as JSONlist_indexesList every index the API key can access, with engine typeget_index_statsGet document count, indexing health, and field distribution for an indexget_documentFetch a single document by its primary keyResources
Each index is exposed as an MCP resource at headband://index/{uid}. Reading a resource returns index stats and a sample of documents — useful for letting an AI assistant understand the shape of your data before searching.
llms.txt#
Auto-generate a standards-compliant llms.txt file from your org's indexes. This machine-readable manifest tells AI agents what data is available and how to query it.
/v1/llms-txtGenerate an llms.txt manifest listing all indexes accessible to the API key, with document counts and search endpoint URLs.
Returns text/plain. Project-scoped keys list that project's indexes; org-scoped keys list all indexes across the org.
Auth: admin or search key
Returns: 200
# My Project
> Search index powered by Headband
## Indexes
- articles (1,247 documents): POST /v1/search {"index":"articles","q":"..."}
- products (8,921 documents): POST /v1/search {"index":"products","q":"..."}
## Endpoints
- Search: POST /v1/search
- AI Search: POST /v1/ai/search
- Chat: POST /v1/chatReact SDK#
@headband/react provides InstantSearch-compatible hooks and pre-built components for building search UIs with React. It handles debouncing, state management, facet filtering, pagination, and more out of the box.
Installation
npm install @headband/reactHeadbandProvider
Wrap your search UI in a HeadbandProvider. It creates a client, manages search state, and provides context to all child hooks and components.
import { headband, HeadbandProvider } from "@headband/react";
const client = headband("https://your-instance.com", "hb_src_YOUR_SEARCH_KEY");
function App() {
return (
<HeadbandProvider client={client} index="products">
{/* Search components go here */}
</HeadbandProvider>
);
}Parameters
clientHeadbandClientRequiredClient created with headband(host, apiKey).indexstringRequiredThe index UID to search.initialQuerystring""Optional initial search query.childrenReactNodeRequiredChild components that use Headband hooks.Hooks
useSearch()Core hook exposing the full search state and all actions. Use the specialized hooks below for most cases.
const { query, results, isLoading, error, setQuery, setPage, refresh } = useSearch();useSearchBox()Binds to the search input. Returns the current query plus setters.
const { query, setQuery, clear } = useSearchBox();
// query: string - Current query
// setQuery(q: string) - Update query (resets pagination, triggers debounced search)
// clear() - Reset query to ""useHits<T>()Returns the current search hits with loading and error state. Supports generics for typed hits.
const { hits, isLoading, error } = useHits<Product>();
// hits: Hit<T>[] - Each hit has document fields + _formatted + __data
// isLoading: boolean
// error: Error | nulluseRefinementList(props)Provides facet filtering for a given attribute. Automatically registers the facet with the provider.
const { items, refine, canToggle, isLoading } = useRefinementList({
attribute: "category",
limit: 20, // max facet values (default: 20)
sortBy: "count", // "count" or "alpha" (default: "count")
});
// items: { value: string, count: number, isRefined: boolean }[]
// refine(value: string) - Toggle a facet value on/offusePagination()Pagination state derived from the current search results.
const { currentPage, totalPages, totalHits, setPage, canPrevious, canNext, pages } = usePagination();
// pages: number[] - Window of page numbers for rendering a page list
// Pages are 0-indexed internallyuseStats()Returns summary stats about the current search results.
const { totalHits, processingTimeMs, query, isLoading } = useStats();useSortBy({ items })Sort-by dropdown state. Pass sort options, get back the current selection.
const { currentSort, setSort, items } = useSortBy({
items: [
{ value: "price:asc", label: "Price (low to high)" },
{ value: "price:desc", label: "Price (high to low)" },
],
});
// items[n].isSelected: boolean - Added to each item
// setSort(null) - Reset to default relevanceuseRange({ attribute })Numeric range filter for a given attribute.
const { min, max, stats, setRange, clear, isLoading } = useRange({ attribute: "price" });
// stats: { min: number, max: number } | undefined - Overall min/max from engine
// setRange({ min: 10, max: 500 }) - Set range filter
// clear() - Remove range filteruseHighlight({ hit, attribute })Extracts the highlighted value for a specific attribute from a hit. Sanitizes HTML -- only allows <em> tags.
const { value, highlighted } = useHighlight({ hit, attribute: "title" });
// value: string - Safe HTML string with <em> tags
// highlighted: boolean - Whether the value contains highlight marks
// Usage: <span dangerouslySetInnerHTML={{ __html: value }} />Components
Pre-built UI components with default Tailwind styles. Set styled=false for unstyled variants.
<SearchBox />Search input with built-in clear button and keyboard handling.
Props: placeholder, className, autoFocus, onQueryChange, styled
<Hits />Renders search result hits. Pass a custom hitComponent for custom rendering.
Props: hitComponent, className, emptyComponent, styled
<RefinementList />Checkbox list for facet filtering on a given attribute.
Props: attribute, limit, sortBy, className, styled
<Pagination />Page navigation with previous/next buttons and page number list.
Props: className, styled
<Stats />Displays result count and processing time (e.g. "42 results in 2ms").
Props: className, styled
<SortBy />Dropdown to switch sort order.
Props: items, className, styled
<Highlight />Renders highlighted text for a given hit attribute.
Props: hit, attribute, className
<RangeInput />Min/max numeric input fields for range filtering.
Props: attribute, className, styled
<PoweredBy />"Powered by Headband" attribution badge.
Props: className, styled
Full Example
A complete product search page with facets, sorting, pagination, and highlighting.
import {
headband,
HeadbandProvider,
SearchBox,
Hits,
RefinementList,
Pagination,
Stats,
SortBy,
Highlight,
} from "@headband/react";
const client = headband(
"https://your-instance.com",
"hb_src_YOUR_SEARCH_KEY"
);
function ProductHit({ hit }) {
return (
<div className="p-4 border rounded-lg">
<Highlight hit={hit} attribute="title" />
<p className="text-sm text-gray-500">${hit.price}</p>
</div>
);
}
export default function SearchPage() {
return (
<HeadbandProvider client={client} index="products">
<div className="max-w-5xl mx-auto p-6">
<SearchBox placeholder="Search products..." autoFocus />
<div className="flex gap-8 mt-6">
<aside className="w-48 shrink-0">
<h3 className="text-sm font-semibold mb-2">Category</h3>
<RefinementList attribute="category" />
<h3 className="text-sm font-semibold mt-4 mb-2">Sort</h3>
<SortBy
items={[
{ value: "price:asc", label: "Price: Low to High" },
{ value: "price:desc", label: "Price: High to Low" },
]}
/>
</aside>
<main className="flex-1">
<Stats />
<Hits hitComponent={ProductHit} />
<Pagination />
</main>
</div>
</div>
</HeadbandProvider>
);
}Engine Compatibility#
Headband supports Meilisearch, Typesense, and Elasticsearch backends. The API is engine-agnostic -- the same endpoints work regardless of which engine your project uses.
When you create a project, you select the engine type and provide a connection URL. All subsequent API calls are translated to the appropriate engine protocol automatically.
The Elasticsearch engine is also compatible with OpenSearch. Point your connection at any OpenSearch node and Headband will work the same way.
Feature Matrix
CORS: The /v1 API supports CORS for browser-based usage. Search keys are safe to use in frontend applications. Admin keys should only be used server-side.
Errors#
Error Format
All error responses follow a consistent JSON structure:
{
"error": "Index not found: products"
}HTTP Status Codes
200OK -- request succeeded.202Accepted -- task enqueued for async processing.400Bad Request -- invalid parameters or body.401Unauthorized -- missing or invalid API key.403Forbidden -- key does not have permission for this action.404Not Found -- index or resource does not exist.405Method Not Allowed -- HTTP method not supported for this endpoint.500Internal Server Error -- something went wrong on our end.