ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Agents

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/:

MethodPathDescription
GET/v1/knowledge/documentsList documents
POST/v1/knowledge/documentsUpload 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}/retryRe-ingest a failed document
GET/v1/knowledge/documents/{document_id}/originalDownload the original uploaded file
POST/v1/knowledge/documents/{document_id}/replaceReplace the file behind a document
POST/v1/knowledge/documents/import-urlImport a web page by URL
POST/v1/knowledge/documents/{document_id}/refreshRe-fetch a URL-imported page
POST/v1/knowledge/searchSemantic search across documents

Legacy base-scoped routes remain available as adapters (a base is now just a named grouping over the flat store):

MethodPathDescription
GET / POST/v1/knowledge-basesList / 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}/documentsList / 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}/retryRe-ingest
POST/v1/knowledge-bases/{knowledge_base_id}/searchSearch 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"
}
FieldTypeDescription
idUUIDDocument id
namestringDisplay name
source_typestringtext (pasted/edited content) or file (uploaded)
original_filenamestringEmpty for text documents
mime_typestringCanonical MIME type (text/plain, text/markdown, text/csv, application/pdf, DOCX)
byte_countintegerSize of the original upload (or the UTF-8 text)
estimated_chunk_countintegerPersisted indexed count when ready; otherwise an estimate from current extracted content (falling back to the last persisted count until binary extraction is available)
statusstringpending, processing, ready, or failed
errorstringIngestion error summary when failed
chunk_countintegerChunks actually indexed
source_originstringupload, url, or null
source_urlstringThe imported page address for url-origin documents, else null
fetched_attimestampLast successful fetch of source_url (null while a re-fetch is queued)
created_at, updated_attimestamp

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

KindExtensionsMax size
Text.txt, .md, .csv1 MB
Binary.pdf, .docx50 MB

Anything else is rejected with a 400 field error naming the file.


List documents

cURL
curl 'https://api.thunderphone.com/v1/knowledge/documents?status=ready' \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Query paramDescription
qSubstring match on name or content
statuspending, 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 (files)
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 (text)
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.

Response (201 Created)
{
  "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
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.

Response
{ "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
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" }'
FieldTypeRequiredDescription
urlstringyeshttp:// or https:// page address (bare domains default to https://)
namestringnoDisplay name; defaults to the URL, then the page title
Response (201)
{
  "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.


Semantic (embedding) search over ready documents. This is the same retrieval the agent's search_knowledge_base tool uses at call time.

cURL
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?"}'
FieldTypeRequiredDescription
querystringyes≤ 4000 chars
document_idsarray of UUIDnoRestrict the search scope; omit to search everything
Response
{
  "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.