Batch Metadata & Relations

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

Batch endpointMirrorsSemantics
Update Document Metadata in BatchesUpdate Document MetadataMerge the supplied keys; keep the rest
Overwrite Document Metadata in BatchesOverwrite Document MetadataOverwrite the whole object
Update Chunk Metadata in BatchesUpdate Chunk MetadataMerge the supplied keys; keep the rest
Overwrite Chunk Metadata in BatchesOverwrite Chunk MetadataOverwrite the whole object
Create or Update Chunk Relations in BatchesCreate Chunk RelationCreate 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.

{
"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.

{
"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.

{
"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.

{
"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 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, 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 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.

© 2026 Captain