Quickstart

App 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 two keys at onboarding, along with your tenant’s endpoint. The write key creates indexes and adds records, and it stays on your server. The search key runs searches, and it’s safe to use in a browser or an app. 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",
"id_field": "id",
"status": "empty",
"record_count": 0,
"pending_changes": false,
"build_id": null,
"created_at": 1759093140
}

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": 20000,
"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",
"lanes": [
"lex",
"splade",
"dense"
],
"build": "b20260928201247"
},
"timing": {
"engine_ms": 21.4,
"stages": {
"understand": 0.6,
"embed": 4.2,
"qdrant": 9.8,
"fuse": 0.4,
"facets": 2.1,
"payload": 1.3
}
}
}

The reply lists results under hits, best first. Each hit carries the record’s id, its fields and a relevance score. total counts every matching record, and timing reports where the time went.

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",
"lanes": [
"lex",
"splade",
"dense"
],
"build": "b20260928201247",
"filter": "brand IN [\"Merrell\", \"Salomon\"] AND price <= 150"
},
"timing": {
"engine_ms": 21.4,
"stages": {
"understand": 0.6,
"embed": 4.2,
"qdrant": 9.8,
"fuse": 0.4,
"facets": 2.1,
"payload": 1.3
}
}
}

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",
"lanes": [
"lex",
"splade",
"dense"
],
"build": "b20260928201247"
},
"timing": {
"engine_ms": 21.4,
"stages": {
"understand": 0.6,
"embed": 4.2,
"qdrant": 9.8,
"fuse": 0.4,
"facets": 2.1,
"payload": 1.3
}
}
}

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.
© 2026 Captain