Knowledge Base
Upload documents into your Knowledge Base from your own systems, search it, and organise its files and folders — the same Knowledge Base your agents answer from.
| Operation | Endpoint | Scope |
|---|---|---|
| Upload a file | POST /api/v1/kb/files | kb:write |
| List files | GET /api/v1/kb/files | kb:read |
| Get a file's details | GET /api/v1/kb/files/{id} | kb:read |
| Download a file | GET /api/v1/kb/files/{id}/download | kb:read |
| Move a file | POST /api/v1/kb/files/{id}/move | kb:write |
| Delete a file | DELETE /api/v1/kb/files/{id} | kb:write |
| Search | POST /api/v1/kb/search | kb:read |
| List folders | GET /api/v1/kb/folders | kb:read |
| Create a folder | POST /api/v1/kb/folders | kb:write |
| Rename a folder | PATCH /api/v1/kb/folders/{id} | kb:write |
| Delete an empty folder | DELETE /api/v1/kb/folders/{id} | kb:write |
Uploading a file
Send the file as multipart/form-data, in a field named file (up to 200 MB):
bash
curl -X POST "https://<your-workspace>-api.perfox.ai/api/v1/kb/files" \
-H "Authorization: Bearer sk_…" \
-F "file=@product-manual.pdf" \
-F "name=Product manual" \
-F "folder_id=fld_abc123"| Field | Required | What |
|---|---|---|
file | yes | The document. The same formats the Knowledge Base page accepts — PDF, Office documents, spreadsheets, text, email files, images, audio and video. An unsupported type returns 415 unsupported_mime. |
name | no | Display name. Defaults to the filename. |
folder_id | no | The folder to put it in. Omit for the root. |
The response is 201 with the new file and status: "pending". Indexing runs in the background: poll GET /api/v1/kb/files/{id} until status is active (ready to answer from) or error.
File details
Every file — from an upload, a get or a list — has this shape:
| Field | Meaning |
|---|---|
id | File id |
name | Display name |
status | pending → processing → active, or error |
mime_type, file_size | What was uploaded, and its size in bytes |
folder_id | Its folder, or null at the root |
chunk_count | How many searchable passages it was split into |
created_at / updated_at | Timestamps |
GET /api/v1/kb/files/{id}/download returns the original bytes with their content type, so you can show a preview or hand the file back to a user.
Listing files
Files come back newest first, with limit up to 200 (50 by default).
bash
curl "https://<your-workspace>-api.perfox.ai/api/v1/kb/files?limit=50" \
-H "Authorization: Bearer sk_…"Pass folder_id to list a single folder. Omit it to list the root — the same spelling the folder listing uses, so you do not need a second convention for "top level". Pass status (for example active) to list only files in that state.
When more rows exist the response carries next_cursor; send it back as cursor. The cursor is a position in time rather than an offset, so files uploaded while you are paging cannot make you skip or repeat a row.
Moving and deleting files
POST /api/v1/kb/files/{id}/move with {"folder_id": "…"} moves a file into a folder; send {"folder_id": null} to move it to the root. A folder that does not exist returns 404 folder_not_found.
DELETE /api/v1/kb/files/{id} removes the file completely — the document, and everything agents used to search it. It is the same as deleting it on the Knowledge Base page. If the file was the current version of a document you had uploaded more than once, the previous version becomes current again, so agents keep answering from the latest one that remains.
Searching
bash
curl -X POST "https://<your-workspace>-api.perfox.ai/api/v1/kb/search" \
-H "Authorization: Bearer sk_…" \
-H "Content-Type: application/json" \
-d '{"query": "warranty on refurbished units", "top_k": 5}'| Field | Values |
|---|---|
query | Required. What to look for, in plain language. |
top_k | How many matches to return, 1–20 (default 8). |
folder_id | Search only the files in this folder. |
kb_id | Search only this knowledge base record. |
Omit both folder_id and kb_id to search everything. The response is { "matches": [ { "name", "id", "score" } ] }, best match first. This is a meaning-based search, so a match does not need to share the query's exact words.
Folders
GET /api/v1/kb/folders lists one level of folders, sorted by name: omit parent_id for the top level, or pass a folder's id to list its subfolders. Each folder has id, name, parent_id, path, created_at and updated_at.
POST /api/v1/kb/folders with {"name": "Manuals", "parent_id": "…"} creates a folder (omit parent_id for the top level) and returns 201. An unknown parent returns 404 parent_not_found.
Renaming a folder
bash
curl -X PATCH "https://<your-workspace>-api.perfox.ai/api/v1/kb/folders/{id}" \
-H "Authorization: Bearer sk_…" \
-H "Content-Type: application/json" \
-d '{"name": "Product manuals"}'Renaming changes what the folder is called (1–200 characters), not which files are in it. Agents that select the folder keep working.
Deleting a folder — empty folders only
DELETE /api/v1/kb/folders/{id} removes a folder that holds no files and no subfolders.
If it still holds either, the call fails with 409 folder_not_empty and tells you exactly what is in the way:
json
{
"error": "folder_not_empty",
"message": "Folder still contains 12 file(s) and 3 subfolder(s). Delete its contents first — this endpoint will not cascade.",
"files": 12,
"subfolders": 3
}This is narrower than deleting a folder in the workspace, on purpose
Deleting a folder in the workspace shows you everything inside it and asks you to confirm. An API call has no such moment, so a single request is not allowed to destroy files you never listed. To remove a whole folder tree, delete its contents first.
Agents are reported, not repaired
If any published agent still selects the folder you deleted, the response lists them:
json
{
"success": true,
"affected_agents": [
{ "id": "…", "name": "Support Assistant", "status": "published" }
]
}Their Knowledge Base selection is left exactly as you configured it — the platform does not rewrite a published agent behind your back. But until you point them somewhere else, those agents retrieve nothing from that selection. This response is the one moment the connection between the deletion and the silence is visible, which is why it is reported here rather than quietly cleaned up.