Skip to main content

Search Block

Search Block

Full-text search engine for modern applications

The Search Block provides lightning-fast full-text search capabilities for your application. Index your data, perform instant searches with typo tolerance, and deliver relevant results with faceting, filtering, and analytics—all through a simple REST API.

Features​

  • Full-Text Search - Lightning-fast queries across all indexed content
  • Typo Tolerance - Fuzzy matching handles misspellings automatically
  • Faceted Filters - Dynamic faceting for result refinement
  • Auto-Suggestions - Real-time search suggestions as users type
  • Synonyms & Stemming - Language-aware matching with custom synonyms
  • Search Analytics - Track popular queries and optimize relevance
  • Custom Ranking - Configure ranking rules and boosting
  • Multi-Index - Federated search across multiple indexes
  • Highlighting - Show matched terms in context
  • Geo Search - Location-aware search and sorting

API Endpoint​

ServiceURL
Searchhttps://search.api.us.23blocks.com

Environment Routing: One URL, one environment. All shared-platform API keys are live keys:

  • pk_live_* / sk_live_* → your production tenant. Need a separate staging environment? It is available as a paid add-on on deployments in your own cloud account — see 23blocks in a Box.

Quick Start​

npm install @23blocks/sdk
import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
apiKey: 'your-api-key', // Your live API key (pk_live_*)
});

// Create an index
await client.search.createIndex('products');

// Index documents
await client.search.indexDocuments('products', [
{ id: '1', name: 'Wireless Headphones', category: 'electronics', price: 299 },
{ id: '2', name: 'Bluetooth Speaker', category: 'electronics', price: 149 },
{ id: '3', name: 'Running Shoes', category: 'sports', price: 129 }
]);

// Search with typo tolerance
const results = await client.search.query('products', {
query: 'wirless headphnes', // typos handled!
filters: { category: 'electronics' },
facets: ['category', 'brand']
});

// Get suggestions
const suggestions = await client.search.suggest('products', {
query: 'wire',
limit: 5
});

Authentication​

All API requests require authentication using your API credentials.

Required Headers​

X-App-Id: your-app-id
X-Api-Key: your-api-key
Content-Type: application/json

Indexes API​

Manage your search indexes.

Create Index​

Create a new search index.

POST /search/v1/indexes

Request Body:

{
"data": {
"type": "indexes",
"attributes": {
"uid": "products",
"primary_key": "id",
"settings": {
"searchable_attributes": ["name", "description", "category"],
"filterable_attributes": ["category", "brand", "price"],
"sortable_attributes": ["price", "created_at", "popularity"],
"ranking_rules": [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
],
"stop_words": ["the", "a", "an"],
"distinct_attribute": null,
"typo_tolerance": {
"enabled": true,
"min_word_size_for_typos": {
"one_typo": 5,
"two_typos": 9
}
}
}
}
}
}

Response:

{
"data": {
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"number_of_documents": 0,
"is_indexing": false
}
}
}

List Indexes​

Retrieve all indexes.

GET /search/v1/indexes

Response:

{
"data": [
{
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"number_of_documents": 15420,
"is_indexing": false
}
},
{
"type": "indexes",
"id": "idx_e5f6g7h8",
"attributes": {
"uid": "articles",
"primary_key": "id",
"number_of_documents": 3250,
"is_indexing": false
}
}
]
}

Get Index​

Retrieve a specific index.

GET /search/v1/indexes/:index_uid

Response:

{
"data": {
"type": "indexes",
"id": "idx_a1b2c3d4",
"attributes": {
"uid": "products",
"primary_key": "id",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T12:45:00Z",
"number_of_documents": 15420,
"is_indexing": false,
"settings": {
"searchable_attributes": ["name", "description", "category"],
"filterable_attributes": ["category", "brand", "price"],
"sortable_attributes": ["price", "created_at", "popularity"]
}
}
}
}

Update Index Settings​

Update index configuration.

PATCH /search/v1/indexes/:index_uid/settings

Request Body:

{
"data": {
"type": "index_settings",
"attributes": {
"searchable_attributes": ["name", "description", "category", "tags"],
"filterable_attributes": ["category", "brand", "price", "in_stock"],
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone"],
"laptop": ["notebook", "computer"]
}
}
}
}

Delete Index​

Delete an index and all its documents.

DELETE /search/v1/indexes/:index_uid

Documents API​

Manage documents within an index.

Index Documents​

Add or update documents.

POST /search/v1/indexes/:index_uid/documents

Request Body:

{
"data": {
"type": "documents",
"attributes": {
"documents": [
{
"id": "prod_001",
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"brand": "AudioMax",
"price": 299.99,
"in_stock": true,
"rating": 4.7,
"tags": ["wireless", "noise-cancelling", "premium"]
},
{
"id": "prod_002",
"name": "Portable Bluetooth Speaker",
"description": "Waterproof speaker with 360-degree sound",
"category": "electronics",
"brand": "SoundWave",
"price": 149.99,
"in_stock": true,
"rating": 4.5,
"tags": ["bluetooth", "portable", "waterproof"]
}
]
}
}
}

Response:

{
"data": {
"type": "indexing_tasks",
"id": "task_xyz123",
"attributes": {
"status": "enqueued",
"documents_received": 2,
"enqueued_at": "2025-01-15T14:30:00Z"
}
}
}

Get Document​

Retrieve a specific document.

GET /search/v1/indexes/:index_uid/documents/:document_id

Response:

{
"data": {
"type": "documents",
"id": "prod_001",
"attributes": {
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"brand": "AudioMax",
"price": 299.99,
"in_stock": true,
"rating": 4.7,
"tags": ["wireless", "noise-cancelling", "premium"]
}
}
}

List Documents​

Browse documents in an index.

GET /search/v1/indexes/:index_uid/documents?offset=0&limit=20

Query Parameters:

ParameterTypeDescription
offsetintegerNumber of documents to skip
limitintegerNumber of documents to return (max 1000)
fieldsstringComma-separated list of fields to return
filterstringFilter expression

Delete Documents​

Delete specific documents.

DELETE /search/v1/indexes/:index_uid/documents

Request Body:

{
"data": {
"type": "document_deletions",
"attributes": {
"document_ids": ["prod_001", "prod_002", "prod_003"]
}
}
}

Delete Documents by Filter​

Delete documents matching a filter.

POST /search/v1/indexes/:index_uid/documents/delete-by-filter

Request Body:

{
"data": {
"type": "document_deletions",
"attributes": {
"filter": "category = 'deprecated' AND updated_at < '2024-01-01'"
}
}
}

Search API​

Perform searches across your indexed data.

POST /search/v1/indexes/:index_uid/search

Request Body:

{
"data": {
"type": "search_queries",
"attributes": {
"query": "wireless headphones",
"limit": 20,
"offset": 0
}
}
}

Response:

{
"data": {
"type": "search_results",
"attributes": {
"hits": [
{
"id": "prod_001",
"name": "Wireless Noise-Cancelling Headphones",
"description": "Premium over-ear headphones with active noise cancellation",
"category": "electronics",
"price": 299.99,
"_formatted": {
"name": "<em>Wireless</em> Noise-Cancelling <em>Headphones</em>",
"description": "Premium over-ear <em>headphones</em> with active noise cancellation"
},
"_matchesPosition": {
"name": [{ "start": 0, "length": 8 }, { "start": 31, "length": 10 }]
}
}
],
"offset": 0,
"limit": 20,
"estimated_total_hits": 128,
"processing_time_ms": 12,
"query": "wireless headphones"
}
}
}

Search with Filters​

Apply filters to narrow results.

{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"filter": "category = 'electronics' AND price < 300 AND in_stock = true",
"limit": 20
}
}
}

Filter Syntax:

OperatorDescriptionExample
=Equalscategory = 'electronics'
!=Not equalsbrand != 'Generic'
>Greater thanprice > 100
>=Greater or equalrating >= 4.0
<Less thanprice < 500
<=Less or equalstock <= 10
INIn arraycategory IN ['electronics', 'audio']
NOT INNot in arraystatus NOT IN ['deleted', 'hidden']
EXISTSField existsdiscount EXISTS
NOT EXISTSField doesn't existarchived NOT EXISTS
ANDLogical andprice < 100 AND in_stock = true
ORLogical orcategory = 'audio' OR category = 'video'

Search with Facets​

Get facet counts for result refinement.

{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"facets": ["category", "brand", "price"],
"limit": 20
}
}
}

Response:

{
"data": {
"type": "search_results",
"attributes": {
"hits": [],
"facet_distribution": {
"category": {
"electronics": 95,
"audio": 28,
"gaming": 12
},
"brand": {
"AudioMax": 42,
"SoundWave": 35,
"TechPro": 28
}
},
"facet_stats": {
"price": {
"min": 29.99,
"max": 599.99,
"avg": 189.50,
"sum": 25678.50
}
}
}
}
}

Search with Sorting​

Sort results by specific attributes.

{
"data": {
"type": "search_queries",
"attributes": {
"query": "headphones",
"sort": ["price:asc", "rating:desc"],
"limit": 20
}
}
}

Search with Highlighting​

Configure result highlighting.

{
"data": {
"type": "search_queries",
"attributes": {
"query": "wireless",
"attributes_to_highlight": ["name", "description"],
"highlight_pre_tag": "<mark>",
"highlight_post_tag": "</mark>",
"limit": 20
}
}
}

Search with geographic constraints.

{
"data": {
"type": "search_queries",
"attributes": {
"query": "coffee shop",
"filter": "_geoRadius(40.7128, -74.0060, 5000)",
"sort": ["_geoPoint(40.7128, -74.0060):asc"],
"limit": 20
}
}
}

Search across multiple indexes.

POST /search/v1/multi-search

Request Body:

{
"data": {
"type": "multi_search_queries",
"attributes": {
"queries": [
{
"index_uid": "products",
"query": "headphones",
"limit": 5
},
{
"index_uid": "articles",
"query": "headphones review",
"limit": 3
}
]
}
}
}

Suggestions API​

Provide search suggestions as users type.

Get Suggestions​

POST /search/v1/indexes/:index_uid/suggest

Request Body:

{
"data": {
"type": "suggestion_queries",
"attributes": {
"query": "wire",
"limit": 5,
"highlight": true
}
}
}

Response:

{
"data": {
"type": "suggestions",
"attributes": {
"suggestions": [
{
"suggestion": "wireless headphones",
"highlighted": "<em>wire</em>less headphones",
"frequency": 1250
},
{
"suggestion": "wireless speaker",
"highlighted": "<em>wire</em>less speaker",
"frequency": 890
},
{
"suggestion": "wireless charger",
"highlighted": "<em>wire</em>less charger",
"frequency": 654
},
{
"suggestion": "wireless mouse",
"highlighted": "<em>wire</em>less mouse",
"frequency": 421
},
{
"suggestion": "wireless keyboard",
"highlighted": "<em>wire</em>less keyboard",
"frequency": 387
}
],
"processing_time_ms": 3
}
}
}

Configure Suggestions​

Configure suggestion behavior.

PATCH /search/v1/indexes/:index_uid/settings/suggestions

Request Body:

{
"data": {
"type": "suggestion_settings",
"attributes": {
"enabled": true,
"source_attributes": ["name", "category", "tags"],
"min_query_length": 2,
"max_suggestions": 10,
"include_frequency": true
}
}
}

Synonyms API​

Configure synonyms for better search matching.

Set Synonyms​

PUT /search/v1/indexes/:index_uid/settings/synonyms

Request Body:

{
"data": {
"type": "synonyms",
"attributes": {
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone", "cellphone"],
"laptop": ["notebook", "computer", "pc"],
"tv": ["television", "smart tv", "flat screen"],
"headphones": ["earphones", "earbuds", "headset"]
},
"one_way_synonyms": {
"iphone": ["phone", "smartphone"],
"macbook": ["laptop", "notebook"]
}
}
}
}

Get Synonyms​

GET /search/v1/indexes/:index_uid/settings/synonyms

Response:

{
"data": {
"type": "synonyms",
"attributes": {
"synonyms": {
"phone": ["smartphone", "mobile", "cell phone", "cellphone"],
"laptop": ["notebook", "computer", "pc"]
},
"one_way_synonyms": {
"iphone": ["phone", "smartphone"]
}
}
}
}

Stop Words API​

Configure stop words to ignore in searches.

Set Stop Words​

PUT /search/v1/indexes/:index_uid/settings/stop-words

Request Body:

{
"data": {
"type": "stop_words",
"attributes": {
"stop_words": ["the", "a", "an", "and", "or", "but", "is", "are", "was", "were"]
}
}
}

Ranking Rules API​

Configure how results are ranked.

Set Ranking Rules​

PUT /search/v1/indexes/:index_uid/settings/ranking-rules

Request Body:

{
"data": {
"type": "ranking_rules",
"attributes": {
"ranking_rules": [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"popularity:desc",
"rating:desc"
]
}
}
}

Built-in Ranking Rules:

RuleDescription
wordsMatches with more query words rank higher
typoMatches with fewer typos rank higher
proximityMatches with words closer together rank higher
attributeMatches in more important attributes rank higher
sortApplies explicit sorting from search request
exactnessExact matches rank higher than partial matches

Analytics API​

Track search analytics and user behavior.

GET /search/v1/indexes/:index_uid/analytics/popular-searches

Query Parameters:

ParameterTypeDescription
periodstringTime period: 24h, 7d, 30d, 90d
limitintegerNumber of results (default: 10)

Response:

{
"data": {
"type": "analytics",
"attributes": {
"popular_searches": [
{
"query": "wireless headphones",
"count": 12450,
"unique_users": 8920,
"avg_click_position": 1.3
},
{
"query": "bluetooth speaker",
"count": 8730,
"unique_users": 6540,
"avg_click_position": 1.8
}
],
"period": "7d",
"total_searches": 156780
}
}
}

Get No-Result Searches​

Find queries with no results.

GET /search/v1/indexes/:index_uid/analytics/no-result-searches

Response:

{
"data": {
"type": "analytics",
"attributes": {
"no_result_searches": [
{
"query": "solar charger",
"count": 234,
"last_searched_at": "2025-01-15T14:30:00Z"
},
{
"query": "wireless earbuds case",
"count": 189,
"last_searched_at": "2025-01-15T14:25:00Z"
}
],
"period": "7d",
"total_no_result_searches": 1250
}
}
}

Get Click Analytics​

Track which results users click.

GET /search/v1/indexes/:index_uid/analytics/clicks

Response:

{
"data": {
"type": "analytics",
"attributes": {
"click_data": [
{
"document_id": "prod_001",
"clicks": 5420,
"impressions": 18500,
"ctr": 0.293,
"avg_position": 1.2
}
],
"period": "7d",
"total_clicks": 45600,
"total_impressions": 156780,
"avg_ctr": 0.29
}
}
}

Track Search Event​

Log search analytics events.

POST /search/v1/analytics/events

Request Body:

{
"data": {
"type": "search_events",
"attributes": {
"event_type": "click",
"index_uid": "products",
"query": "wireless headphones",
"document_id": "prod_001",
"position": 1,
"user_id": "user_abc123",
"session_id": "sess_xyz789"
}
}
}

Tasks API​

Monitor indexing tasks.

Get Task Status​

GET /search/v1/tasks/:task_id

Response:

{
"data": {
"type": "tasks",
"id": "task_xyz123",
"attributes": {
"status": "succeeded",
"task_type": "document_addition",
"index_uid": "products",
"details": {
"documents_received": 250,
"documents_indexed": 250
},
"enqueued_at": "2025-01-15T14:30:00Z",
"started_at": "2025-01-15T14:30:01Z",
"finished_at": "2025-01-15T14:30:03Z",
"duration_ms": 2340
}
}
}

List Tasks​

GET /search/v1/tasks?status=succeeded&index_uid=products&limit=20

Task Statuses:

StatusDescription
enqueuedTask is waiting to be processed
processingTask is currently being processed
succeededTask completed successfully
failedTask failed with an error
canceledTask was canceled

Webhooks​

Configure webhooks for search events.

Register Webhook​

POST /search/v1/webhooks

Request Body:

{
"data": {
"type": "webhooks",
"attributes": {
"url": "https://your-app.com/webhooks/search",
"events": [
"indexing.completed",
"indexing.failed",
"index.created",
"index.deleted"
],
"secret": "your-webhook-secret"
}
}
}

Webhook Events​

EventDescription
indexing.completedBatch indexing completed successfully
indexing.failedBatch indexing failed
index.createdNew index created
index.deletedIndex was deleted
settings.updatedIndex settings changed

Webhook Payload Example​

{
"event": "indexing.completed",
"occurred_at": "2025-01-15T14:30:03Z",
"data": {
"task_id": "task_xyz123",
"index_uid": "products",
"documents_indexed": 250,
"duration_ms": 2340
}
}

Error Handling​

Error Response Format​

{
"errors": [
{
"status": "400",
"code": "invalid_filter_syntax",
"title": "Invalid Filter Syntax",
"detail": "The filter 'price <> 100' contains an invalid operator. Use '!=' instead of '<>'.",
"source": {
"parameter": "filter"
}
}
]
}

Common Error Codes​

CodeStatusDescription
index_not_found404The specified index does not exist
document_not_found404The specified document does not exist
invalid_filter_syntax400Filter syntax is invalid
invalid_sort_attribute400Sort attribute is not sortable
attribute_not_filterable400Attribute is not configured as filterable
max_documents_exceeded400Document batch exceeds maximum size
index_already_exists409Index with this UID already exists
rate_limit_exceeded429Too many requests

SDK Examples​

React Search Component​

import { useState, useEffect, useCallback } from 'react';
import { useDebounce } from 'use-debounce';
import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
appId: 'your-app-id',
});

function ProductSearch() {
const [query, setQuery] = useState('');
const [debouncedQuery] = useDebounce(query, 300);
const [results, setResults] = useState([]);
const [suggestions, setSuggestions] = useState([]);
const [facets, setFacets] = useState({});
const [filters, setFilters] = useState({});
const [loading, setLoading] = useState(false);

// Fetch suggestions as user types
useEffect(() => {
if (debouncedQuery.length < 2) {
setSuggestions([]);
return;
}

client.search.suggest('products', {
query: debouncedQuery,
limit: 5
}).then(data => {
setSuggestions(data.suggestions);
});
}, [debouncedQuery]);

// Perform search
const performSearch = useCallback(async (searchQuery) => {
if (!searchQuery) {
setResults([]);
setFacets({});
return;
}

setLoading(true);
try {
const response = await client.search.query('products', {
query: searchQuery,
filter: buildFilterString(filters),
facets: ['category', 'brand'],
limit: 20,
attributesToHighlight: ['name', 'description']
});

setResults(response.hits);
setFacets(response.facetDistribution);

// Track search analytics
await client.search.trackEvent({
eventType: 'search',
indexUid: 'products',
query: searchQuery,
resultsCount: response.estimatedTotalHits
});
} finally {
setLoading(false);
}
}, [filters]);

// Handle result click
const handleResultClick = async (hit, position) => {
await client.search.trackEvent({
eventType: 'click',
indexUid: 'products',
query,
documentId: hit.id,
position
});
};

return (
<div className="search-container">
{/* Search Input */}
<div className="search-input-wrapper">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && performSearch(query)}
placeholder="Search products..."
className="search-input"
/>

{/* Suggestions Dropdown */}
{suggestions.length > 0 && (
<ul className="suggestions-dropdown">
{suggestions.map((suggestion, index) => (
<li
key={index}
onClick={() => {
setQuery(suggestion.suggestion);
performSearch(suggestion.suggestion);
setSuggestions([]);
}}
dangerouslySetInnerHTML={{ __html: suggestion.highlighted }}
/>
))}
</ul>
)}
</div>

<div className="search-layout">
{/* Facets Sidebar */}
<aside className="facets-sidebar">
{Object.entries(facets).map(([facetName, values]) => (
<div key={facetName} className="facet-group">
<h3>{facetName}</h3>
{Object.entries(values).map(([value, count]) => (
<label key={value}>
<input
type="checkbox"
checked={filters[facetName]?.includes(value)}
onChange={() => toggleFilter(facetName, value)}
/>
{value} ({count})
</label>
))}
</div>
))}
</aside>

{/* Results */}
<main className="search-results">
{loading ? (
<div className="loading">Searching...</div>
) : (
<ul>
{results.map((hit, index) => (
<li
key={hit.id}
onClick={() => handleResultClick(hit, index + 1)}
className="result-item"
>
<h3 dangerouslySetInnerHTML={{
__html: hit._formatted?.name || hit.name
}} />
<p dangerouslySetInnerHTML={{
__html: hit._formatted?.description || hit.description
}} />
<span className="price">${hit.price}</span>
</li>
))}
</ul>
)}
</main>
</div>
</div>
);
}

Vue 3 Composable​

<script setup>
import { ref, watch, computed } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { search: 'https://search.api.us.23blocks.com' },
appId: 'your-app-id',
});

const query = ref('');
const results = ref([]);
const suggestions = ref([]);
const facets = ref({});
const selectedFilters = ref({});
const loading = ref(false);

// Debounced suggestion fetch
const fetchSuggestions = useDebounceFn(async (searchQuery) => {
if (searchQuery.length < 2) {
suggestions.value = [];
return;
}

const data = await client.search.suggest('products', {
query: searchQuery,
limit: 5
});
suggestions.value = data.suggestions;
}, 300);

watch(query, (newQuery) => {
fetchSuggestions(newQuery);
});

// Build filter string from selected filters
const filterString = computed(() => {
const parts = [];
for (const [key, values] of Object.entries(selectedFilters.value)) {
if (values.length > 0) {
parts.push(`${key} IN [${values.map(v => `'${v}'`).join(', ')}]`);
}
}
return parts.join(' AND ');
});

// Perform search
async function search() {
if (!query.value) return;

loading.value = true;
try {
const response = await client.search.query('products', {
query: query.value,
filter: filterString.value || undefined,
facets: ['category', 'brand'],
limit: 20
});

results.value = response.hits;
facets.value = response.facetDistribution;
} finally {
loading.value = false;
}
}

// Toggle facet filter
function toggleFilter(facetName, value) {
if (!selectedFilters.value[facetName]) {
selectedFilters.value[facetName] = [];
}

const index = selectedFilters.value[facetName].indexOf(value);
if (index === -1) {
selectedFilters.value[facetName].push(value);
} else {
selectedFilters.value[facetName].splice(index, 1);
}

search();
}
</script>

<template>
<div class="search-page">
<div class="search-box">
<input
v-model="query"
@keyup.enter="search"
placeholder="Search products..."
/>

<ul v-if="suggestions.length" class="suggestions">
<li
v-for="(suggestion, index) in suggestions"
:key="index"
@click="query = suggestion.suggestion; search(); suggestions = []"
v-html="suggestion.highlighted"
/>
</ul>
</div>

<div class="content">
<aside class="filters">
<div v-for="(values, facetName) in facets" :key="facetName">
<h4>{{ facetName }}</h4>
<label v-for="(count, value) in values" :key="value">
<input
type="checkbox"
:checked="selectedFilters[facetName]?.includes(value)"
@change="toggleFilter(facetName, value)"
/>
{{ value }} ({{ count }})
</label>
</div>
</aside>

<main class="results">
<div v-if="loading">Loading...</div>
<div v-else-if="results.length === 0">No results found</div>
<ul v-else>
<li v-for="hit in results" :key="hit.id">
<h3 v-html="hit._formatted?.name || hit.name" />
<p v-html="hit._formatted?.description || hit.description" />
<span class="price">${{ hit.price }}</span>
</li>
</ul>
</main>
</div>
</div>
</template>

Rate Limits​

PlanSearches/secondDocuments/indexIndexes
Free1010,0003
Starter50100,00010
Growth2001,000,00050
EnterpriseCustomUnlimitedUnlimited

Best Practices​

Indexing​

  1. Batch documents - Index in batches of 100-1000 documents for optimal performance
  2. Use primary keys - Always define a primary key for document identification
  3. Configure searchable attributes - Only index fields that need to be searchable
  4. Set filterable attributes - Pre-configure attributes you'll filter on

Searching​

  1. Use suggestions - Implement auto-complete for better UX
  2. Apply filters wisely - Filters are fast, use them for category navigation
  3. Track analytics - Log searches and clicks to improve relevance
  4. Handle typos - Typo tolerance is enabled by default

Performance​

  1. Debounce queries - Wait 200-300ms between keystrokes before searching
  2. Limit results - Request only needed fields and reasonable result counts
  3. Cache static facets - Cache facet distributions that don't change often
  4. Use pagination - Implement infinite scroll or pagination for large result sets

Use This Block with AI Agents​

This block ships an official Claude Code plugin from the public 23blocks marketplace (19 plugins, 205 skills). Your agent gets full working knowledge of this API — endpoints, identity registration, and conventions.

# one-time: add the 23blocks marketplace
claude plugin marketplace add https://github.com/23blocks-OS/ai-agents

# install this block's plugin
claude plugin install search-block

Configure BLOCKS_API_URL, BLOCKS_API_KEY, and BLOCKS_AUTH_TOKEN. Register identity first: each block is autonomous — register the user with this block's identity endpoint before private calls (only user_unique_id is required; email/phone are optional notification-routing fields).

Full reference: AI Agents & Claude Code Plugins · github.com/23blocks-OS/ai-agents