Skip to navigation

Index settings

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

Each index has settings that shape its search. They decide which fields are searched and how much each one counts, which fields can be faceted and sorted, and which synonyms and rules apply to a query.

Field types and facets are set when an index is created. Settings, synonyms and rules change later with the same write key, with no help from Captain. Captain sets two things with you: per-record access control (acl, tenant_field) and what the audit log keeps (audit). A write key that changes one of those gets 403 FORBIDDEN. An Algolia import can also bring settings in.

What you set when you create an index

Create Index takes an optional fields object. Give each field a type, and set facet to true for the fields you want counted in facets. A number can also carry a unit, and an enum its allowed values.

curl -X POST "https://acme.captain.dev/v1/indexes" \
-H "Authorization: Bearer $CAPTAIN_WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"fields": {
"title": {
"type": "text"
},
"description": {
"type": "text"
},
"brand": {
"type": "keyword",
"facet": true
},
"category": {
"type": "keyword",
"facet": true
},
"price": {
"type": "number",
"unit": "usd"
},
"rating": {
"type": "number"
},
"image_url": {
"type": "url"
}
}
}'
Example: (201 Created)
{
"name": "products",
"status": "empty",
"staged_documents": 0,
"staged_records": 0,
"record_count": 0,
"build_id": null,
"last_job": null,
"created_at": 1759093140,
"staged_changes_since_build": false,
"pending_changes": false,
"object_search": true,
"last_build_at": null,
"serving": null,
"id_field": "id"
}

Fields you leave out are typed from the first records you add. Records lists the types and what each one supports.

A field accepts only type, facet, unit and values. Anything else answers 400 VALIDATION_ERROR. A write key that sends settings or schema gets 403 FORBIDDEN. Only a write key for every index in the tenant can create indexes. A key limited to some indexes gets 403.

Change settings

Get Settings returns the settings you set, the field table search uses, and up to three example values per field. Update Settings changes only the keys you send, as a JSON merge patch: objects merge key by key, null removes a key, and anything else replaces it.

curl -X PATCH "https://acme.captain.dev/v1/indexes/products/settings" \
-H "Authorization: Bearer $CAPTAIN_WRITE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"fields": {
"brand": {"type": "keyword", "facet": true, "weight": "low"},
"material": {"type": "keyword", "facet": true}
}
}'
Example: (202 Accepted)
{
"object_search": true,
"settings": {"fields": {"brand": {"type": "keyword", "facet": true, "weight": "low"}, "material": {"type": "keyword", "facet": true}}},
"warnings": [],
"job": {"id": "job_20261002193012_9f8e7d", "type": "index.build", "index": "products", "status": "queued"},
"applies_at": "when job job_20261002193012_9f8e7d succeeds"
}

A field takes type, searchable, weight (primary, secondary or low), facet, filterable, sortable, unit and values. The settings are checked against your records before anything is saved, and a change that doesn’t fit answers 400 VALIDATION_ERROR and changes nothing.

A change rebuilds the index, the same way adding records does. The reply’s job is that rebuild, and Get Job follows it. Search keeps answering with the old settings until the job succeeds, then switches in one step. If a rebuild of the index is already running, job is that one, and your change goes into the rebuild that starts right after it. An index with no records yet answers 200 with no job, and the settings apply to its first build.

Replace Settings (PUT) sends the whole settings object instead. Leave out acl, tenant_field and audit as they are: a write key that changes them gets 403.

Get Synonyms and Replace Synonyms, and Get Rules and Replace Rules, work the same way with the write key. Their changes apply to the next search, with no rebuild.

Searchable fields and weights

Search matches the words in text, keyword, enum and identifier fields, and in numbers and booleans as written. Dates, locations and URLs aren’t searched.

Each text field has a weight. Weights are three tiers, not numbers:

  • Primary is the field that names the record, such as a title. A match here counts most.
  • Secondary is longer text, such as a description. This is the default for long text.
  • Low is everything else that should still match, such as tags or attributes.

Captain picks the tiers from your records. Change a field’s tier with weight, or stop it from being searched with "searchable": false, in Update Settings. Ranking uses one relevance model for every index, so there is no ranking formula to tune per index.

Facets

Enum and bool fields are facets by default. So are keyword fields whose values repeat, such as a brand or a category. Facet fields whose values repeat. A facet on a description lists almost every record once. For short labels such as a color, use the keyword type.

A number facet counts values only while the field has at most 1,000 distinct values, such as years or quantities. A date facet, or a number facet with more values such as prices, gives its minimum and maximum through facet_stats instead of value counts.

A search asks for facets with facet_keys. Search shows the request, and List Filters says which fields can be faceted.

Sort

Number and date fields are sortable. A search sorts by one of them instead of relevance with sort, as in {"field": "price", "order": "asc"}. Each request picks its own order, so an index needs no sorted copies.

Custom ranking

custom_ranking is Algolia’s customRanking: up to 4 number or date fields that order results the query leaves equally relevant. Send it with Update Settings as [{"field": "sales", "order": "desc"}, {"field": "rating", "order": "desc"}], or in Algolia’s form, ["desc(sales)", "desc(rating)"]. Each field also becomes sortable. An Algolia import sets it from customRanking.

  • A search with no text, and browse, list every match in this order. The first field decides, the next breaks its ties, and a record missing a value comes after the records that have one.
  • A search with text orders the results Captain finds equally relevant by these fields. Captain walks the results in relevance order and groups them: a result stays in the current group while its score is within 2% of the query’s score spread from the group’s first result. Inside a group, the custom ranking decides the order. Captain’s relevance is one combined score, so results Algolia calls tied can score differently here and keep their relevance order.
  • A request’s own sort replaces the custom ranking.

The order for a search with no text is exact. Captain packs the fields into one number per record, with 52/n bits for each of n fields. Each field’s range comes from a sample of the records and is widened by that range on each side, so only values far outside what the build saw tie at the end. After an update that changes a ranked value in place, that order catches up at the next build.

Synonyms

A synonym makes a query match words it didn’t contain. There are two kinds:

  • Two-way: any word in the group matches the others. sofa, couch and settee all find each other.
  • One-way: the input adds the synonyms, but not the reverse. tv also matches television, and television doesn’t match tv.
[
{"objectID": "s1", "type": "synonym", "synonyms": ["sofa", "couch", "settee"]},
{"objectID": "s2", "type": "oneWaySynonym", "input": "tv", "synonyms": ["television"]}
]

Synonyms match whole words after spelling is folded and plurals are reduced, and a phrase matches only as written in order. They widen keyword matching. Meaning-based matching already relates words like these on its own.

Replace Synonyms (PUT /v1/indexes/{index}/synonyms) sets the whole list. Synonyms apply as soon as they are saved. There is no build to wait for.

Rules

A rule changes the results of the queries that match its condition. The condition is a pattern and an anchoring: is, contains, startsWith or endsWith. An empty pattern matches every query.

A rule can do four things:

  • Promote pins a record at a position, counted from 0.
  • Hide removes a record from the results.
  • Filter adds a filter to the query, in Captain’s syntax or as an Algolia filters string.
  • Boost lifts results that match a filter. It reorders results the query already found, and never adds new ones.
{
"objectID": "r1",
"condition": {"pattern": "rain jacket", "anchoring": "contains"},
"consequence": {
"promote": [{"objectID": "sku-20311", "position": 0}],
"hide": [{"objectID": "sku-20402"}]
}
}

Replace Rules (PUT /v1/indexes/{index}/rules) sets the whole list. Rules apply as soon as they are saved, like synonyms.

Repeatable results

With deterministic on, the same query and filters return the same records in the same order, with the same total, for as long as the index receives no writes. That holds across restarts and under load. It isn’t promised across rebuilds, or while records are added, changed or deleted.

An index built with fewer than 100,000 records has deterministic on by default. An index with 100,000 records or more has it off. A setting of true or false always wins over the default. Set it yourself with Update Settings. Captain can also move the 100,000-record limit for your tenant.

A change to field types, weights, facets or sort applies when the index is rebuilt. Update Settings starts that rebuild and returns it as job. Search serves with the previous settings until the rebuild is live. Get Update Status reports where it stands in build.

Synonyms and rules don’t need a build. They apply to the next search.

Settings from Algolia

An Algolia import carries the index’s settings, synonyms and rules, and runs as soon as you send it. An import that reads from Algolia maps the settings onto the field settings on this page by default. An export sent in the body, or settings sent for records added earlier, does so with "object_search": true. An export’s reply lists each setting, synonym and rule as mapped, approximated or not mapped, with the reason. An import that reads from Algolia reports the counts in its job.

Algolia settingIn Captain
objectIDThe record id.
searchableAttributesTiers of text fields. The first entry is primary, the second secondary, the rest low. Fields not listed aren’t searched. ordered() and unordered() aren’t reproduced. Keyword fields, such as a color, are matched as attributes whatever their tier, and title, brand, description and category keep Captain’s built-in matching.
attributesForFacetingFilterable facets. filterOnly(x) becomes filterable but not faceted. searchable(x) becomes a facet, but searching inside facet values isn’t built. afterDistinct(x) becomes a plain facet. Fields not listed aren’t faceted, and prose text can’t be a facet.
customRankingNumber and date fields become sortable and set custom_ranking in Algolia’s order, up to 4 fields. Algolia’s tie-breaking criteria (typo, geo, words, filters, proximity, attribute, exact) aren’t reproduced. A string field isn’t ranked, and the import report says so.
rankingNot supported. Captain’s relevance model decides the order.
replicasNot needed. Sort each request with sort.
distinct, attributeForDistinctgroup_by on the field, up to 10 records per group, taken from the 100 most relevant records, or further down the 1,000 when a page needs more groups. The field needs one keyword, identifier or enum value per record.
typoTolerancetrue works as in Algolia: one typo from 4 letters and two from 8. min and strict are approximated. false isn’t supported.
minWordSizefor1Typo, minWordSizefor2TyposFixed at 4 and 8.
disableTypoToleranceOnAttributesIdentifier fields are never corrected. Other fields follow the usual typo rules.
allowTyposOnNumericTokensNumbers and codes are never corrected.
disableTypoToleranceOnWordsNot supported.
ignorePluralsPlurals always match their singular.
removeStopWordsCaptain’s own English stop words.
queryLanguages, indexLanguagesEnglish only.
optionalWords, removeWordsIfNoResultsApproximated. A search never requires every word to match.
decompoundQueryApproximated. Hyphens, and words joined or split, are handled.
attributesToHighlight, attributesToSnippetSet per request with highlight: true.
attributesToRetrieve, hitsPerPage, paginationLimitedTo, maxValuesPerFacet, highlightPreTag, highlightPostTag, responseFieldsSupported.
unretrievableAttributesNot supported. A field is either returned or dropped from the index.
advancedSyntax, relevancyStrictness, separatorsToIndex, camelCaseAttributes, enablePersonalization, renderingContent, userDataNot supported.

Synonyms and rules map this way:

AlgoliaIn Captain
synonym, oneWaySynonymThe same synonyms.
altCorrection1, altCorrection2One-way synonyms. A correction isn’t ranked below exact matches.
placeholderNot supported.
Rule pattern and anchoringThe same condition.
Rule promote, hide and params.filtersThe same consequence.
Rule optionalFiltersA boost.
Rule validityIgnored: the rule always applies. A rule whose windows have all ended is skipped, and so is a disabled rule.
Rule contexts, filter conditions, more than one condition, query edits, automaticFacetFilters, userData, renderingContentNot supported. A rule that has nothing else left isn’t imported.

Next steps

  • Records: field types, and how changes reach search.
  • Search: filters, facets, sort and grouping in a request.
  • Algolia compatibility: keep your Algolia client and query Captain.