> For a complete page index of the Captain API documentation, fetch https://docs.captain.dev/llms.txt?excludeSpec=true

# Update Settings

PATCH https://acme.captain.dev/v1/indexes/{index}/settings
Content-Type: application/json

Change some settings, as a JSON merge patch. The reply's `job` is the rebuild that applies them; search serves the old settings until it succeeds. `acl`, `tenant_field` and `audit` are set with Captain (403).

Reference: https://docs.captain.dev/reference/product-search/indexes/update-settings

## Authentication

- `Authorization` header (bearer token, required) — A write key, issued by Captain. Server side only. Sent as Authorization: Bearer \<key>.

## Request

### Path parameters

- `index` (string, required) — index name

### Query parameters

- `rebuild` (enum, optional, default: false) — Off unless Captain turns it on for your tenant, and ignored until then. true: accept a change that only a full build applies; the change is saved and the build starts (202 with the job)
  - Allowed values: `true`, `false`

### Headers

- `Idempotency-Key` (string, optional) — Replays within 24 hours return the stored response (header Idempotent-Replayed: true). The same key on a different request answers 422. A concurrent duplicate answers 409. Failed requests do not keep the key.

## Response

### 200

saved

- `object_search` (boolean, optional)
- `settings` (ObjectSettings, optional) — Typed fields inferred from the records, with explicit overrides. Knobs, not tuning: ranking uses the one general config for every index. Applies at the next build.
- `effective` (SettingsResponseEffective, optional, nullable) — every field with its inferred or set type and flags (plus up to 3 example values under samples; never for the access-list fields), and children. Identifier fields carry format (cusip, isin, lei, cik, docket, citation, icd10) when 80% of values share one
- `served` (SettingsResponseServed, optional, nullable) — the field table of the build being served
- `warnings` (list of SettingsResponseWarningsItems, optional) — settings that were resolved by a rule rather than by you. FACET_NO_COUNTS: a facet with more than 1,000 distinct values, or a date facet, has no value counts (it still filters, sorts and gives facet_stats min and max). Otherwise: several fields claiming a single-valued role (title, brand, category, section), with the one that won; ID_FROM_CODE_FIELD: no id field was given and the build keys on the code field named. UNKNOWN_FIELD: settings.fields names a field no record added so far has, so it does nothing yet. FACET_ON_TEXT: a facet on a text field, with the share of its values that differ; type it keyword if its values are short labels. ID_INFERRED (schema preview): the id was detected from the data, please confirm it. SETTING_NO_EFFECT: weight or role set on a built-in field name (title, description, brand, category and the other built-in names), which keeps its built-in role, so the setting does nothing. None blocks
- `applies_at` (string, optional) — when the settings take effect: served, the next build, when job \<id> succeeds, or the build after a running job
- `job` (Job, optional, nullable) — Update Settings and Replace Settings, answered 202: the rebuild that applies the change, or the build already running, after which the next build applies it

### 202

saved; job is the build that applies it

- `object_search` (boolean, optional)
- `settings` (ObjectSettings, optional) — Typed fields inferred from the records, with explicit overrides. Knobs, not tuning: ranking uses the one general config for every index. Applies at the next build.
- `effective` (SettingsResponseEffective, optional, nullable) — every field with its inferred or set type and flags (plus up to 3 example values under samples; never for the access-list fields), and children. Identifier fields carry format (cusip, isin, lei, cik, docket, citation, icd10) when 80% of values share one
- `served` (SettingsResponseServed, optional, nullable) — the field table of the build being served
- `warnings` (list of SettingsResponseWarningsItems, optional) — settings that were resolved by a rule rather than by you. FACET_NO_COUNTS: a facet with more than 1,000 distinct values, or a date facet, has no value counts (it still filters, sorts and gives facet_stats min and max). Otherwise: several fields claiming a single-valued role (title, brand, category, section), with the one that won; ID_FROM_CODE_FIELD: no id field was given and the build keys on the code field named. UNKNOWN_FIELD: settings.fields names a field no record added so far has, so it does nothing yet. FACET_ON_TEXT: a facet on a text field, with the share of its values that differ; type it keyword if its values are short labels. ID_INFERRED (schema preview): the id was detected from the data, please confirm it. SETTING_NO_EFFECT: weight or role set on a built-in field name (title, description, brand, category and the other built-in names), which keeps its built-in role, so the setting does nothing. None blocks
- `applies_at` (string, optional) — when the settings take effect: served, the next build, when job \<id> succeeds, or the build after a running job
- `job` (Job, optional, nullable) — Update Settings and Replace Settings, answered 202: the rebuild that applies the change, or the build already running, after which the next build applies it

## Errors

### 422 Unprocessable Entity Error

FIELD_NEEDS_REBUILD (off unless Captain turns it on for your tenant): on a built index, the change only takes effect after a full build. Nothing is saved. The body names the fields, the reason, and estimated_minutes (from this server's measured full builds, or null). Send again with ?rebuild=true to save it and start the build.

- `error` (string, optional) — machine code on /v1 management routes (VALIDATION_ERROR, INDEX_NOT_FOUND, INDEX_NOT_READY, INDEX_EXISTS, INDEX_TOO_LARGE, NO_DOCUMENTS, JOB_RUNNING, INDEXING_UNAVAILABLE, PAYLOAD_TOO_LARGE, RATE_LIMITED, ORIGIN_NOT_ALLOWED, UNAUTHORIZED, FORBIDDEN, IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED, KEY_NOT_FOUND, JOB_NOT_FOUND, FILTER_INVALID, FACET_UNKNOWN, ACCESS_DENIED, FIELD_NEEDS_REBUILD, RECORD_NOT_FOUND, NOT_SUPPORTED, AUDIT_UNAVAILABLE, OPERATOR_REQUIRED, OPERATOR_NOT_ALLOWED, PAGE_TOO_DEEP, JOB_NOT_CANCELLABLE, ROUTE_NOT_FOUND, METHOD_NOT_ALLOWED, ALIAS_NOT_FOUND, ALIAS_NOT_INDEX, INDEX_HAS_ALIAS, KEY_IS_CALLER, KEY_ISSUED_BY_CAPTAIN, CASCADE_INCLUDES_CALLER, ID_CONFLICT, ALGOLIA_APP_NOT_FOUND; from the search routes also INDEX_NOT_FOUND, VARIANT_NOT_FOUND, RECORD_NOT_FOUND, ACCESS_DENIED, FORBIDDEN, UNAUTHORIZED, INTERNAL_ERROR, UNAVAILABLE, UPSTREAM_ERROR, UPSTREAM_TIMEOUT, and 503 FACET_TIMEOUT when counting facets ran past its time limit: retry, or name fewer facet_keys or filter more). Every /v1 error has this shape; branch on error, show message. The clients add BUILD_FAILED (status 0) when build(wait) sees the job fail: the server answered every call, and the job error says why.
- `message` (string, optional)
- `legacy_error` (string, optional) — RECORD_NOT_FOUND only: its former name, DOCUMENT_NOT_FOUND, for clients that still branch on it
- `position` (integer, optional) — FILTER_INVALID: the character where the expression went wrong
- `expected` (list of string, optional) — FILTER_INVALID: what could come there (fields, operators, enum values)
- `param` (string, optional) — FILTER_INVALID: filter, or prefer[i].filter
- `id_suggestion` (V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaIdSuggestion, optional) — every record lacked the id field: the field the data points to, for you to name as id_field when creating the index (nothing is set for you)
- `status_code` (integer, optional)
- `details` (list of V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaDetailsItems, optional)
- `fields` (list of string, optional)
- `reason` (string, optional)
- `estimated_minutes` (double, optional, nullable)

## Types

### ObjectSettings

Typed fields inferred from the records, with explicit overrides. Knobs, not tuning: ranking uses the one general config for every index. Applies at the next build.

- `fields` (map from string to FieldSetting, optional) — overrides, by field name
- `searchable` (list of string, optional) — exactly these text fields are searched; the first is primary
- `identifiers` (list of string, optional) — exact codes: ranked first on an exact match, exact filters
- `facets` (list of string, optional)
- `sortable` (list of string, optional)
- `acl` (ObjectSettingsAcl, optional) — record-level access control, enforced in the engine on every result, count and facet, and on spelling help (it learns only from what the key may see). Keys must carry principals; the admin key sees all
- `tenant_field` (string, optional) — a keyword field every non-admin key must be restricted to
- `strict_types` (boolean, optional) — true: an upload rejects a record whose value doesn't fit its field's declared type (date, number, bool, enum, geo), by NDJSON line or array index (code BAD_VALUE; on_error=skip stages the rest). false (default): the upload takes it, and the build leaves that value empty (the record stays searchable by its text) and counts it in a BAD_VALUES warning; a field whose type the settings declare and where more than half the values don't fit fails the build with error_code BAD_VALUES (an inferred type in that case is read as text instead: TYPE_FALLBACK warning)
- `deterministic` (boolean, optional) — exact search and stable tie-breaking: the same query and filters give the same ids, order and total (scores equal to within 1e-5) while the index receives no writes, across restarts and concurrent load. Not promised across rebuilds or after records are added, changed or deleted. Unset: on for an index built with fewer than 100,000 records, off at 100,000 or more; true or false always wins
- `audit` (ObjectSettingsAudit, optional)
- `group_by` (GroupBy, optional) — the index's default grouping for every search (Algolia's distinct). A request's group_by overrides it; group_by: false turns it off for one request
- `noun` (ObjectSettingsNoun, optional) — what the index holds, for interfaces (a label only)

### SettingsResponseEffective

every field with its inferred or set type and flags (plus up to 3 example values under samples; never for the access-list fields), and children. Identifier fields carry format (cusip, isin, lei, cik, docket, citation, icd10) when 80% of values share one

### SettingsResponseServed

the field table of the build being served

### SettingsResponseWarningsItems

- `code` (enum, optional)
  - Allowed values: `ROLE_CLAIMED_TWICE`, `ID_FROM_CODE_FIELD`, `FACET_NO_COUNTS`, `UNKNOWN_FIELD`, `FACET_ON_TEXT`, `ID_INFERRED`, `SETTING_NO_EFFECT`
- `settings` (list of string, optional) — SETTING_NO_EFFECT: the settings that do nothing (weight, role)
- `role` (string, optional)
- `distinct_share` (double, optional)
- `field` (string, optional)
- `fields` (list of string, optional)
- `chosen` (string, optional, nullable)
- `message` (string, optional)

### Job

- `id` (string, optional)
- `type` (enum, optional) — index.write: record updates and deletes through the ordered update log, one job per burst
  - Allowed values: `index.build`, `index.delete`, `index.import`, `index.write`
- `index` (string, optional)
- `seq_from` (integer, optional) — index.write: the first update this job carries
- `seq_to` (integer, optional) — index.write: the last update this job carries
- `records` (integer, optional) — index.write: records written
- `status` (enum, optional)
  - Allowed values: `queued`, `running`, `importing`, `succeeded`, `failed`
- `created_at` (integer, optional)
- `started_at` (integer, optional, nullable)
- `finished_at` (integer, optional, nullable)
- `stages` (list of JobStagesItems, optional)
- `progress` (BuildProgress, optional) — index.build: how far the build has got. records_done counts the records the running step has done; a step that doesn't count records (tables, verify, publish) reports every record done
- `build_id` (string, optional)
- `error` (string, optional, nullable)
- `error_code` (string, optional) — why a failed job failed; error says it in words to show. A build: BAD_VALUES (more than half of a field's values don't fit its declared type; see bad_values), BUILD_QUALITY (the build's checks refused it; the previous build keeps serving), NO_RECORDS (every record was deleted or cleared before the build read them), CANCELLED (the index is being deleted), ENGINE_ERROR (a fault on Captain's side, in the build or in running the job; the index keeps serving its previous build; retry). An index delete: DELETE_FAILED (Captain couldn't delete the index; retry). An Algolia import: the Algolia code (ALGOLIA_APP_NOT_FOUND, ALGOLIA_UNREACHABLE, ALGOLIA_INDEX_NOT_FOUND, ALGOLIA_FORBIDDEN, ALGOLIA_TIMEOUT, ALGOLIA_ERROR, INDEX_TOO_LARGE, VALIDATION_ERROR) or IMPORT_FAILED (a fault on Captain's side; nothing was changed; retry)
- `bad_values` (list of JobBadValuesItems, optional) — with error_code BAD_VALUES: per field, count of bad values, of how many, and up to 3 examples
- `warnings` (list of JobWarningsItems, optional) — \{code, message}. Settings resolved by a rule (the same warnings GET settings shows). Any build: SETTLE\_TIMEOUT (the build is live, but its search results are still settling and may shift for a few minutes; no action needed)
- `facet_report` (list of JobFacetReportItems, optional) — per number or date facet, whether it has value counts and why
- `result` (JobResult, optional, nullable)

### V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaIdSuggestion

every record lacked the id field: the field the data points to, for you to name as id_field when creating the index (nothing is set for you)

- `message` (string, optional)
- `pick` (string, optional, nullable) — null: none qualifies, or fewer than 50 records were sent
- `id_candidates` (list of string, optional)
- `near` (list of V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaIdSuggestionNearItems, optional) — runners-up: field, missing, duplicates, reason

### V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaDetailsItems

### FieldSetting

- `type` (enum, optional)
  - Allowed values: `text`, `keyword`, `enum`, `identifier`, `number`, `date`, `geo`, `bool`, `children`, `url`, `skip`
- `searchable` (boolean, optional) — text, keyword, enum, identifier, number and bool are searched by default
- `weight` (enum, optional) — text: how much a match in the field counts. primary ranks it like a title, secondary (the default) like body text, low like an attribute. Tiers, not numbers: every index uses the one general ranking config. title, description, brand, category and other built-in names keep their built-in role: weight or role on one of them does nothing (warning SETTING_NO_EFFECT)
  - Allowed values: `primary`, `secondary`, `low`
- `role` (enum, optional) — section: the heading in force ("9.3 Limitation Of Liability"), passed to passage ingestion, which then does not infer one
  - Allowed values: `title`, `brand`, `category`, `description`, `section`
- `facet` (boolean, optional)
- `filterable` (boolean, optional)
- `sortable` (boolean, optional) — number and date only
- `unit` (string, optional) — number: its unit (lb, in, gal, w, ...); values in other units of the quantity are converted
- `values` (list of string, optional) — enum: the closed set of values
- `list` (boolean, optional)

### ObjectSettingsAcl

record-level access control, enforced in the engine on every result, count and facet, and on spelling help (it learns only from what the key may see). Keys must carry principals; the admin key sees all

- `field` (string, required) — the record field holding principals (users, groups, roles)
- `deny_field` (string, optional) — principals that may never see the record (beats field)
- `missing` (enum, optional, default: deny) — a record without the field
  - Allowed values: `deny`, `public`

### ObjectSettingsAudit

- `query_text` (enum, optional, default: redacted) — how queries appear in the audit log. redacted: PII-redacted text and a keyed fingerprint. hashed: the keyed fingerprint only. none: nothing. full: the raw text (explicit opt-in: it stores whatever users type)
  - Allowed values: `redacted`, `hashed`, `none`, `full`
- `client_ip` (enum, optional, default: keyed) — how the caller's address appears. keyed: a keyed fingerprint (repeated sources correlate, the address is not stored). none: nothing. full: the address (explicit opt-in: personal data)
  - Allowed values: `keyed`, `none`, `full`
- `on_failure` (enum, optional, default: serve) — when a search's audit record cannot be written. serve: answer anyway and count the failure (serving.audit_failures). refuse: answer 503 AUDIT_UNAVAILABLE with no results, for records rules that require a complete log
  - Allowed values: `serve`, `refuse`

### GroupBy

- `field` (string, required) — a single-valued keyword, identifier or enum field. A record without a value is a group of its own
- `size` (integer, optional, default: 1) — hits per group: 1 is Algolia's distinct: true, N is distinct: N

### ObjectSettingsNoun

what the index holds, for interfaces (a label only)

- `singular` (string, required)
- `plural` (string, required)

### JobStagesItems

- `name` (enum, optional)
  - Allowed values: `schema`, `dense`, `splade`, `upload`, `tables`, `verify`, `publish`, `replicate`, `drop`, `pull`, `read`, `map`, `add`, `validate`, `embed`, `upsert`, `refresh`
- `status` (enum, optional)
  - Allowed values: `queued`, `pending`, `running`, `done`, `failed`, `skipped`
- `count` (integer, optional)
- `ms` (double, optional)
- `started_at` (double, optional)
- `finished_at` (double, optional)
- `reason` (string, optional) — why this step was skipped, in words
- `items` (integer, optional)
- `of` (integer, optional)
- `seconds` (integer, optional)

### BuildProgress

index.build: how far the build has got. records_done counts the records the running step has done; a step that doesn't count records (tables, verify, publish) reports every record done

- `step` (string, optional) — the step running now, as named in stages; queued before the first, done after the last
- `step_number` (integer, optional) — its place in the build, from 1
- `steps` (integer, optional) — steps in this build
- `records_done` (integer, optional)
- `records_total` (integer, optional, nullable)

### JobBadValuesItems

### JobWarningsItems

### JobFacetReportItems

- `field` (string, optional)
- `type` (string, optional)
- `distinct` (integer, optional, nullable)
- `facet_counts` (boolean, optional)
- `reason` (string, optional)

### JobResult

- `alias` (string, optional)
- `build` (string, optional)
- `physical` (string, optional)
- `previous` (string, optional, nullable)
- `records` (integer, optional)
- `facets` (list of string, optional)
- `seconds` (integer, optional)
- `checks` (JobResultChecks, optional)
- `constraints` (JobResultConstraints, optional) — index.build: whether measurement queries ('under 30 inches') are scored on the new build, and why not
- `report` (string, optional) — index.import: where the import report is
- `build_job` (string, optional, nullable) — index.import: the build job it queued

### V1IndexesIndexSettingsPatchResponsesContentApplicationJsonSchemaIdSuggestionNearItems

### JobResultChecks

### JobResultConstraints

index.build: whether measurement queries ('under 30 inches') are scored on the new build, and why not

- `ranking` (boolean, required)
- `reason` (string, required, nullable)

## Examples

**Request**

```json
{
  "fields": {
    "material": {
      "type": "keyword",
      "facet": true
    }
  }
}
```

**Response**

```json
{
  "object_search": true,
  "settings": {
    "fields": {
      "brand": {
        "type": "keyword",
        "facet": true
      },
      "material": {
        "type": "keyword",
        "facet": true
      }
    }
  },
  "warnings": [],
  "applies_at": "when job job_20261002193012_9f8e7d succeeds",
  "job": {
    "id": "job_20261002193012_9f8e7d",
    "type": "index.build",
    "index": "products",
    "status": "queued"
  }
}
```

**SDK Code**

```python Python REST
import json
import requests
import uuid

BASE_URL = "https://acme.captain.dev"
API_KEY = "your_write_key"
INDEX = "products"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    "Idempotency-Key": str(uuid.uuid4()),
}

response = requests.patch(
    f"{BASE_URL}/v1/indexes/{INDEX}/settings",
    headers=headers,
    json={
        "fields": {"material": {"type": "keyword", "facet": True}},
    },
    timeout=30.0,
)

print(json.dumps(response.json(), indent=2))
```

```typescript TypeScript REST
const BASE_URL = "https://acme.captain.dev";
const API_KEY = "your_write_key";
const index = "products";

const response = await fetch(`${BASE_URL}/v1/indexes/${index}/settings`, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    fields: { material: { type: "keyword", facet: true } },
  }),
});

const result = await response.json();
console.log(JSON.stringify(result, null, 2));
```