Knowledge bases
Knowledge bases API
Manage knowledge bases, the documents an agent answers from, and the agent attachments that turn on RAG. See Knowledge bases for the concepts and API overview for shared conventions.
All routes are organization‑scoped under /api/orgs/{orgId}/…. Authenticate with an API key (x-api-key: vai_live_…) or a user JWT (Authorization: Bearer … + X-Org-Id). Responses use the standard dashboard envelope:
{ "success": true, "data": { /* result */ } }
Roles
Endpoints enforce your workspace role:
| Operation | Required role |
|---|---|
| List bases / list documents | Any member |
| Create base, add/upload/ingest document, delete document, attach a base to an agent | owner, admin, or developer |
| Delete a base | owner or admin only |
| Detach a base from an agent | any member (no role check) |
A role that's too low returns 403 with { "success": false, "error": "this action requires one of: …" }.
Knowledge bases
| Method | Path | Purpose |
|---|---|---|
GET | /api/orgs/{orgId}/knowledge-bases | List knowledge bases |
POST | /api/orgs/{orgId}/knowledge-bases | Create a knowledge base |
DELETE | /api/orgs/{orgId}/knowledge-bases/{kbId} | Soft‑delete a knowledge base |
Create a knowledge base
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/knowledge-bases \
-H "x-api-key: vai_live_…" \
-H "Content-Type: application/json" \
-d '{ "name": "Support FAQ", "description": "Refunds, shipping, returns" }'
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Trimmed; must be non‑empty |
description | string | no | Free text |
Returns 201 Created with the new base under data:
{
"success": true,
"data": {
"id": "5b1f…",
"org_id": "a02c…",
"name": "Support FAQ",
"description": "Refunds, shipping, returns",
"embedding_model": "text-embedding-3-small",
"created_by": "9d4e…",
"created_at": "2026-06-13T10:00:00Z",
"updated_at": "2026-06-13T10:00:00Z",
"deleted_at": null
}
}
List knowledge bases
GET /api/orgs/{orgId}/knowledge-bases returns the org's non‑deleted bases, newest first:
{
"success": true,
"data": {
"knowledgeBases": [ { /* base */ } ],
"total": 1
}
}
Delete a knowledge base
DELETE /api/orgs/{orgId}/knowledge-bases/{kbId} soft‑deletes the base (sets deleted_at). It and all of its documents stop appearing and stop being searched. There is no recovery — re‑create the base if you delete it by mistake. Returns:
{ "success": true, "message": "knowledge base deleted" }
Deleting an already‑deleted or unknown base returns 404.
Documents
| Method | Path | Purpose |
|---|---|---|
GET | /api/orgs/{orgId}/knowledge-bases/{kbId}/documents | List documents |
POST | /api/orgs/{orgId}/knowledge-bases/{kbId}/documents | Add a document from pasted text |
POST | /api/orgs/{orgId}/knowledge-bases/{kbId}/documents/upload | Upload a file (multipart) |
POST | /api/orgs/{orgId}/knowledge-bases/{kbId}/documents/url | Ingest a public https:// page |
DELETE | /api/orgs/{orgId}/knowledge-bases/{kbId}/documents/{docId} | Soft‑delete a document |
Every add path returns 201 with the created document row. Embedding then runs in the background: the document is created with status: "pending" and flips to embedded (or failed) once chunking and embedding complete. The create call returns before that finishes — poll GET …/documents to watch the status.
Document object
| Field | Type | Notes |
|---|---|---|
id | uuid | |
kb_id | uuid | Owning knowledge base |
org_id | uuid | |
title | string | |
source_type | string | inline (pasted text), upload (file), or url |
source_uri | string | null | Original file name for uploads, fetched URL for URL ingests, null for pasted text |
body | string | Extracted plain text that was chunked and embedded |
status | string | pending, embedded, or failed |
error | string | null | Failure reason when status is failed |
created_at | timestamp | |
updated_at | timestamp | |
deleted_at | timestamp | null | Set on soft delete |
Add a document from text
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/knowledge-bases/{kbId}/documents \
-H "x-api-key: vai_live_…" \
-H "Content-Type: application/json" \
-d '{ "title": "Refund policy", "body": "We refund within 30 days…" }'
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | yes | Trimmed; non‑empty |
body | string | yes | Trimmed; non‑empty. Stored as source_type: "inline" |
Upload a file
multipart/form-data with a file part and an optional title part:
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/knowledge-bases/{kbId}/documents/upload \
-H "x-api-key: vai_live_…" \
-F "[email protected]" \
-F "title=Refund policy"
| Part | Required | Notes |
|---|---|---|
file | yes | The document. See limits below |
title | no | Defaults to the file's name without its extension |
| Limit | Value |
|---|---|
| Accepted extensions | .pdf, .docx, .txt, .md, .csv (content type is also sniffed as a fallback) |
| Maximum size | 20 MB |
Stored source_type | upload; source_uri is the original file name |
Text is extracted server‑side (PDFs via a text extractor, .docx by reading the document XML, text/markdown/CSV decoded as UTF‑8). Errors return 400:
unsupported file type — supported: .pdf, .docx, .txt, .md, .csvcould not extract text from the file(e.g. an image‑only/scanned PDF — OCR it first)empty file/missing 'file' field
A file over the limit returns 413 Payload Too Large.
Ingest a URL
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/knowledge-bases/{kbId}/documents/url \
-H "x-api-key: vai_live_…" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/docs/refund-policy", "title": "Refund policy" }'
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Must be a public https:// URL |
title | string | no | Defaults to the page <title>, then the last URL path segment |
The page is fetched once, stripped to readable text (scripts/styles/markup removed, entities decoded), and stored with source_type: "url" and source_uri set to the URL.
| Behavior | Detail |
|---|---|
| Scheme | https:// only; private/internal addresses are blocked (SSRF protection) |
| Redirects | Not followed — a 30x response is treated as a fetch failure |
| Connection pinning | The host is resolved and the connection pinned to the vetted address, defeating DNS‑rebinding |
| Content types | text/html, text/plain, or text/markdown only |
| Maximum size | 5 MB of fetched body |
| Timeouts | 5 s connect, 10 s total |
Errors return 400 — for example could not fetch URL: …, URL returned 404, an unsupported content-type (e.g. a PDF link — upload it instead), or could not extract text from the page. A body over 5 MB returns 413.
Delete a document
DELETE /api/orgs/{orgId}/knowledge-bases/{kbId}/documents/{docId} soft‑deletes the document (no recovery) and removes its chunks from retrieval:
{ "success": true, "message": "document deleted" }
An unknown document returns 404. To "update" a document, delete it and add the new version — there is no edit‑in‑place endpoint.
Attaching to an agent
A knowledge base only affects agents it's attached to. Attachment is a join — repeated attaches are idempotent.
| Method | Path | Purpose |
|---|---|---|
GET | /api/orgs/{orgId}/agents/{agentId}/knowledge-bases | List the agent's bases + their document scopes |
PUT | /api/orgs/{orgId}/agents/{agentId}/knowledge-bases | Replace the whole set in one transaction |
POST | /api/orgs/{orgId}/agents/{agentId}/knowledge-bases/{kbId} | Attach a base to an agent |
DELETE | /api/orgs/{orgId}/agents/{agentId}/knowledge-bases/{kbId} | Detach |
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/agents/{agentId}/knowledge-bases/{kbId} \
-H "x-api-key: vai_live_…"
{ "success": true, "message": "knowledge base attached" }
Both the agent and the base must belong to orgId, or you get 404. Attach the same base to multiple agents, or several bases to one agent — at call time the agent searches across all attached bases together.
Scoping an agent to specific documents
By default an attachment covers the whole base, including documents added to it later. You can instead pin the agent to specific documents — useful when one base serves several agents and each should only speak from part of it.
Send documentIds on the attach:
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/agents/{agentId}/knowledge-bases/{kbId} \
-H "x-api-key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "documentIds": ["8f1c…", "b32a…"] }'
The scope is a three-state value, and GET returns it as document_ids on each row:
document_ids | Meaning |
|---|---|
null | The whole base, including documents added later. |
["…", "…"] | Exactly those documents, and nothing else. |
[] | Scoped to nothing — the base stays attached but the agent never reads from it. |
Rules worth knowing before you automate against this:
- Omitting
documentIdsonPOSTleaves an existing scope alone. It does not reset the base to whole. A bare re-attach — which is what the SDK'sattach()and every app install sends — can therefore never widen a scope somebody chose. documentIds: []is rejected with400onPOST. To make a base contribute nothing, detach it. (The bulkPUTbelow does accept[], because it has to be able to re-send a state the server itself produced.)- Deleting a document removes it from every scope that pinned it. If it was the last one, the scope becomes
[]rather than reverting to the whole base — retrieval narrows, it never silently widens. - A document id from another base is rejected with
400 document_not_in_base, naming the offending ids.
Replacing the whole set at once
PUT /api/orgs/{orgId}/agents/{agentId}/knowledge-bases states the agent's complete attachment set — bases and scopes — in a single transaction. Bases you leave out are detached.
curl -X PUT https://api.telenow.ai/api/orgs/{orgId}/agents/{agentId}/knowledge-bases \
-H "x-api-key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "knowledgeBases": [
{ "kbId": "3f9e…" },
{ "kbId": "a71b…", "documentIds": ["8f1c…"] }
] }'
{ "success": true, "message": "knowledge bases updated" }
Every base and document id is validated before anything is written, so a bad id in the middle of the list can't leave a half-applied set. This is what the dashboard builders use: a partially-applied save is survivable for a plain attach list, but not when the point of the save is to exclude something.
How retrieval runs at call time
Once a base is attached, on every user turn:
- The caller's message is embedded with the org's embeddings model (
text-embedding-3-small, 1536‑dim). - The top 3 chunks across all attached bases are selected by cosine distance — restricted to the pinned documents on any base that has a scope.
- Any matches are injected into the model's context as a hidden
systemmessage immediately before the user turn, so the reply is grounded.
The per‑turn query embedding is metered into the session's usage whether or not it matches anything (see Billing & usage). Ingestion is metered once per document when it becomes searchable.
RAG degrades gracefully: if the vector store is unavailable in a deployment, the call still proceeds without grounding rather than failing. On Telenow Cloud the vector store is always available.
Notes
- Embeddings provider. Documents and queries are embedded with OpenAI
text-embedding-3-small(1536 dimensions). The model id is recorded on each base asembedding_modeland surfaces on usage records. - Chunking. Documents are split on blank lines and merged into ~500‑token windows before embedding — no parameter to tune this over the API.
- Background failures don't fail the request. If embedding fails (e.g. a missing provider key), the create call has already returned
201; the document'sstatusbecomesfailedwith anerror. Delete and re‑add to retry.
See Knowledge bases for the dashboard walkthrough, Building agents for attaching bases in context, and API agents for managing the agents you attach them to.