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

# Add Records

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

Add records, or replace records with the same id. They become searchable within a few seconds. Send a JSON array (up to 1,000 records and 8 MiB), NDJSON (one record per line, up to 100,000 records and 64 MiB) or CSV with a header row (the NDJSON limits). Each record is a JSON object with an id and the index's fields. See Records for how to structure a record.

Reference: https://docs.captain.dev/reference/product-search/records/add-records

## 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

- `on_error` (enum, optional, default: fail) — fail: one bad record rejects the request (400, details names every bad row). skip: the good records are added and the bad ones returned in rejected
  - Allowed values: `fail`, `skip`
- `on_duplicate` (enum, optional, default: warn) — an id sent more than once in one upload. warn: the last copy is kept and the answer carries duplicates and a DUPLICATE_ID warning. fail: the request is rejected (400, details names each repeated id and its lines)
  - Allowed values: `warn`, `fail`
- `line_offset` (integer, optional, default: 0) — CSV: added to every line the answer names, so a client that splits a big file (repeating the header) reports the file's own lines

### 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.

### Body (application/json)

This endpoint expects a list of V1IndexesIndexRecordsPostRequestBodyContentApplicationJsonSchemaItems.

- `list of V1IndexesIndexRecordsPostRequestBodyContentApplicationJsonSchemaItems`

## Response

### 202

Accepted. The records are searchable within a few seconds.

- `index` (string, optional)
- `accepted` (integer, optional) — Records accepted from this request.
- `new` (integer, optional) — Accepted records with an id the index did not have.
- `replaced` (integer, optional) — Accepted records that replaced a record with the same id.
- `record_count` (integer, optional) — Records the index holds after this request, built or not.
- `max_records` (integer, optional, nullable) — The most records the index can hold.
- `rejected` (list of V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaRejectedItems, optional) — on_error=skip: the first 100 rows left out
- `rejected_count` (integer, optional) — on_error=skip: every row left out
- `id_suggestion` (V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaIdSuggestion, 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)
- `duplicates` (integer, optional) — records that repeat an id sent earlier in this upload (the last copy is kept); 0 when none. A repeat across uploads is a replace, counted in replaced
- `warnings` (list of V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaWarningsItems, optional) — DUPLICATE_ID when duplicates > 0: count, ids (the first 20, each with its lines or indexes) and message
- `seq` (integer, optional) — This change's place in order. Get Update Status reports the last applied seq.
- `accepted_at` (double, optional) — unix seconds
- `live_updates` (string, optional)
- `regions_applied` (list of string, optional) — The region where these records become searchable (GET .../updates shows when).
- `regions_pending` (list of string, optional) — Regions without these records until the index's next build is published and restored there.
- `replicates_on_next_build` (boolean, optional)

## Types

### V1IndexesIndexRecordsPostRequestBodyContentApplicationJsonSchemaItems

### V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaRejectedItems

- `line` (integer, optional) — NDJSON: the line, from 1
- `index` (integer, optional) — JSON array: the position
- `code` (enum, optional)
  - Allowed values: `BAD_JSON`, `BAD_ROW`, `MISSING_ID`, `NOT_AN_OBJECT`
- `message` (string, optional)

### V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaIdSuggestion

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 V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaIdSuggestionNearItems, optional) — runners-up: field, missing, duplicates, reason

### V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaWarningsItems

### V1IndexesIndexRecordsPostResponsesContentApplicationJsonSchemaIdSuggestionNearItems

## Examples

**Request**

```json
[
  {
    "id": "sku-20001",
    "title": "Speedcross 6 Trail Running Shoe",
    "brand": "Salomon",
    "category": "Trail Running Shoes",
    "price": 139,
    "color": "black"
  },
  {
    "id": "sku-10482",
    "title": "Moab 3 Mid Waterproof Hiking Boot",
    "brand": "Merrell",
    "category": "Hiking Boots",
    "price": 129,
    "color": "brown"
  }
]
```

**Response**

```json
{
  "index": "products",
  "accepted": 2,
  "new": 1,
  "replaced": 1,
  "record_count": 12841,
  "max_records": null,
  "rejected": [],
  "rejected_count": 0,
  "duplicates": 0,
  "warnings": [],
  "seq": 1842,
  "accepted_at": 1759095298.114,
  "regions_applied": [
    "us-east-1"
  ],
  "regions_pending": [
    "us-west-1",
    "us-east-2"
  ]
}
```

**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.post(
    f"{BASE_URL}/v1/indexes/{INDEX}/records",
    headers=headers,
    json=[
        {
            "id": "sku-20001",
            "title": "Speedcross 6 Trail Running Shoe",
            "brand": "Salomon",
            "category": "Trail Running Shoes",
            "price": 139.0,
            "color": "black",
        },
        {
            "id": "sku-10482",
            "title": "Moab 3 Mid Waterproof Hiking Boot",
            "brand": "Merrell",
            "category": "Hiking Boots",
            "price": 129.0,
            "color": "brown",
        },
    ],
    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}/records`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify([
    {
      id: "sku-20001",
      title: "Speedcross 6 Trail Running Shoe",
      brand: "Salomon",
      category: "Trail Running Shoes",
      price: 139.0,
      color: "black",
    },
    {
      id: "sku-10482",
      title: "Moab 3 Mid Waterproof Hiking Boot",
      brand: "Merrell",
      category: "Hiking Boots",
      price: 129.0,
      color: "brown",
    },
  ]),
});

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