Index Management API
The Index Management API keeps your SearchJet index in sync with your content. Every endpoint on this page needs your private key, so only call them from server-side code. If you run WordPress or WooCommerce, the SearchJet Instant Search plugin handles indexing for you.
Authentication
Authorization: Bearer YOUR_PRIVATE_KEY
Accept: application/json
Content-Type: application/json
The private key is 32 characters long. You create it in the dashboard under Sites → API Keys, and it's shown only once. If you send the public search key here, you get 401 {"message": "Invalid or expired token"}. Always send Accept: application/json: without it, a validation error comes back as a redirect instead of a 422 JSON response. See API Key Setup.
Add, replace or update documents
POST https://app.searchjetengine.com/api/v1/index/bulk
PATCH https://app.searchjetengine.com/api/v1/index/bulk
- POST adds documents. A document with an existing
idis replaced entirely. - PATCH merges the fields you send into existing documents. Use it for partial updates, such as changing only
price.
Body
| Field | Required | Description |
|---|---|---|
documents |
Yes | Array of documents. Up to 1,000 per request on Free and 5,000 on paid plans. |
full_reindex |
No | true clears your existing documents before adding these. Limited per day by plan: Free 1, Pro 4, Growth and Enterprise unlimited. |
Each document:
- needs an
id, plus at least one other non-empty field. Documents with only anidare dropped. - must be at most 1 MB as JSON. Larger documents get
413 payload_too_large. - should use only letters, digits,
-and_in itsid. Other characters make the asynchronous task fail.
Useful fields are title, content, url, excerpt, type (default page), author, date, image, thumbnail, price, sale_price, regular_price, currency, sku, stock_status, and categories and tags (as arrays of strings). You can add custom fields too. Before storing documents, SearchJet trims strings, removes empty values, and converts prices to numbers.
Example
curl -X POST "https://app.searchjetengine.com/api/v1/index/bulk" \
-H "Authorization: Bearer $SEARCHJET_PRIVATE_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{
"id": "post-1",
"title": "Getting Started with SearchJet",
"content": "SearchJet is a hosted site search service...",
"url": "https://example.com/blog/getting-started",
"type": "post",
"date": "2026-01-15"
}
]
}'
import os
import httpx
HEADERS = {
"Authorization": f"Bearer {os.environ['SEARCHJET_PRIVATE_KEY']}",
"Accept": "application/json",
}
response = httpx.patch( # merge: update only the price
"https://app.searchjetengine.com/api/v1/index/bulk",
headers=HEADERS,
json={"documents": [{"id": "product-42", "price": 19.99}]},
)
response.raise_for_status()
task_uid = response.json()["taskUid"]
use Illuminate\Support\Facades\Http;
$task = Http::withToken(config('services.searchjet.private_key'))
->acceptJson()
->post('https://app.searchjetengine.com/api/v1/index/bulk', [
'documents' => [[
'id' => 'post-1',
'title' => 'Getting Started with SearchJet',
'url' => 'https://example.com/blog/getting-started',
]],
])
->throw()
->json();
Response
Indexing is asynchronous. A successful request returns the queued task:
{
"taskUid": 1234,
"indexUid": "your-index",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2026-10-01T09:00:00Z",
"message": "Indexed 1 documents. Total indexed: 120.",
"was_cleared": false,
"task_uid": 1234
}
There's no success field. Use taskUid to check the task's status.
Check a task
GET https://app.searchjetengine.com/api/v1/index/tasks/{taskUid}
The response is {"success": true, "task": {"uid", "status", "type", "details", "error", "duration", "enqueued_at", "started_at", "finished_at"}}. If status is failed, look at error. A typical cause is an invalid id.
Delete documents
DELETE https://app.searchjetengine.com/api/v1/index/delete
Send a JSON body with either {"id": "post-1"} or {"ids": ["post-1", "post-2"]} (at most 500 ids). The response is {success, task_uid, status}. If you send neither field, you get 400 missing_parameters.
curl -X DELETE "https://app.searchjetengine.com/api/v1/index/delete" \
-H "Authorization: Bearer $SEARCHJET_PRIVATE_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"ids": ["post-1", "post-2"]}'
POST https://app.searchjetengine.com/api/v1/index/delete-batch
Send either {"ids": [...]} or {"filter": "type = \"draft\""}. The response is {success, taskUid, deletedCount, message}, and deletedCount is -1 when you delete by filter. An invalid filter returns 400 invalid_filter.
Read your index
GET /index/countreturns{success, total_indexed_documents}. The value is cached for up to 5 minutes.GET /index/{id}returns{success, document}with all stored fields, or404 not_found.
Limits to plan around
- Page quota: requests that would take you past your plan's indexed-page limit (Free 1,000, Pro 25,000, Growth 100,000) get
429 quota_exceeded. Incoming documents are counted as new even when their ids already exist, so near the limit, re-sending existing documents can be rejected. - Abuse detection: on Free and Pro, indexing more than 1,000 documents per minute adds an
X-Abuse-Warningheader. After repeated violations within an hour, the key is temporarily suspended (429 "Abuse detected"), and retrying keeps the block going. Keep batches to 1,000 documents or fewer, and wait at least a minute between large batches. - Request limits: private-key requests are limited per plan (Free 10 per minute, Pro 60). See Rate Limits & Quotas.
Best practices
- Use stable ids, such as
post-42, so that re-indexing replaces documents instead of duplicating them. - Use PATCH when only a few fields have changed.
- Index in the background with a queue, and retry
429and5xxresponses with backoff. See Error Handling. - Delete when you unpublish. Step-by-Step Installation & Configuration shows a Laravel observer that does this.