Knowledge
Upload documents, search them semantically, and ground your agents in your own content.
The knowledge store holds documents (uploaded files, pasted text, or
imported web pages) that agents can search mid-call via the
built-in search_knowledge_base tool. Attach documents (or grouping
knowledge bases) to an agent with the knowledge_base_ids /
knowledge_document_ids fields on Agents.
Documents are ingested asynchronously: uploads start in
status="pending", move through processing (text extraction for
PDF/DOCX, chunking, embedding), and land on ready or failed.
Endpoints
The primary store is flat ("upload-first") under /v1/knowledge/:
| Method | Path | Description |
|---|---|---|
GET | /v1/knowledge/documents | List documents |
POST | /v1/knowledge/documents | Upload file(s) or create a text document |
GET | /v1/knowledge/documents/{document_id} | Retrieve a document (includes extracted content) |
PATCH | /v1/knowledge/documents/{document_id} | Rename / edit text content |
DELETE | /v1/knowledge/documents/{document_id} | Delete a document |
POST | /v1/knowledge/documents/{document_id}/retry | Re-ingest a failed document |
GET | /v1/knowledge/documents/{document_id}/original | Download the original uploaded file |
POST | /v1/knowledge/documents/{document_id}/replace | Replace the file behind a document |
POST | /v1/knowledge/documents/import-url | Import a web page by URL |
POST | /v1/knowledge/documents/{document_id}/refresh | Re-fetch a URL-imported page |
POST | /v1/knowledge/search | Semantic search across documents |
Legacy base-scoped routes remain available as adapters (a base is now just a named grouping over the flat store):
| Method | Path | Description |
|---|---|---|
GET / POST | /v1/knowledge-bases | List / create knowledge bases |
GET / PATCH / DELETE | /v1/knowledge-bases/{knowledge_base_id} | Manage one base (delete detaches its documents, it does not delete them) |
GET / POST | /v1/knowledge-bases/{knowledge_base_id}/documents | List / create documents inside a base |
PATCH / DELETE | /v1/knowledge-bases/{knowledge_base_id}/documents/{document_id} | Manage a document inside a base |
POST | /v1/knowledge-bases/{knowledge_base_id}/documents/{document_id}/retry | Re-ingest |
POST | /v1/knowledge-bases/{knowledge_base_id}/search | Search within one base |
Knowledge document object
{
"id": "8b2f4a1e-…",
"name": "Refund policy.pdf",
"source_type": "file",
"original_filename": "Refund policy.pdf",
"mime_type": "application/pdf",
"byte_count": 482113,
"estimated_chunk_count": 12,
"status": "ready",
"error": "",
"chunk_count": 12,
"created_at": "2026-04-20T18:24:10.113Z",
"updated_at": "2026-04-20T18:24:31.040Z"
}| Field | Type | Description |
|---|---|---|
id | UUID | Document id |
name | string | Display name |
source_type | string | text (pasted/edited content) or file (uploaded) |
original_filename | string | Empty for text documents |
mime_type | string | Canonical MIME type (text/plain, text/markdown, text/csv, application/pdf, DOCX) |
byte_count | integer | Size of the original upload (or the UTF-8 text) |
estimated_chunk_count | integer | Persisted indexed count when ready; otherwise an estimate from current extracted content (falling back to the last persisted count until binary extraction is available) |
status | string | pending, processing, ready, or failed |
error | string | Ingestion error summary when failed |
chunk_count | integer | Chunks actually indexed |
source_origin | string | upload, url, or null |
source_url | string | The imported page address for url-origin documents, else null |
fetched_at | timestamp | Last successful fetch of source_url (null while a re-fetch is queued) |
created_at, updated_at | timestamp |
The detail endpoint (GET /v1/knowledge/documents/{id}) adds:
content (the canonical extracted text), uploaded_by
({id, name, email} or null), replaced_at, has_original,
original_size_bytes, and original_mime_type.
Upload limits
| Kind | Extensions | Max size |
|---|---|---|
| Text | .txt, .md, .csv | 1 MB |
| Binary | .pdf, .docx | 50 MB |
Anything else is rejected with a 400 field error naming the file.
List documents
curl 'https://api.thunderphone.com/v1/knowledge/documents?status=ready' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"| Query param | Description |
|---|---|
q | Substring match on name or content |
status | pending, processing, ready, failed |
Returns a plain array of document objects, newest first.
Create documents
Two content types are accepted:
Multipart upload — one or more files in files[] (or a single
file part, optionally with a name field):
curl -X POST https://api.thunderphone.com/v1/knowledge/documents \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-F 'files[]=@refund-policy.pdf' \
-F 'files[]=@faq.md'curl -X POST https://api.thunderphone.com/v1/knowledge/documents \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Escalation playbook", "content": "When a caller asks for a manager…"}'JSON — {name, content} creates a source_type="text" document.
{
"documents": [ /* document objects, status "pending" */ ],
"warnings": [
{
"document_id": "8b2f4a1e-…",
"duplicate_document_ids": ["77aa…"],
"message": "This file has the same content as an existing document."
}
]
}Every file is validated before anything is created — one bad file
fails the whole request with 400. Duplicate-content uploads are
allowed but reported in warnings.
Retrieve, edit, delete
GET /v1/knowledge/documents/{id} returns the
detail shape including extracted
content.
PATCH accepts JSON {name?, content?} (or a multipart file to
swap content). Changing name or content re-queues ingestion
(status returns to pending). Returns the updated document.
DELETE returns 204 No Content and removes the document and its
chunks from the index.
Download the original file
For file-sourced documents, fetch the exact bytes that were uploaded.
curl 'https://api.thunderphone.com/v1/knowledge/documents/8b2f4a1e-…/original?download=1' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Returns either the file stream directly or a short-lived signed URL:
{ "url": "https://storage.googleapis.com/…", "expires_in": 900 }download=1 sets an attachment Content-Disposition. Returns 404
when no original is stored (text documents).
Replace the file
POST /v1/knowledge/documents/{id}/replace with a multipart file
part swaps the document's content for the new file (same validation
and limits as upload), stores the new original, stamps replaced_at,
and re-queues ingestion.
{ "document": { /* document object, status "pending" */ }, "warnings": [] }If the uploaded bytes are identical to the current file, the request still succeeds and a warning says so.
Retry ingestion
POST /v1/knowledge/documents/{id}/retry re-queues a failed
document. Returns the document with status="pending", or 400 with
{"detail": "Only failed documents can be retried."} if it is not in
failed.
Import a web page by URL
Creates a document from a public web page. The page is fetched
server-side and asynchronously: the document is returned
immediately in status="pending", then the ingestion worker fetches
the URL (through the same SSRF-guarded transport as webhooks — private
networks, localhost, and cloud metadata addresses are refused, with at
most 5 redirects, each hop re-validated), extracts the main content
(HTML boilerplate such as navigation and footers is stripped;
application/pdf responses go through PDF text extraction), and
indexes it. Pages are capped at 5 MB.
If no name is given, the document is named after the URL and adopts
the page title once fetched.
curl -X POST https://api.thunderphone.com/v1/knowledge/documents/import-url \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/pricing" }'| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | http:// or https:// page address (bare domains default to https://) |
name | string | no | Display name; defaults to the URL, then the page title |
{
"document": { /* document object, status "pending", source_origin "url" */ },
"warnings": []
}warnings lists earlier documents that were imported from the same
URL. Pages that can't be fetched (HTTP errors, blocked destinations)
or contain no extractable text (for example JavaScript-only apps) land
in status="failed" with a readable error.
Refresh a URL-imported document
POST /v1/knowledge/documents/{document_id}/refresh re-fetches
source_url and re-indexes the document (admin role required; only
url-origin documents can be refreshed). The stored ETag /
Last-Modified validators are sent, so an unchanged page is a cheap
no-op that keeps the existing index.
Search
Semantic (embedding) search over ready documents. This is the same
retrieval the agent's search_knowledge_base tool uses at call time.
curl -X POST https://api.thunderphone.com/v1/knowledge/search \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "what is the refund window?"}'| Field | Type | Required | Description |
|---|---|---|---|
query | string | yes | ≤ 4000 chars |
document_ids | array of UUID | no | Restrict the search scope; omit to search everything |
{
"results": [
{
"text": "Refunds are available within 30 days of purchase…",
"document": "Refund policy.pdf",
"score": 0.874312
}
]
}Results are ranked by cosine similarity (score closer to 1 is
better), capped per document so one file can't fill every slot, and
each text is length-capped.
POST /v1/knowledge-bases/{id}/search takes the same body and scopes
the search to that base's documents.
Knowledge bases (grouping)
A knowledge base is a named group:
{id (UUID), name, description, document_count, created_at, updated_at}.
Create with POST /v1/knowledge-bases {name, description?} (201);
update with PATCH; DELETE (204) detaches the base's documents
back into the flat store without deleting them. Attach whole bases to
an agent via knowledge_base_ids.