Skip to main content

Knowledge Base & RAG

Store content your bots use to answer questions, plus a vector Knowledge API that chunks, embeds, and semantically retrieves your sources for RAG.

Overview

There are two independent layers of knowledge, and they do not share storage:

  1. Bot knowledge base — a flat document store scoped to a single bot. Add plain text or upload PDF/DOCX/TXT (max 20 MB); text is extracted on upload. This layer is not searched by RAG.
  2. Knowledge API (RAG) — a vector store. Ingest text, URLs, or PDFs as sources; each is chunked and embedded, then retrieved by semantic search and passed as context to the model.

Every source you ingest is attached to a bot via a required bot_id. Retrieval is always tenant-scoped, and additionally bot-scoped whenever you pass a bot_id.

Every endpoint on both layers accepts either a cm_ API key or a dashboard session (JWT). API keys need the knowledge:read / knowledge:write scopes; scopes do not apply to JWT callers, whose access is role-based.

Base paths: https://api.callmissed.com/api/v1/knowledge for RAG, and https://api.callmissed.com/api/v1/bots/{bot_id}/knowledge for the flat store.

Which layer to use

You want to…Use
Ground an LLM reply in your documentsKnowledge API (RAG)
Run semantic search over your contentKnowledge API (RAG)
Keep a plain list of documents attached to a botBot knowledge base
Upload DOCX, or a file up to 20 MBBot knowledge base

New integrations should use the Knowledge API. The flat store predates it and is kept for existing bots — it still counts toward the storage total in your usage rollup.

Bot Knowledge Base

A flat, non-vector list of entries on one bot. The bot must belong to your tenant or the request returns 404.

GET /api/v1/bots/{bot_id}/knowledge · scope knowledge:read

Returns every entry for the bot, newest first.

POST /api/v1/bots/{bot_id}/knowledge · scope knowledge:write

FieldTypeRequiredNotes
namestring (1–255)YesLabel for the entry
contentstring (1–100000)YesThe raw text
metadataobjectNoArbitrary JSON you can read back
curl -X POST https://api.callmissed.com/api/v1/bots/$BOT_ID/knowledge \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Refund policy","content":"Refunds are issued within 7 days of purchase."}'

Response (201 Created) — a knowledge entry:

{
  "id": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
  "bot_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
  "name": "Refund policy",
  "content": "Refunds are issued within 7 days of purchase.",
  "metadata": null,
  "file_url": null,
  "file_size_bytes": null,
  "format": null,
  "status": "indexed",
  "error_message": null,
  "created_at": "2026-07-20T09:15:00Z"
}

Upload a document

POST /api/v1/bots/{bot_id}/knowledge/upload · scope knowledge:write

A multipart upload of a single file. Accepted extensions are PDF, DOCX, and TXT, up to 20 MB. Text is extracted server-side and stored in content; if the extracted text is over 5 MB of UTF-8 the upload returns 413.

curl -X POST https://api.callmissed.com/api/v1/bots/$BOT_ID/knowledge/upload \
  -H "Authorization: Bearer cm_your_key" \
  -F 'file=@handbook.pdf'

The entry comes back with format set to the extension, file_size_bytes set, and status either indexed (text extracted) or failed with an error_message. A scanned PDF with no text layer yields failed — there is no OCR.

Delete an entry

DELETE /api/v1/bots/{bot_id}/knowledge/{entry_id} · scope knowledge:write · returns 204 No Content

Knowledge API (RAG)

How ingestion works

Ingestion is synchronous — the response returns only once the source is fully indexed, so there is no job to poll:

  1. 1

    Extract

    Text is taken as-is, fetched from a URL, or pulled out of a PDF

  2. 2

    Chunk

    Split into ~600-token chunks with a 100-token overlap

  3. 3

    Embed & store

    Each chunk is embedded to a 768-dimension vector and indexed for cosine similarity

Embedding tokens are billed against your credits. The cost is checked before anything is embedded: if your balance (or monthly budget) cannot cover it, nothing is embedded or charged, and the source is saved with status: "failed" and an error_message saying so — the response's status is "failed" too. Top up and re-ingest.

The source object

FieldTypeNotes
iduuidSource identifier
tenant_iduuidOwning tenant
bot_iduuidBot the source is attached to
kindstringtext, pdf, or url
titlestring | nullYour label; defaults to the URL for URL sources
uristring | nullSource URL, or the uploaded filename for PDFs
statusstringpending → ingesting → ready, or failed
error_messagestring | nullSet when status is failed
byte_sizeint | nullSize of the extracted text in bytes
token_countint | nullTotal tokens embedded
chunk_countintNumber of retrievable chunks
created_atdatetime
ingested_atdatetime | nullSet when indexing completes

All three ingest endpoints return the same envelope — the source plus a flat summary:

{
  "source": { "id": "…", "kind": "text", "status": "ready", "chunk_count": 12, "…": "…" },
  "status": "ready",
  "chunk_count": 12,
  "token_count": 7043
}

Ingest text

POST /api/v1/knowledge/sources · scope knowledge:write · returns 201

FieldTypeRequiredNotes
bot_iduuidYesMust be a bot in your tenant
titlestring (1–512)YesLabel for the source
contentstringYesThe text to index; max 5 MB of UTF-8
curl -X POST https://api.callmissed.com/api/v1/knowledge/sources \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
    "title": "Shipping FAQ",
    "content": "We ship across India in 3-5 business days. Express delivery reaches metro cities next day."
  }'

Ingest a URL

POST /api/v1/knowledge/sources/url · scope knowledge:write · returns 201

The server fetches the page itself, strips HTML to text, and indexes the result.

FieldTypeRequiredNotes
bot_iduuidYesMust be a bot in your tenant
urlstringYesA bare domain works — https:// is added when no scheme is present
titlestring (≤512)NoDefaults to the URL
curl -X POST https://api.callmissed.com/api/v1/knowledge/sources/url \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"bot_id":"'$BOT_ID'","url":"acme.in/help/returns","title":"Returns policy"}'

Only public URLs. Requests that resolve to private or internal address ranges are rejected with 400 URL rejected. The fetch caps the download at 2 MB, times out after 30 seconds, and follows at most 5 redirects — each hop is re-validated.

A page that yields no extractable text returns 400, so JavaScript-rendered pages with no server-side HTML will not ingest.

Ingest a PDF

POST /api/v1/knowledge/sources/pdf · scope knowledge:write · returns 201

A multipart upload. Unlike the text and URL endpoints, the fields are form fields, not JSON.

FieldTypeRequiredNotes
bot_iduuid (form)YesMust be a bot in your tenant
titlestring (form)YesLabel for the source
filefileYesPDF only, max 5 MB. The extracted text is also capped at 5 MB of UTF-8 (413 above it)
curl -X POST https://api.callmissed.com/api/v1/knowledge/sources/pdf \
  -H "Authorization: Bearer cm_your_key" \
  -F "bot_id=$BOT_ID" \
  -F 'title=Product catalogue' \
  -F 'file=@catalogue.pdf'

A scanned PDF is an image, not text. With no text layer to extract, the upload returns 400 — there is no OCR in v1.

List sources

GET /api/v1/knowledge/sources · scope knowledge:read

QueryTypeDefaultNotes
bot_iduuid—Filter to one bot; omit to list the whole tenant
limitint (1–200)50Page size
offsetint (0–10000)0Page offset

Returns { "items": [...], "total": 42 }, newest first, where total is the count before paging.

GET /api/v1/knowledge/sources/{source_id} returns a single source, or 404 if it is not in your tenant.

Delete a source

DELETE /api/v1/knowledge/sources/{source_id} · scope knowledge:write · returns 204 No Content

Deleting a source also deletes all of its chunks, which removes it from retrieval immediately.

POST /api/v1/knowledge/search · scope knowledge:read

Runs the retrieval step on its own. Use it to tune k and min_score, or to build your own RAG prompt.

FieldTypeDefaultNotes
querystring (1–4096)—Required. The text to match against
bot_iduuidnullRestrict to one bot; omit to search every source in the tenant
kint (1–50)6How many chunks to return
min_scorefloat (0–1)0.0Drop chunks below this score
curl -X POST https://api.callmissed.com/api/v1/knowledge/search \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
    "query": "how long does delivery take?",
    "k": 4,
    "min_score": 0.6
  }'

Response (200 OK)

{
  "query": "how long does delivery take?",
  "chunks": [
    {
      "id": "e4a1b2c3-d4e5-6f70-8192-a3b4c5d6e7f8",
      "source_id": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
      "chunk_index": 0,
      "content": "We ship across India in 3-5 business days...",
      "token_count": 412,
      "score": 0.8317
    }
  ]
}

Reading the score

score is cosine similarity — 1.0 is an exact semantic match and 0.0 is unrelated. Two details matter when tuning:

  • min_score is applied after ranking, not before. You can get back fewer than k chunks, including zero.
  • A default min_score of 0 returns the nearest chunks whether or not they are relevant. For question-answering, start around 0.6 to drop off-topic matches.

Searching a bot with no ingested chunks returns an empty list without spending credits. Otherwise each search embeds the query, which is billed as a small number of embedding tokens.

Grounding a chat completion

You do not have to call /search and assemble a prompt yourself. Pass bot_id to chat completions and the server retrieves for you, prepending the matched chunks to the system message before the model runs.

FieldTypeDefaultNotes
bot_idstring—Enables retrieval against that bot's sources
knowledge_top_kint (1–50)6Chunks to retrieve
knowledge_min_scorefloat (0–1)0.0Minimum score to include
curl -X POST https://api.callmissed.com/v1/chat/completions \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini",
    "bot_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
    "knowledge_top_k": 4,
    "knowledge_min_score": 0.6,
    "messages": [{"role": "user", "content": "Do you deliver to Pune?"}]
  }'

Behaviour worth knowing:

  • Retrieval runs against the latest user message in messages, not the system prompt or conversation history.
  • Retrieved context is merged into your existing system message, or inserted as one if you did not send any.
  • If retrieval fails, the completion still runs — just without context, rather than returning an error.
  • A bot_id belonging to another tenant is treated as "no knowledge" instead of returning 404.

Cost is one embedding call for the query plus the added context tokens, billed at your model's normal input rate.

Limits

Bot knowledge baseKnowledge API (RAG)
StorageRaw documentsChunks + vectors
FormatsPDF, DOCX, TXT, plain textPlain text, PDF, URL
Max upload20 MB5 MB (2 MB for a URL fetch)
Max text per entry100,000 characters typed; 5 MB of UTF-8 extracted from an upload5 MB of UTF-8
ChunkingNone~600 tokens, 100-token overlap
Semantic searchNoYes
ScopingOne botIngest per bot; search per bot or tenant-wide

Status codes

CodeMeaning
201Source ingested
204Source or entry deleted
400No extractable text, a non-PDF upload, an unsupported format, or a rejected URL
403Key is missing the knowledge:read / knowledge:write scope
404Bot or source not found in your tenant
413Content or upload exceeds the size cap
502Indexing failed after the text was extracted — safe to retry

A 502 means extraction succeeded but embedding did not, so nothing was stored. Retry the same request. See Errors for the standard error shape.