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

# Batch Metadata & Relations

> Update document metadata, chunk metadata, and chunk relations in batches of up to 100 items with per-item results.

## Agent Quick Reference - Batch Writes

* **Document metadata update (merge)**: `PATCH /v3/collections/{collection_name}/documents/metadata/batch`
* **Document metadata overwrite**: `PUT /v3/collections/{collection_name}/documents/metadata/batch`
* **Chunk metadata update (merge)**: `PATCH /v3/collections/{collection_name}/chunks/metadata/batch`
* **Chunk metadata overwrite**: `PUT /v3/collections/{collection_name}/chunks/metadata/batch`
* **Relation create or update**: `PUT /v3/collections/{collection_name}/relations/batch`
* **Envelope**: every request body is `{"items": [...]}`, 1 to 100 items, at most 1 MiB. A bare object is rejected.
* **Per-item correlation**: `item_id`, a non-empty string unique within the request. It is not an idempotency key.
* **Response**: HTTP `200` for any accepted batch. `results[]` has one entry per item, in input order, each with `status` (`succeeded`, `failed`, `unknown`), `http_status`, and exactly one of `data` or `error`.
* **Not atomic**: a succeeded item is not rolled back when another item fails.
* **Request-level rejects before any write**: `400` for a malformed envelope, a bad item count, a duplicate `item_id`, or duplicate targets; `413` for a body over 1 MiB.
* **Overwrite is destructive**: omitted keys are removed and `{}` clears everything. Copy the collection first if a revert may be needed.
* **No `Idempotency-Key`** on the batch endpoints.

The batch endpoints apply many metadata or relation writes in one request. They exist for the step after indexing, when a collection needs the values a review or a pipeline has already decided: a per-file ID from another system, a review status on a set of chunks, a typed link between passages. The four metadata endpoints apply the same per-item semantics as their single-item counterparts. The relations endpoint does not: it is an upsert keyed on relation identity, where the single-item endpoint is a plain create. The difference is spelled out in the [Relations](#relations) section.

| Batch endpoint                                                                               | Mirrors                                                                    | Semantics                                                                |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Update Document Metadata in Batches](/reference/v3/document/metadata/batch/update)          | [Update Document Metadata](/reference/v3/document/metadata/update)         | Merge the supplied keys; keep the rest                                   |
| [Overwrite Document Metadata in Batches](/reference/v3/document/metadata/batch/replace)      | [Overwrite Document Metadata](/reference/v3/document/metadata/replace)     | Overwrite the whole object                                               |
| [Update Chunk Metadata in Batches](/reference/v3/documents/chunk/metadata/batch/update)      | [Update Chunk Metadata](/reference/v3/documents/chunk/metadata/update)     | Merge the supplied keys; keep the rest                                   |
| [Overwrite Chunk Metadata in Batches](/reference/v3/documents/chunk/metadata/batch/replace)  | [Overwrite Chunk Metadata](/reference/v3/documents/chunk/metadata/replace) | Overwrite the whole object                                               |
| [Create or Update Chunk Relations in Batches](/reference/v3/documents/chunk/relations/batch) | [Create Chunk Relation](/reference/v3/documents/chunk/relations/create)    | Create a new relation, or replace the metadata of the one existing match |

The batch endpoints do not create documents or chunks, do not delete anything, and do not classify content. They apply values that already exist.

## The request envelope

Every batch request is an object with one key, `items`. Send an array even for a single item; a bare object is rejected with `400`.

```json
{
  "items": [
    {
      "item_id": "update-1",
      "document_id": "doc-a",
      "metadata": {
        "review_status": "reviewed"
      }
    },
    {
      "item_id": "update-2",
      "document_id": "doc-b",
      "metadata": {
        "review_status": "pending"
      }
    }
  ]
}
```

A request holds between 1 and 100 items and at most 1 MiB of UTF-8 JSON. `item_id` is a non-empty string that is unique within the request. It exists only to match results to items. It is not an idempotency key, and the batch endpoints take no `Idempotency-Key` header.

Use the IDs the API returned when indexing or listing. A `document_id` is the value from the indexing job or the document list, and a `chunk_id` is the value from the chunk list or a query result. Filenames and guessed IDs are not accepted.

## The response

An accepted batch always returns HTTP `200`, even when every item fails. The body holds one result per item, in the same order as the request, and the array is never empty.

```json
{
  "results": [
    {
      "item_id": "update-1",
      "status": "succeeded",
      "http_status": 200,
      "data": {
        "document_id": "doc-a",
        "custom_metadata": {
          "review_status": "reviewed",
          "owner": "team-a"
        },
        "filterable": true
      }
    },
    {
      "item_id": "update-2",
      "status": "failed",
      "http_status": 404,
      "error": {
        "code": "target_not_found",
        "message": "Target not found."
      }
    }
  ]
}
```

Each result carries:

* `item_id`, echoed from the request.
* `status`, one of `succeeded`, `failed`, or `unknown`.
* `http_status`, the status the single-item endpoint would have returned for that item.
* Exactly one of `data` (the single-item response for that resource) or `error` (a `code` and a `message`).

Error codes are the ones the matching single-item endpoint uses for the same failure. A missing target and a target outside the key's organization, environment, or collection both return the same item-level `404`, so a response never reveals that an inaccessible target exists.

## Partial success

A batch is not a transaction. When one item fails, the items that succeeded stay written. There is no ordering guarantee between items either, so a later item cannot depend on an earlier one having been applied first.

Validation happens in two stages:

1. **Before any write**, the whole request is checked. A malformed envelope, an item count outside 1 to 100, a duplicate `item_id`, duplicate targets after ID resolution, or a missing required field on any item rejects the whole request with `400`. A body over 1 MiB is rejected with `413`. These responses have no `results` array.
2. **Per item**, target existence and value validation are checked as each item is applied. Those failures land in `results[]` and never stop the other items.

## The `unknown` status

`status: unknown` with `http_status: 500` and the error code `outcome_unknown` means the write's outcome could not be confirmed. The item may or may not have been applied.

```json
{
  "item_id": "update-1",
  "status": "unknown",
  "http_status": 500,
  "error": {
    "code": "outcome_unknown",
    "message": "Write outcome could not be confirmed."
  }
}
```

`failed` is reserved for definite failures. Treat `unknown` differently: read the target back before deciding whether to replay the item. When a connection drops with no response at all, every item in that request is unknown until it is checked.

## `filterable`

Metadata is stored in two places: the collection record, which every read returns, and the search index, which query filters use. A metadata item can succeed while the search index write does not land. The result then reports `filterable: false`.

```json
{
  "item_id": "update-1",
  "status": "succeeded",
  "http_status": 200,
  "data": {
    "document_id": "doc-a",
    "custom_metadata": {
      "review_status": "reviewed"
    },
    "filterable": false
  }
}
```

The metadata is saved and will be returned on reads. Query filters may not match it until the index catches up. Track those items for a later check rather than treating them as fully ready for retrieval.

## Overwrite is destructive

`PUT` overwrites the whole custom metadata object on each target. Any key left out of the request is removed, and an empty object `{}` clears everything on that target. There is no undo.

Before an overwrite batch, validate the payload carefully, and consider [copying the collection](/reference/collections/copy) first. A copy is a complete snapshot that can be queried or restored if the overwrite turns out to be wrong. Because the batch endpoints take no `Idempotency-Key`, the copy is the safe way to make an overwrite reversible.

## Relations

`PUT /v3/collections/{collection_name}/relations/batch` creates or updates relations. Each item names a `source_chunk_id`, a `target_chunk_id`, a `relation_type`, and a `metadata` object; `metadata` is required on every item. `target_document_id` is optional and, when supplied, must be the document that contains the target chunk.

A relation's identity is the collection plus the source chunk, the target chunk, and the relation type. For each item:

* **No relation with that identity exists**: one is created and the item returns `201` with the new `relation_id`.
* **Exactly one exists**: it keeps its `relation_id`, and its entire `metadata` object is replaced by the item's `metadata`. Keys omitted from the item are removed, and `{}` clears the relation's metadata. The item returns `200`.
* **More than one exists**: nothing is changed and the item returns `409` with the error code `relation_identity_ambiguous`. Delete the extras with [Delete Chunk Relation](/reference/v3/documents/chunk/relations/delete), then retry the item.

Relations not named in the request are left unchanged, and no reverse relation is created.

This is the one place the batch endpoint deliberately differs from its single-item counterpart. [Create Chunk Relation](/reference/v3/documents/chunk/relations/create) always creates, so calling it twice with the same source, target, and type produces two relations. The batch endpoint treats that identity as unique: it updates the one existing match, and it refuses to guess when a collection already holds duplicates from the single-item endpoint.

A missing source or target chunk fails the item with `404`. A relation from a chunk to itself is rejected the same way the single-item endpoint rejects it.