Algolia compatibility

App Search is in beta with design partners. Endpoints roll out per tenant.

Each tenant also answers Algolia’s search routes, so an existing Algolia client or InstantSearch front end can search Captain:

RouteAlgolia equivalent
POST /1/indexes/*/queriesMulti-query, used by InstantSearch and search()
POST /1/indexes/{index}/querySingle-index query
POST /1/eventsInsights events

These routes read Captain’s copy of the index. They never forward requests to Algolia.

Setup

  1. Import the Algolia index into Captain under the same index name. See Import from Algolia.
  2. Point the Algolia client at the tenant host, acme.captain.dev.
  3. Pass a Captain search key where the client expects the Algolia API key.

The application id can be any value. The hostname selects the tenant.

Point the Algolia client at Captain

The JavaScript client takes the tenant host in hosts. The same client works with InstantSearch.

import { liteClient as algoliasearch } from "algoliasearch/lite";
const searchClient = algoliasearch("acme", "your_search_key", {
hosts: [{ url: "acme.captain.dev", accept: "readWrite", protocol: "https" }],
});
const { results } = await searchClient.search({
requests: [{ indexName: "products", query: "hiking boots", hitsPerPage: 2 }],
});

Multi-query

Each request names an index and its Algolia search parameters. The reply holds one result per request, in Algolia’s format.

curl -X POST "https://acme.captain.dev/1/indexes/*/queries" \
-H "x-algolia-api-key: $CAPTAIN_SEARCH_KEY" \
-H "x-algolia-application-id: acme" \
-H "Content-Type: application/json" \
-d '{
"requests": [
{
"indexName": "products",
"params": "query=hiking%20boots&hitsPerPage=2"
}
]
}'
Example: (200 OK)
{
"results": [
{
"hits": [
{
"title": "Moab 3 Mid Waterproof Hiking Boot",
"brand": "Merrell",
"category": "Hiking Boots",
"price": 139.0,
"objectID": "sku-10482",
"_highlightResult": {
"title": {
"value": "Moab 3 Mid Waterproof <em>Hiking</em> <em>Boot</em>",
"matchLevel": "full",
"matchedWords": [
"hiking",
"boots"
],
"fullyHighlighted": false
}
}
},
{
"title": "X Ultra 4 Mid GTX Hiking Boot",
"brand": "Salomon",
"category": "Hiking Boots",
"price": 165.0,
"objectID": "sku-10517",
"_highlightResult": {
"title": {
"value": "X Ultra 4 Mid GTX <em>Hiking</em> <em>Boot</em>",
"matchLevel": "full",
"matchedWords": [
"hiking",
"boots"
],
"fullyHighlighted": false
}
}
}
],
"nbHits": 42,
"page": 0,
"hitsPerPage": 2,
"nbPages": 21,
"exhaustiveNbHits": true,
"exhaustiveFacetsCount": true,
"exhaustive": {
"nbHits": true,
"facetsCount": true
},
"facets": {},
"query": "hiking boots",
"params": "query=hiking%20boots&hitsPerPage=2",
"index": "products",
"processingTimeMS": 14,
"queryID": "c97e0b2a4f6d41e3a58b1c7d2e9f0a36",
"renderingContent": {}
}
]
}

Single-index query

curl -X POST "https://acme.captain.dev/1/indexes/products/query" \
-H "x-algolia-api-key: $CAPTAIN_SEARCH_KEY" \
-H "x-algolia-application-id: acme" \
-H "Content-Type: application/json" \
-d '{
"params": "query=rain%20jacket&hitsPerPage=1"
}'
Example: (200 OK)
{
"hits": [
{
"title": "Torrentshell 3L Rain Jacket",
"brand": "Patagonia",
"category": "Rain Jackets",
"price": 179.0,
"objectID": "sku-20311",
"_highlightResult": {
"title": {
"value": "Torrentshell 3L <em>Rain</em> <em>Jacket</em>",
"matchLevel": "full",
"matchedWords": [
"rain",
"jacket"
],
"fullyHighlighted": false
}
}
}
],
"nbHits": 18,
"page": 0,
"hitsPerPage": 1,
"nbPages": 18,
"exhaustiveNbHits": true,
"exhaustiveFacetsCount": true,
"exhaustive": {
"nbHits": true,
"facetsCount": true
},
"facets": {},
"query": "rain jacket",
"params": "query=rain%20jacket&hitsPerPage=1",
"index": "products",
"processingTimeMS": 11,
"queryID": "4b8d2f0e6a1c43b79e5d3a2c8f7b6e10",
"renderingContent": {}
}

Insights events

Send clicks and conversions with the queryID from the search result. They feed the same analytics as the native events route.

curl -X POST "https://acme.captain.dev/1/events" \
-H "x-algolia-api-key: $CAPTAIN_SEARCH_KEY" \
-H "x-algolia-application-id: acme" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"eventType": "click",
"eventName": "Result clicked",
"index": "products",
"userToken": "user-481",
"queryID": "c97e0b2a4f6d41e3a58b1c7d2e9f0a36",
"objectIDs": [
"sku-10482"
],
"positions": [
1
]
}
]
}'
Example: (200 OK)
{
"status": 200,
"message": "OK"
}

Limits

  • Supported client versions, headers and parameters are validated per integration before a migration goes live.
  • Indexes with per-record access control are not served on these routes. Use the native search routes with scoped keys.
  • Facet counts are exact while a query matches up to 250 records. Above that they cover the relevance-cut result set, and the reply says exhaustiveFacetsCount: false, as Algolia does on large result sets.
© 2026 Captain