Skip to navigation

Quickstart

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

This guide goes from nothing to search results. You create an index, add three product records, check that they’re live, and search them.

Captain gives you three keys at onboarding, along with your tenant’s endpoint: a search key, a write key and an issuer key. The write key creates indexes and adds records, and it stays on your server. The search key runs searches and reads the index’s analytics, so keep it on your server too. The issuer key mints short-lived scoped keys, and it stays on your server as well. In a browser or an app, use a scoped key limited to your site with allowed_origins. The examples use the tenant acme. Replace it with the endpoint from onboarding.

1. Create an index

An index holds one kind of record, such as products, articles or listings. Give it a name of lowercase letters, digits, dashes and underscores.

curl -X POST "https://acme.captain.dev/v1/indexes" \
-H "Authorization: Bearer $CAPTAIN_WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "products"
}'
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"
}

The new index is empty until records arrive. Field types are optional. When you leave them out, Captain types each field from the first records you add. To set them yourself, see Create Index and the field types in Records.

2. Add records

Send records as a JSON array. Each record needs an id and the fields you want search to know about.

curl -X POST "https://acme.captain.dev/v1/indexes/products/records" \
-H "Authorization: Bearer $CAPTAIN_WRITE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '[
{
"id": "sku-10482",
"title": "Moab 3 Mid Waterproof Hiking Boot",
"brand": "Merrell",
"category": "Hiking Boots",
"price": 139.0,
"rating": 4.6,
"image_url": "https://cdn.acme.example/img/sku-10482.jpg",
"description": "Waterproof leather and mesh mid boot with a Vibram TC5+ outsole and a breathable membrane."
},
{
"id": "sku-10517",
"title": "X Ultra 4 Mid GTX Hiking Boot",
"brand": "Salomon",
"category": "Hiking Boots",
"price": 165.0,
"rating": 4.5,
"image_url": "https://cdn.acme.example/img/sku-10517.jpg",
"description": "GORE-TEX mid boot with a Contagrip outsole, built for rocky trails."
},
{
"id": "sku-11020",
"title": "Targhee III Waterproof Mid Boot",
"brand": "KEEN",
"category": "Hiking Boots",
"price": 150.0,
"rating": 4.4,
"image_url": "https://cdn.acme.example/img/sku-11020.jpg",
"description": "Waterproof nubuck boot with a KEEN.DRY membrane and a wide toe box."
}
]'
Example: (202 Accepted)
{
"index": "products",
"accepted": 3,
"new": 3,
"replaced": 0,
"rejected": [],
"rejected_count": 0,
"duplicates": 0,
"warnings": [],
"record_count": 3,
"max_records": null,
"seq": 1,
"accepted_at": 1759093201.552,
"regions_applied": [
"us-east-1"
],
"regions_pending": []
}

The reply confirms that three new records were accepted, and gives the change a seq number. Records become searchable within a few seconds. There’s no separate build step.

3. Check the records are live

Get Update Status reports the last change search has applied. When applied_seq reaches the seq from step 2, the records are live.

curl -X GET "https://acme.captain.dev/v1/indexes/products/updates" \
-H "Authorization: Bearer $CAPTAIN_WRITE_KEY"
Example: (200 OK)
{
"accepted_seq": 1,
"applied_seq": 1,
"pending": 0
}

In an app, you rarely need this check. It’s useful in a script that adds records and then searches them straight away.

Searches use the search key. Send the words people type as query.

curl -X POST "https://acme.captain.dev/v1/indexes/products/search" \
-H "Authorization: Bearer $CAPTAIN_SEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "waterproof hiking boots",
"k": 3
}'
Example: (200 OK)
{
"hits": [
{
"id": "sku-10482",
"title": "Moab 3 Mid Waterproof Hiking Boot",
"brand": "Merrell",
"category": "Hiking Boots",
"price": 139.0,
"rating": 4.6,
"image_url": "https://cdn.acme.example/img/sku-10482.jpg",
"description": "Waterproof leather and mesh mid boot with a Vibram TC5+ outsole and a breathable membrane.",
"score": 0.912
},
{
"id": "sku-11020",
"title": "Targhee III Waterproof Mid Boot",
"brand": "KEEN",
"category": "Hiking Boots",
"price": 150.0,
"rating": 4.4,
"image_url": "https://cdn.acme.example/img/sku-11020.jpg",
"description": "Waterproof nubuck boot with a KEEN.DRY membrane and a wide toe box.",
"score": 0.874
},
{
"id": "sku-10517",
"title": "X Ultra 4 Mid GTX Hiking Boot",
"brand": "Salomon",
"category": "Hiking Boots",
"price": 165.0,
"rating": 4.5,
"image_url": "https://cdn.acme.example/img/sku-10517.jpg",
"description": "GORE-TEX mid boot with a Contagrip outsole, built for rocky trails.",
"score": 0.851
}
],
"total": 3,
"query_id": "7f3a9c2e4b1d48e6a0c5d2f18b93e470",
"query_understanding": {
"category_match": [
"Hiking Boots"
],
"evidence": 3,
"evidence_missing": 0,
"missing_words": []
},
"request": {
"mode": "full",
"build": "b20260928201247"
},
"timing": {
"engine_ms": 21.4
}
}

The reply lists results under hits, best first. Each hit carries the record’s id, its fields and a relevance score. total is an estimate of how many records matched, not an exact count, so page with page.has_more rather than total. Send "exact_total": true when you need a count to show, and check total_exact in the reply. timing.engine_ms is how long Captain took to answer, in milliseconds.

5. Add a filter

A filter keeps only the records that meet every condition. List Filters shows which fields you can filter on and which operators each one accepts.

curl -X POST "https://acme.captain.dev/v1/indexes/products/search" \
-H "Authorization: Bearer $CAPTAIN_SEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "hiking boots",
"filter": "brand IN [\"Merrell\", \"Salomon\"] AND price <= 150",
"k": 3
}'
Example: (200 OK)
{
"hits": [
{
"id": "sku-10482",
"title": "Moab 3 Mid Waterproof Hiking Boot",
"brand": "Merrell",
"category": "Hiking Boots",
"price": 139.0,
"rating": 4.6,
"image_url": "https://cdn.acme.example/img/sku-10482.jpg",
"description": "Waterproof leather and mesh mid boot with a Vibram TC5+ outsole and a breathable membrane.",
"score": 0.904
}
],
"total": 1,
"query_id": "2c81d7e05a3f4b9c8e16f0a4d72b5c93",
"query_understanding": {
"category_match": [
"Hiking Boots"
],
"evidence": 2,
"evidence_missing": 0,
"missing_words": []
},
"request": {
"mode": "full",
"build": "b20260928201247",
"filter": "brand IN [\"Merrell\", \"Salomon\"] AND price <= 150"
},
"timing": {
"engine_ms": 21.4
}
}

Only the Merrell boot is both from one of the two brands and at or under $150, so it’s the one result.

6. Count facets

Facets count how many results have each value of a field, which is what a sidebar of brand or category checkboxes needs.

curl -X POST "https://acme.captain.dev/v1/indexes/products/search" \
-H "Authorization: Bearer $CAPTAIN_SEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "hiking boots",
"k": 3,
"facet_keys": [
"brand"
]
}'
Example: (200 OK)
{
"hits": [
{
"id": "sku-10482",
"title": "Moab 3 Mid Waterproof Hiking Boot",
"brand": "Merrell",
"category": "Hiking Boots",
"price": 139.0,
"rating": 4.6,
"image_url": "https://cdn.acme.example/img/sku-10482.jpg",
"description": "Waterproof leather and mesh mid boot with a Vibram TC5+ outsole and a breathable membrane.",
"score": 0.921
},
{
"id": "sku-10517",
"title": "X Ultra 4 Mid GTX Hiking Boot",
"brand": "Salomon",
"category": "Hiking Boots",
"price": 165.0,
"rating": 4.5,
"image_url": "https://cdn.acme.example/img/sku-10517.jpg",
"description": "GORE-TEX mid boot with a Contagrip outsole, built for rocky trails.",
"score": 0.883
},
{
"id": "sku-11020",
"title": "Targhee III Waterproof Mid Boot",
"brand": "KEEN",
"category": "Hiking Boots",
"price": 150.0,
"rating": 4.4,
"image_url": "https://cdn.acme.example/img/sku-11020.jpg",
"description": "Waterproof nubuck boot with a KEEN.DRY membrane and a wide toe box.",
"score": 0.862
}
],
"total": 3,
"facets": {
"brand": [
{
"value": "KEEN",
"count": 1
},
{
"value": "Merrell",
"count": 1
},
{
"value": "Salomon",
"count": 1
}
]
},
"facets_exact": true,
"facet_set_size": 3,
"query_id": "b6e2a9f14c7d4e038a5f91c2d6b7e814",
"request": {
"mode": "full",
"build": "b20260928201247"
},
"timing": {
"engine_ms": 21.4
}
}

facets in the reply holds the count for each value of each requested field.

Next steps

  • Records: structure records, update and delete them, and see how changes reach search.
  • Search: typeahead, browse, similar records and multiple searches.
  • Analytics: send click and conversion events, and read what people search for.