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

# Create or Update Chunk Relations in Batches

PUT https://api.captain.dev/v3/collections/{collection_name}/relations/batch
Content-Type: application/json

Create or update up to 100 chunk relations in one request. A relation is identified by its source chunk, target chunk, and `relation_type`. When no relation with that identity exists, one is created and the item returns 201. When exactly one exists, it keeps its `relation_id` and its entire `metadata` object is replaced: keys omitted from the item are removed, and `{}` clears the metadata. Relations not named in the request are left unchanged. This differs from [Create Chunk Relation](/reference/v3/documents/chunk/relations/create), which always creates and can therefore produce duplicate relations; when more than one existing relation matches an item's identity, the batch changes nothing and fails that item with 409 `relation_identity_ambiguous`. Returns 200 with one result per item, in order. Envelope, partial success, and `unknown` outcomes are covered in the [Batch Metadata & Relations](/guides/batch-writes) guide.

Reference: https://docs.captain.dev/reference/v3/documents/chunk/relations/batch

## Request

### Path parameters

- `collection_name` (string, required)

### Body (application/json)

This endpoint expects a BatchRelationUpsertRequestV3.

- `items` (list of BatchRelationUpsertItemV3, required) — 1 to 100 items. The request body may not exceed 1 MiB of UTF-8 JSON.

## Response

### 200

Create or Update Chunk Relations in Batches response.

- `results` (list of BatchRelationUpsertResultV3, required) — One result per request item, in request order. Never empty for an accepted batch.

## Errors

### 400 Bad Request Error

Rejected before any write: malformed envelope, item count outside 1 to 100, duplicate `item_id` or targets, or a structural field error.

- `error` (BatchItemErrorV3, required)

### 401 Unauthorized Error

Missing or invalid authentication.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 403 Forbidden Error

API key cannot write to this collection.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 404 Not Found Error

Collection not found. A missing item target is reported inside `results[]`.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 413 Content Too Large Error

Request body larger than 1 MiB.

- `error` (BatchItemErrorV3, required)

## Types

### BatchRelationUpsertItemV3

- `item_id` (string, required) — Non-empty string, unique within the request. Echoed on the matching result. Not an idempotency key.
- `source_chunk_id` (string, required) — Chunk ID where the relation starts.
- `target_chunk_id` (string, required) — Chunk ID where the relation points. Must differ from `source_chunk_id`.
- `relation_type` (string, required) — Application-defined relation label. Part of the relation's identity together with the source and target chunk.
- `metadata` (map from string to any, required) — Required. On create, the relation's metadata. On update, replaces the existing relation's entire metadata object: omitted keys are removed, and `{}` clears it.
- `target_document_id` (string, optional, nullable) — Optional. When supplied, the document must contain the target chunk. It does not change the relation's identity.

### BatchRelationUpsertResultV3

One entry per request item, in request order. Exactly one of `data` (status `succeeded`) or `error` (status `failed` or `unknown`) is present; a result never carries both or neither.

- `status`: `succeeded` (BatchRelationUpsertSuccessV3)
  - `data` (ChunkRelationResponseV3, required) — The same `relation` object the single-item create endpoint returns.
  - `http_status` (integer, required) — The status the matching single-item endpoint would have returned for this item.
  - `item_id` (string, required) — Echoed from the request item.
- `status`: `failed` (BatchRelationUpsertFailureV3)
  - `error` (BatchItemErrorV3, required)
  - `http_status` (integer, required) — The status the matching single-item endpoint would have returned for this item.
  - `item_id` (string, required) — Echoed from the request item.
- `status`: `unknown` (BatchRelationUpsertFailureV3)
  - `error` (BatchItemErrorV3, required)
  - `http_status` (integer, required) — The status the matching single-item endpoint would have returned for this item.
  - `item_id` (string, required) — Echoed from the request item.

### BatchItemErrorV3

- `code` (string, required) — Stable error code. Item-level codes are the ones the matching single-item endpoint uses for the same failure, plus `outcome_unknown` and `relation_identity_ambiguous`.
- `message` (string, required)

### ChunkRelationResponseV3

- `relation` (ChunkRelationFromV3, required)

### ChunkRelationFromV3

- `relation_id` (string, required)
- `source_chunk_id` (string, required)
- `target_chunk_id` (string, required)
- `relation_type` (string, required)
- `target_document_id` (string, optional, nullable)
- `target_status` (enum, optional, default: unknown)
  - Allowed values: `found`, `missing`, `unknown`
- `metadata` (ChunkRelationFromV3Metadata, optional)
- `created_at` (string, optional, nullable)
- `updated_at` (string, optional, nullable)

### ChunkRelationFromV3Metadata

## Examples

### Create or Update Chunk Relations in Batches

**Request**

```json
{
  "items": [
    {
      "item_id": "link-1",
      "source_chunk_id": "file-a:3",
      "target_chunk_id": "file-a:8",
      "relation_type": "supported_by",
      "metadata": {
        "review_status": "reviewed"
      }
    },
    {
      "item_id": "link-2",
      "source_chunk_id": "file-b:2",
      "target_chunk_id": "file-b:9",
      "relation_type": "supported_by",
      "metadata": {
        "review_status": "pending"
      }
    }
  ]
}
```

**Response**

```json
{
  "results": [
    {
      "status": "succeeded",
      "data": {
        "relation": {
          "relation_id": "rel-1",
          "source_chunk_id": "file-a:3",
          "target_chunk_id": "file-a:8",
          "relation_type": "supported_by",
          "metadata": {
            "review_status": "reviewed"
          }
        }
      },
      "http_status": 201,
      "item_id": "link-1"
    },
    {
      "status": "failed",
      "error": {
        "code": "target_not_found",
        "message": "Source or target chunk not found."
      },
      "http_status": 404,
      "item_id": "link-2"
    }
  ]
}
```

**SDK Code**

```python Create or Update Chunk Relations in Batches
import json
import requests

BASE_URL = "https://api.captain.dev"
API_KEY = "your_api_key"
COLLECTION_NAME = "my_documents"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

response = requests.put(
    f"{BASE_URL}/v3/collections/{COLLECTION_NAME}/relations/batch",
    headers=headers,
    json={
        "items": [
            {
                "item_id": "link-1",
                "source_chunk_id": "file-a:3",
                "target_chunk_id": "file-a:8",
                "relation_type": "supported_by",
                "metadata": {
                    "review_status": "reviewed"
                }
            },
            {
                "item_id": "link-2",
                "source_chunk_id": "file-b:2",
                "target_chunk_id": "file-b:9",
                "relation_type": "supported_by",
                "metadata": {
                    "review_status": "pending"
                }
            }
        ]
    },
    timeout=60.0
)

for result in response.json()["results"]:
    print(result["item_id"], result["status"], result["http_status"])
```

```typescript Create or Update Chunk Relations in Batches
const BASE_URL = "https://api.captain.dev";

const API_KEY = "your_api_key";

const collection_name = "my_documents";

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

const response = await fetch(`${BASE_URL}/v3/collections/${collection_name}/relations/batch`, {
  method: "PUT",
  headers,
  body: JSON.stringify({
    "items": [
      {
        "item_id": "link-1",
        "source_chunk_id": "file-a:3",
        "target_chunk_id": "file-a:8",
        "relation_type": "supported_by",
        "metadata": {
          "review_status": "reviewed"
        }
      },
      {
        "item_id": "link-2",
        "source_chunk_id": "file-b:2",
        "target_chunk_id": "file-b:9",
        "relation_type": "supported_by",
        "metadata": {
          "review_status": "pending"
        }
      }
    ]
  }),
});

const { results } = await response.json();
for (const result of results) {
  console.log(result.item_id, result.status, result.http_status);
}
```

### Update an existing relation

**Request**

```json
{
  "items": [
    {
      "item_id": "link-1",
      "source_chunk_id": "file-a:3",
      "target_chunk_id": "file-a:8",
      "relation_type": "supported_by",
      "metadata": {
        "review_status": "reviewed"
      }
    }
  ]
}
```

**Response**

```json
{
  "results": [
    {
      "status": "succeeded",
      "data": {
        "relation": {
          "relation_id": "rel-1",
          "source_chunk_id": "file-a:3",
          "target_chunk_id": "file-a:8",
          "relation_type": "supported_by",
          "metadata": {
            "review_status": "reviewed"
          }
        }
      },
      "http_status": 200,
      "item_id": "link-1"
    }
  ]
}
```

**SDK Code**

```python Update an existing relation
import json
import requests

BASE_URL = "https://api.captain.dev"
API_KEY = "your_api_key"
COLLECTION_NAME = "my_documents"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

response = requests.put(
    f"{BASE_URL}/v3/collections/{COLLECTION_NAME}/relations/batch",
    headers=headers,
    json={
        "items": [
            {
                "item_id": "link-1",
                "source_chunk_id": "file-a:3",
                "target_chunk_id": "file-a:8",
                "relation_type": "supported_by",
                "metadata": {
                    "review_status": "reviewed"
                }
            }
        ]
    },
    timeout=60.0
)

for result in response.json()["results"]:
    print(result["item_id"], result["status"], result["http_status"])
```

```typescript Update an existing relation
const BASE_URL = "https://api.captain.dev";

const API_KEY = "your_api_key";

const collection_name = "my_documents";

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

const response = await fetch(`${BASE_URL}/v3/collections/${collection_name}/relations/batch`, {
  method: "PUT",
  headers,
  body: JSON.stringify({
    "items": [
      {
        "item_id": "link-1",
        "source_chunk_id": "file-a:3",
        "target_chunk_id": "file-a:8",
        "relation_type": "supported_by",
        "metadata": {
          "review_status": "reviewed"
        }
      }
    ]
  }),
});

const { results } = await response.json();
for (const result of results) {
  console.log(result.item_id, result.status, result.http_status);
}
```