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).
Upload a document
Section titled “Upload a document”Three steps: start a session, push the chunks, complete.
-
Start a session
POST /v1/knowledge-base/upload/startcurl -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" } }totalChunksmust match what you actually send. Target 10 MB per chunk.400if the file exceeds 100 MB or the MIME type is unsupported. -
Upload each chunk
POST /v1/knowledge-base/upload/chunk/{sessionId}multipart/form-datawithchunk(binary) andchunkIndex(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"400for a missing chunk, a negative index, or a chunk over 10 MB.404if the session does not exist. -
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" }400if the session is not in theuploadingstate or is already linked to a knowledge base. -
Poll until ready
Indexing runs in the background. Poll
GET /v1/knowledge-base/files/{knowledgeBaseId}untilstatusisready.
Full upload, in code
Section titled “Full upload, in code”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)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);
}
}List files
Section titled “List files”GET /v1/knowledge-base/files{
"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
Section titled “Get one file”GET /v1/knowledge-base/files/{knowledgeBaseId}Same object under data. This is the endpoint you poll after complete.
Delete a collection
Section titled “Delete a collection”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"| 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
Section titled “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.