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

# Knowledge base

> Upload documents for retrieval-augmented answers — chunked upload, indexing status, collections.

<Note>
  **This documents the v1 product.** It is kept for integrations already running
  on it. If you are building something new, start with the
  [Quickstart](/v2/quickstart).
</Note>

Upload documents to a workspace and the assistant can search them mid-call to
answer policy and product questions.

<Note>
  The knowledge base is a v1 capability. [v2](/v2/overview) has no RAG yet.
</Note>

**Limits:** 100 MB per file, 10 MB per chunk, 1,000 chunks maximum. Supported
types: `application/pdf`, `text/plain`, `text/markdown`, and `.docx`
(`application/vnd.openxmlformats-officedocument.wordprocessingml.document`).

## Upload a document

Three steps: start a session, push the chunks, complete.

<Steps>
  <Step title="Start a session">
    ```http theme={null}
    POST /v1/knowledge-base/upload/start
    ```

    ```bash theme={null}
    curl -X POST https://api.voice-agents.miraiminds.co/v1/knowledge-base/upload/start \
      -H "x-public-key: pk_1234567890abcdef1234567890abcdef" \
      -H "x-private-key: sk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
      -H "workspace: 6690a1b2c3d4e5f600000002" \
      -H "Content-Type: application/json" \
      -d '{
        "fileName": "product-catalog.pdf",
        "totalChunks": 1,
        "fileSize": 524288,
        "mimeType": "application/pdf"
      }'
    ```

    ```json title="201 Created" theme={null}
    { "status_code": 201, "message": "Upload session created.", "data": { "sessionId": "sess_abc123xyz" } }
    ```

    `totalChunks` must match what you actually send. Target 10 MB per chunk.

    `400` if the file exceeds 100 MB or the MIME type is unsupported.
  </Step>

  <Step title="Upload each chunk">
    ```http theme={null}
    POST /v1/knowledge-base/upload/chunk/{sessionId}
    ```

    `multipart/form-data` with `chunk` (binary) and `chunkIndex` (zero-based).

    ```bash theme={null}
    curl -X POST https://api.voice-agents.miraiminds.co/v1/knowledge-base/upload/chunk/sess_abc123xyz \
      -H "x-public-key: pk_1234567890abcdef1234567890abcdef" \
      -H "x-private-key: sk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
      -H "workspace: 6690a1b2c3d4e5f600000002" \
      -F "chunk=@part-0.bin" \
      -F "chunkIndex=0"
    ```

    `400` for a missing chunk, a negative index, or a chunk over 10 MB.
    `404` if the session does not exist.
  </Step>

  <Step title="Complete">
    ```http theme={null}
    POST /v1/knowledge-base/upload/complete/{sessionId}
    ```

    ```bash theme={null}
    curl -X POST https://api.voice-agents.miraiminds.co/v1/knowledge-base/upload/complete/sess_abc123xyz \
      -H "x-public-key: pk_1234567890abcdef1234567890abcdef" \
      -H "x-private-key: sk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
      -H "workspace: 6690a1b2c3d4e5f600000002"
    ```

    ```json title="200 OK" theme={null}
    {
      "message": "Upload completed. Processing has started in the background.",
      "sessionId": "sess_abc123xyz",
      "knowledgeBaseId": "6701a1b2c3d4e5f600000050",
      "knowledgeBaseStatus": "processing"
    }
    ```

    `400` if the session is not in the `uploading` state or is already linked to
    a knowledge base.
  </Step>

  <Step title="Poll until ready">
    Indexing runs in the background. Poll
    `GET /v1/knowledge-base/files/{knowledgeBaseId}` until `status` is `ready`.
  </Step>
</Steps>

### Full upload, in code

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import os, time, math, httpx

    API = "https://api.voice-agents.miraiminds.co"
    H = {
        "x-public-key": os.environ["MIRAI_PUBLIC_KEY"],
        "x-private-key": os.environ["MIRAI_PRIVATE_KEY"],
        "workspace": os.environ["MIRAI_WORKSPACE"],
    }
    CHUNK = 10 * 1024 * 1024


    def upload(path: str, mime: str) -> str:
        size = os.path.getsize(path)
        total = math.ceil(size / CHUNK)

        start = httpx.post(
            f"{API}/v1/knowledge-base/upload/start",
            headers=H,
            json={
                "fileName": os.path.basename(path),
                "totalChunks": total,
                "fileSize": size,
                "mimeType": mime,
            },
            timeout=30,
        ).raise_for_status().json()
        session = start["data"]["sessionId"]

        with open(path, "rb") as fh:
            for i in range(total):
                httpx.post(
                    f"{API}/v1/knowledge-base/upload/chunk/{session}",
                    headers=H,
                    files={"chunk": fh.read(CHUNK)},
                    data={"chunkIndex": i},
                    timeout=300,
                ).raise_for_status()

        done = httpx.post(
            f"{API}/v1/knowledge-base/upload/complete/{session}", headers=H, timeout=60
        ).raise_for_status().json()
        kb_id = done["knowledgeBaseId"]

        while True:  # indexing is async
            f = httpx.get(
                f"{API}/v1/knowledge-base/files/{kb_id}", headers=H, timeout=30
            ).raise_for_status().json()["data"]
            if f["status"] in ("ready", "failed"):
                if f["status"] == "failed":
                    raise RuntimeError(f"indexing failed for {kb_id}")
                return kb_id
            time.sleep(5)
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import fs from "node:fs";
    import path from "node:path";

    const API = "https://api.voice-agents.miraiminds.co";
    const H = {
      "x-public-key": process.env.MIRAI_PUBLIC_KEY,
      "x-private-key": process.env.MIRAI_PRIVATE_KEY,
      workspace: process.env.MIRAI_WORKSPACE,
    };
    const CHUNK = 10 * 1024 * 1024;
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

    export async function upload(filePath, mimeType) {
      const buf = fs.readFileSync(filePath);
      const totalChunks = Math.ceil(buf.length / CHUNK);

      const start = await fetch(`${API}/v1/knowledge-base/upload/start`, {
        method: "POST",
        headers: { ...H, "Content-Type": "application/json" },
        body: JSON.stringify({
          fileName: path.basename(filePath),
          totalChunks,
          fileSize: buf.length,
          mimeType,
        }),
      }).then((r) => r.json());
      const sessionId = start.data.sessionId;

      for (let i = 0; i < totalChunks; i++) {
        const form = new FormData();
        form.append("chunk", new Blob([buf.subarray(i * CHUNK, (i + 1) * CHUNK)]));
        form.append("chunkIndex", String(i));
        await fetch(`${API}/v1/knowledge-base/upload/chunk/${sessionId}`, {
          method: "POST",
          headers: H,
          body: form,
        });
      }

      const done = await fetch(
        `${API}/v1/knowledge-base/upload/complete/${sessionId}`,
        { method: "POST", headers: H }
      ).then((r) => r.json());

      for (;;) {
        const { data } = await fetch(
          `${API}/v1/knowledge-base/files/${done.knowledgeBaseId}`,
          { headers: H }
        ).then((r) => r.json());
        if (data.status === "ready") return done.knowledgeBaseId;
        if (data.status === "failed") throw new Error("indexing failed");
        await sleep(5000);
      }
    }
    ```
  </Tab>
</Tabs>

## List files

```http theme={null}
GET /v1/knowledge-base/files
```

```json title="200 OK" theme={null}
{
  "status_code": 200,
  "message": "Knowledge base files retrieved successfully.",
  "data": [
    {
      "_id": "6701a1b2c3d4e5f600000050",
      "fileName": "product-catalog.pdf",
      "collectionName": "rag_acme_product_catalog_v1",
      "type": "application/pdf",
      "size": 524288,
      "status": "ready",
      "processingPercentage": 100,
      "workspace": "6690a1b2c3d4e5f600000002",
      "createdAt": "2026-06-30T10:00:00.000Z"
    }
  ]
}
```

| `status`     | Meaning                                        |
| :----------- | :--------------------------------------------- |
| `processing` | Indexing; read `processingPercentage` (0–100). |
| `ready`      | Searchable.                                    |
| `failed`     | Indexing failed. Re-upload.                    |

## Get one file

```http theme={null}
GET /v1/knowledge-base/files/{knowledgeBaseId}
```

Same object under `data`. This is the endpoint you poll after `complete`.

## Delete a collection

```http theme={null}
DELETE /v1/knowledge-base/collection/{collectionName}
```

Deletes by `collectionName`, not by file `_id`.

```bash theme={null}
curl -X DELETE https://api.voice-agents.miraiminds.co/v1/knowledge-base/collection/rag_acme_product_catalog_v1 \
  -H "x-public-key: pk_1234567890abcdef1234567890abcdef" \
  -H "x-private-key: sk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
  -H "workspace: 6690a1b2c3d4e5f600000002"
```

| Status | Cause                                    |
| :----- | :--------------------------------------- |
| `404`  | No such collection in this workspace     |
| `409`  | The collection is in use by an assistant |

Detach it from the assistant first.

## Writing documents that retrieve well

* **Short, self-contained sections.** Retrieval returns fragments, not
  documents. A fragment that only makes sense with the page around it is a
  fragment the assistant will read out wrong.
* **Put the question in the text.** A heading of "Return policy" retrieves worse
  than a line reading "How do I return an item?".
* **One fact per paragraph.** Tables and multi-column PDFs chunk badly — flatten
  them to prose before uploading.
* **Say numbers explicitly.** "Returns accepted within 7 days of delivery" is
  retrievable; "within the standard window" is not.
