> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conare.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Write documents

> Add or replace documents in a namespace.

`POST /namespaces/:namespace/documents`

Writes 1-100 documents. Conare splits each into passages, then embeds and indexes them. Writing an
existing id replaces the whole document: its text, metadata and `user`. The first write creates the
namespace.

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.conare.ai/namespaces/docs/documents \
    -H "Authorization: Bearer $CONARE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "documents": [
        {
          "id": "refunds",
          "text": "Refunds are available for 30 days.",
          "metadata": { "team": "support" }
        },
        {
          "id": "shipping",
          "text": "Orders ship within two days.",
          "metadata": { "team": "ops" }
        }
      ]
    }'
  ```

  ```ts TypeScript theme={null}
  await docs.write([
    {
      id: "refunds",
      text: "Refunds are available for 30 days.",
      metadata: { team: "support" },
    },
    {
      id: "shipping",
      text: "Orders ship within two days.",
      metadata: { team: "ops" },
    },
  ]);
  ```
</CodeGroup>

```json Response theme={null}
{
  "ids": ["refunds", "shipping"],
  "versions": [1, 1],
  "results": [
    { "id": "refunds", "result": "created", "version": 1, "chunks": 1, "chunks_embedded": 1 },
    { "id": "shipping", "result": "created", "version": 1, "chunks": 1, "chunks_embedded": 1 }
  ]
}
```

## Request

* `documents` (array, required): 1-100 documents, each id at most once.

Each document:

* `id` (string): 1-256 characters of `A-Z a-z 0-9 . _ : -`, starting with a letter or digit.
  Generated if you leave it out.
* `text` (string, required): Up to 256 KiB, not blank.
* `metadata` (object): [Values](#metadata) to [filter](/filters) on.
* `user` (string): Who the document belongs to. See [Users](#users).
* `seq` (integer): Your sequence number for the document, 0 to 2^53. See
  [Sequence numbers](#sequence-numbers).
* `if_version` (integer): Write only if the document is at this [version](#versions).

## Response

* `ids`: The documents' ids, in request order.
* `versions`: Their versions after the write, in the same order.
* `results`: What the write did with each document, in the same order.
  * `id`: The document's id.
  * `result`: `created`, `updated`, `unchanged` or `stale`. See [Results](#results).
  * `version`: Its version after the write.
  * `chunks`: Its passages.
  * `chunks_embedded`: The passages this write embedded.
  * `stored_seq`: With `stale`, the `seq` Conare holds.

## Results

| Result | Meaning |
| - | - |
| `created` | The id held no document. |
| `updated` | The document replaced the one stored. |
| `unchanged` | Conare already held the document as sent. Nothing was embedded, and nothing written but a higher `seq`. |
| `stale` | Conare holds a higher `seq`. Nothing was written. |

* A document is `unchanged` when its text, metadata and `user` are the ones stored. Its `version`
  and `updated_at` stay, and `chunks_embedded` is 0. If it raises `seq`, the new `seq` is stored
  and its version changes.
* An `updated` document embeds only the passages it didn't already hold. The others keep their
  stored vectors.
* When Conare's embedding model changes, a document written under the old one is `updated` and
  embedded again, even if you send it as stored.

## Metadata

Metadata values are strings, numbers, booleans or arrays of strings:

```json theme={null}
{
  "team": "support",
  "year": 2025,
  "public": true,
  "tags": ["billing", "refunds"]
}
```

* Up to 32 keys of 1-64 characters of `A-Z a-z 0-9 _ . -`, starting with a letter. `id`, `text`,
  `vector` and `ttl_at_ms` are reserved.
* A string is up to 4 KiB, and an array holds up to 256 strings.
* A document holds up to 256 distinct values. A number or a short string counts as two, and an array
  item, a boolean or a long string as one. A `user` counts as one, plus one per `:` in it.

## Users

Set `user` to say whose a document is, when one namespace holds many users' documents. A
[query](/query#users) can then keep to one user, or to a group of users: use `:` to group them, such
as `tenant-7:u_42`, and query `tenant-7:` for the whole group.

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.conare.ai/namespaces/docs/documents \
    -H "Authorization: Bearer $CONARE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "documents": [
        {
          "id": "ticket-981",
          "text": "Customer asked for a refund on order 4411.",
          "user": "tenant-7:u_42"
        }
      ]
    }'
  ```

  ```ts TypeScript theme={null}
  await docs.write({
    id: "ticket-981",
    text: "Customer asked for a refund on order 4411.",
    user: "tenant-7:u_42",
  });
  ```
</CodeGroup>

* A user is 1-128 characters of `A-Z a-z 0-9 . _ : @ -`, starting with a letter or digit, with at
  most 8 `:`.
* It's separate from metadata: a `user` metadata key is a different value, and a
  [filter](/filters) can't match the document's `user`.
* A write without `user` removes the stored one.

## Sequence numbers

Set `seq` to keep an older copy of a document from replacing a newer one, such as when two workers
sync the same source. Use a number that grows with each copy, such as the source's last-modified
time in milliseconds:

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.conare.ai/namespaces/docs/documents \
    -H "Authorization: Bearer $CONARE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "documents": [
        {
          "id": "ticket-981",
          "text": "Customer asked for a refund on order 4411.",
          "seq": 1759698843000
        }
      ]
    }'
  ```

  ```ts TypeScript theme={null}
  await docs.write({
    id: "ticket-981",
    text: "Customer asked for a refund on order 4411.",
    seq: 1759698843000,
  });
  ```
</CodeGroup>

```json Response theme={null}
{
  "ids": ["ticket-981"],
  "versions": [7],
  "results": [
    {
      "id": "ticket-981",
      "result": "stale",
      "version": 7,
      "chunks": 1,
      "chunks_embedded": 0,
      "stored_seq": 1759698900000
    }
  ]
}
```

* A write with a lower `seq` than stored is `stale`: nothing is written, and `stored_seq` says what
  Conare holds.
* A write with an equal or higher `seq` is written as usual, and stores its `seq`.
* A write without `seq` is never `stale`, and keeps the stored `seq`.
* `if_version` is checked first.

## Versions

A write that stores something new gives a document a larger `version`. An `unchanged` write keeps
it, unless it raises `seq`. Versions only grow; they don't count writes. Send the version back as
`if_version` to write only if the document hasn't changed since you read it:

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.conare.ai/namespaces/docs/documents \
    -H "Authorization: Bearer $CONARE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "documents": [
        {
          "id": "refunds",
          "text": "Refunds are available for 60 days.",
          "metadata": { "team": "support" },
          "if_version": 1
        }
      ]
    }'
  ```

  ```ts TypeScript theme={null}
  const doc = await docs.get("refunds"); // null if it doesn't exist
  if (doc) {
    await docs.write({
      id: "refunds",
      text: "Refunds are available for 60 days.",
      metadata: doc.metadata,
      ifVersion: doc.version,
    });
  }
  ```
</CodeGroup>

If the document is at another version, the write returns `409 version_conflict`. `if_version: 0`
writes only a document that doesn't exist yet.

## Behavior

* A request isn't atomic. If it fails, some of its documents may already be written. Retry with the
  same ids, which replace rather than duplicate. Without `if_version`, a document already written
  comes back `unchanged` if it still holds the same content and embedding model, and isn't embedded
  again. A retry with `if_version` can answer `409 version_conflict` because its first attempt landed:
  get the document to check. Send your own ids if you may retry: an id left out is generated again on
  each request.
* Only `created` and `updated` documents count as documents written in the console's
  [Usage](/console).
* A document is searchable as soon as its write returns.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.