Skip to content
Discord

Knowledge base

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

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

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

  1. Start a session

    POST /v1/knowledge-base/upload/start
    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"
      }'
    201 Created
    { "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.

  2. Upload each chunk

    POST /v1/knowledge-base/upload/chunk/{sessionId}

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

    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.

  3. Complete

    POST /v1/knowledge-base/upload/complete/{sessionId}
    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"
    200 OK
    {
      "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.

  4. Poll until ready

    Indexing runs in the background. Poll GET /v1/knowledge-base/files/{knowledgeBaseId} until status is ready.

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)
GET /v1/knowledge-base/files
200 OK
{
  "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"
    }
  ]
}
statusMeaning
processingIndexing; read processingPercentage (0–100).
readySearchable.
failedIndexing failed. Re-upload.
GET /v1/knowledge-base/files/{knowledgeBaseId}

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

DELETE /v1/knowledge-base/collection/{collectionName}

Deletes by collectionName, not by file _id.

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"
StatusCause
404No such collection in this workspace
409The collection is in use by an assistant

Detach it from the assistant first.

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