---
title: "Knowledge base API: import and index documents"
description: "Halo HTTP API for knowledge bases: create and delete collections, bind them to workspaces and Digital Humans, import documents and follow indexing."
type: "reference"
audience: ["developer"]
lang: "en"
canonical: "https://hello-halo.cc/docs/en/developers/api/knowledge-base.html"
last_updated: "2026-10-19T00:11:17+08:00"
source: "Halo 3.0.4 docs"
---
# API: Knowledge bases

Halo HTTP API for knowledge bases: create and delete collections, bind them to workspaces and Digital Humans, import documents and follow indexing.

A knowledge base is a collection of documents Halo can search (`tlon` in the API paths). Use these operations to create and delete knowledge bases, bind them to workspaces and Digital Humans, import and remove documents, and follow indexing.

Paths are relative to your Halo base URL, and every request carries the access password. Set `$HALO_URL` and `$HALO_TOKEN` as in the [quickstart](https://hello-halo.cc/docs/en/developers/quickstart.html); base URL, authentication and error conventions are in the [HTTP API overview](https://hello-halo.cc/docs/en/developers/api/).

| Operation | Endpoint |
| --- | --- |
| [List all knowledge collections](#get-api-tlon) | `GET /api/tlon` |
| [Create a knowledge collection](#post-api-tlon) | `POST /api/tlon` |
| [Get one knowledge collection by id](#get-api-tlon-kbid) | `GET /api/tlon/:kbId` |
| [Rename or edit a collection's description/status](#put-api-tlon-kbid) | `PUT /api/tlon/:kbId` |
| [Delete a knowledge collection and all its data](#delete-api-tlon-kbid)  (Irreversible) | `DELETE /api/tlon/:kbId` |
| [Bind a knowledge collection so a digital human can read it](#post-api-tlon-kbid-bind-app) | `POST /api/tlon/:kbId/bind-app` |
| [Bind a knowledge collection to a space](#post-api-tlon-kbid-bind-space) | `POST /api/tlon/:kbId/bind-space` |
| [Wipe extracted text and re-index a collection from scratch](#post-api-tlon-kbid-clear-relearn) | `POST /api/tlon/:kbId/clear-relearn` |
| [Add existing server-local files to a collection by absolute path](#post-api-tlon-kbid-files) | `POST /api/tlon/:kbId/files` |
| [Read a collection's document map (index.md)](#get-api-tlon-kbid-index) | `GET /api/tlon/:kbId/index` |
| [Start indexing a collection's documents (fire-and-forget)](#post-api-tlon-kbid-ingest) | `POST /api/tlon/:kbId/ingest` |
| [Get the current indexing progress for a collection](#get-api-tlon-kbid-ingest-status) | `GET /api/tlon/:kbId/ingest-status` |
| [Watch a server-local directory as a live document source](#post-api-tlon-kbid-linked-dirs) | `POST /api/tlon/:kbId/linked-dirs` |
| [Stop watching a linked directory](#delete-api-tlon-kbid-linked-dirs-linkid) | `DELETE /api/tlon/:kbId/linked-dirs/:linkId` |
| [Look for an unavailable linked directory again and resume watching it if it is back](#post-api-tlon-kbid-linked-dirs-linkid-retry) | `POST /api/tlon/:kbId/linked-dirs/:linkId/retry` |
| [List the raw source files added to a collection](#get-api-tlon-kbid-raw) | `GET /api/tlon/:kbId/raw` |
| [Remove one document (raw source file) from a collection](#post-api-tlon-kbid-remove-raw)  (Irreversible) | `POST /api/tlon/:kbId/remove-raw` |
| [Map paths read from a collection back to their source documents](#post-api-tlon-kbid-resolve-sources) | `POST /api/tlon/:kbId/resolve-sources` |
| [Unbind a knowledge collection from a digital human](#post-api-tlon-kbid-unbind-app) | `POST /api/tlon/:kbId/unbind-app` |
| [Unbind a knowledge collection from a space](#post-api-tlon-kbid-unbind-space) | `POST /api/tlon/:kbId/unbind-space` |
| [List knowledge collections bound to a space (or the default one)](#get-api-tlon-for-space-spaceid) | `GET /api/tlon/for-space/:spaceId` |
| [Set (or clear) the collection new conversations auto-load](#post-api-tlon-set-default) | `POST /api/tlon/set-default` |

## List all knowledge collections {#get-api-tlon}

`GET /api/tlon`  (Assistant)

**Response**

```ts
{success:true,data:[{id,name,icon,description,status,spaceIds,appIds,stats,isDefault?,...}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Create a knowledge collection {#post-api-tlon}

`POST /api/tlon`  (Assistant)

- Optional fields: icon, description, linkedDirs (array of {path,label}).

**Request body**

```json
{
  "name": "Product Docs"
}
```

**Response**

```ts
{success:true,data:{id,name,icon,description,status:"active",createdAt,updatedAt,path,linkedDirs:[],spaceIds:[],appIds:[],stats:{...}}}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Product Docs"}'
```

## Get one knowledge collection by id {#get-api-tlon-kbid}

`GET /api/tlon/:kbId`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:{id,name,icon,description,status,linkedDirs,spaceIds,appIds,stats,...}} or {success:false,error:"Knowledge base not found"}
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon/<kbId>" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Rename or edit a collection's description/status {#put-api-tlon-kbid}

`PUT /api/tlon/:kbId`  (Reversible)  (Assistant)

- All fields optional: name, icon, description, status ("active"|"paused"|"error").

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "name": "New name"
}
```

**Response**

```ts
{success:true,data:<updated entry>} or {success:false,error:"Knowledge base not found"}
```

**Example**

```bash
curl -sX PUT "$HALO_URL/api/tlon/<kbId>" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"New name"}'
```

## Delete a knowledge collection and all its data {#delete-api-tlon-kbid}

`DELETE /api/tlon/:kbId`  (Irreversible)  (Assistant)

::: danger Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
If you only need something narrower, use [`POST /api/tlon/:kbId/remove-raw`](#post-api-tlon-kbid-remove-raw)
:::

- Permanently deletes the collection's entire folder — original source files, extracted text, and the index. Not just the registry entry. If the user only meant to remove one document, use remove-raw instead.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:<deletion result>}
```

**Example**

```bash
curl -sX DELETE "$HALO_URL/api/tlon/<kbId>" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Bind a knowledge collection so a digital human can read it {#post-api-tlon-kbid-bind-app}

`POST /api/tlon/:kbId/bind-app`  (Reversible)  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "appId": "<appId — a uuid from GET /api/apps>"
}
```

**Response**

```ts
{success:true,data:<updated entry>}
```

**Errors**

- 400 if appId is missing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/bind-app" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId — a uuid from GET /api/apps>"}'
```

## Bind a knowledge collection to a space {#post-api-tlon-kbid-bind-space}

`POST /api/tlon/:kbId/bind-space`  (Reversible)  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "spaceId": "<spaceId — a uuid from GET /api/spaces>"
}
```

**Response**

```ts
{success:true,data:<updated entry>}
```

**Errors**

- 400 if spaceId is missing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/bind-space" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spaceId":"<spaceId — a uuid from GET /api/spaces>"}'
```

## Wipe extracted text and re-index a collection from scratch {#post-api-tlon-kbid-clear-relearn}

`POST /api/tlon/:kbId/clear-relearn`  (Reversible)  (Assistant)

- Use when documents were updated in place and stale extracted text needs replacing. Only clears derived/regeneratable data (extracted text, index) — original source files are untouched and text is rebuilt automatically.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:true} or {success:false,error:"Learning is already in progress"}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/clear-relearn" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Add existing server-local files to a collection by absolute path {#post-api-tlon-kbid-files}

`POST /api/tlon/:kbId/files`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "filePaths": [
    "/absolute/path/to/file.pdf"
  ]
}
```

**Response**

```ts
{success:true,data:{added:string[],rejected:string[]}}
```

**Errors**

- 400 if filePaths is missing or empty. Files are copied in, not linked — see linked-dirs for live-watched folders.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/files" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"filePaths":["/absolute/path/to/file.pdf"]}'
```

## Read a collection's document map (index.md) {#get-api-tlon-kbid-index}

`GET /api/tlon/:kbId/index`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:string}  — markdown text, empty string if not yet generated
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon/<kbId>/index" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Start indexing a collection's documents (fire-and-forget) {#post-api-tlon-kbid-ingest}

`POST /api/tlon/:kbId/ingest`  (Assistant)

- Returns immediately; the indexing job runs in the background. Poll [`GET /api/tlon/:kbId/ingest-status`](#get-api-tlon-kbid-ingest-status) for progress — back off (2s, then double), do not tight-loop.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:true}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/ingest" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Get the current indexing progress for a collection {#get-api-tlon-kbid-ingest-status}

`GET /api/tlon/:kbId/ingest-status`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:{kbId,total,completed,current?,phase:"idle"|"running"|"done"|"error",error?,errors?:[{file,message}]}}
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon/<kbId>/ingest-status" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Watch a server-local directory as a live document source {#post-api-tlon-kbid-linked-dirs}

`POST /api/tlon/:kbId/linked-dirs`  (Assistant)

- Optional field: label (defaults to path).
- Failure carries a code instead of throwing: TOO_MANY_FILES (data:{count,limit}), DISK_ROOT (cannot watch a filesystem root), FOLDER_TOO_LARGE, PATH_NOT_FOUND, KB_NOT_FOUND.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "path": "/Users/me/Documents/product-docs"
}
```

**Response**

```ts
{success:true,data:{id,path,label,watching,lastScannedAt?}} on success
```

**Errors**

- 400 if path is missing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/linked-dirs" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path":"/Users/me/Documents/product-docs"}'
```

## Stop watching a linked directory {#delete-api-tlon-kbid-linked-dirs-linkid}

`DELETE /api/tlon/:kbId/linked-dirs/:linkId`  (Reversible)  (Assistant)

- Only removes the watch registration — the target folder and its files are untouched, and can be re-linked.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |
| `linkId` | Id of a folder linked to a knowledge base. |

**Response**

```ts
{success:true,data:<removal result>}
```

**Example**

```bash
curl -sX DELETE "$HALO_URL/api/tlon/<kbId>/linked-dirs/<linkId>" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Look for an unavailable linked directory again and resume watching it if it is back {#post-api-tlon-kbid-linked-dirs-linkid-retry}

`POST /api/tlon/:kbId/linked-dirs/:linkId/retry`  (Reversible)  (Assistant)

- A link shows watching:false when its folder was missing at the last check (e.g. an unplugged drive). Halo looks again at every start; this asks now.
- Failure carries a code: PATH_NOT_FOUND (the folder is still missing), LINK_NOT_FOUND, KB_NOT_FOUND.
- learningPaused ({reason:"too-many-files",count,limit} or {reason:"too-large"}) on a link means the folder is there but over the watched-folder limits, so its files are not learned.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |
| `linkId` | Id of a folder linked to a knowledge base. |

**Response**

```ts
{success:true,data:{id,path,label,watching:true,lastScannedAt?,learningPaused?}} when the folder is there
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/linked-dirs/<linkId>/retry" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## List the raw source files added to a collection {#get-api-tlon-kbid-raw}

`GET /api/tlon/:kbId/raw`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Response**

```ts
{success:true,data:[{name,path,size,learned:boolean,state:"learned"|"pending"|"no-text"|"failed",error?,source:"raw"|"linked",dirLabel?}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon/<kbId>/raw" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Remove one document (raw source file) from a collection {#post-api-tlon-kbid-remove-raw}

`POST /api/tlon/:kbId/remove-raw`  (Irreversible)  (Assistant)

::: danger Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
:::

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "relativePath": "raw/file.pdf"
}
```

**Response**

```ts
{success:true,data:<removal result>}
```

**Errors**

- 400 if relativePath is missing. Permanently deletes the copied file from the collection — no trash, no undo.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/remove-raw" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"relativePath":"raw/file.pdf"}'
```

## Map paths read from a collection back to their source documents {#post-api-tlon-kbid-resolve-sources}

`POST /api/tlon/:kbId/resolve-sources`  (Assistant)

- Maps paths read from a knowledge base back to the original documents, deduplicated and absolute. Paths that no longer exist are dropped, so a shorter list than the one sent is normal.

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "readPaths": [
    "/absolute/path/inside/the/collection"
  ]
}
```

**Response**

```ts
{success:true,data:[{name:string,path:string}]}  — deduped, absolute, files that still exist
```

**Errors**

- 400 when `readPaths` is not an array; an empty array returns nothing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/resolve-sources" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"readPaths":["/absolute/path/inside/the/collection"]}'
```

## Unbind a knowledge collection from a digital human {#post-api-tlon-kbid-unbind-app}

`POST /api/tlon/:kbId/unbind-app`  (Reversible)  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "appId": "<appId — a uuid from GET /api/apps>"
}
```

**Response**

```ts
{success:true,data:<updated entry>}
```

**Errors**

- 400 if appId is missing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/unbind-app" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appId":"<appId — a uuid from GET /api/apps>"}'
```

## Unbind a knowledge collection from a space {#post-api-tlon-kbid-unbind-space}

`POST /api/tlon/:kbId/unbind-space`  (Reversible)  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `kbId` | Knowledge base id, from [`GET /api/tlon`](#get-api-tlon). |

**Request body**

```json
{
  "spaceId": "<spaceId — a uuid from GET /api/spaces>"
}
```

**Response**

```ts
{success:true,data:<updated entry>}
```

**Errors**

- 400 if spaceId is missing.

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/<kbId>/unbind-space" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spaceId":"<spaceId — a uuid from GET /api/spaces>"}'
```

## List knowledge collections bound to a space (or the default one) {#get-api-tlon-for-space-spaceid}

`GET /api/tlon/for-space/:spaceId`  (Assistant)

**Path parameters**

| Name | Description |
| --- | --- |
| `spaceId` | Workspace id, from [`GET /api/spaces`](https://hello-halo.cc/docs/en/developers/api/workspace.html#get-api-spaces). The Halo Workspace is always `halo-temp`. |

**Response**

```ts
{success:true,data:[{id,name,icon,description,status,...}]}
```

**Example**

```bash
curl -s "$HALO_URL/api/tlon/for-space/<spaceId>" \
  -H "Authorization: Bearer $HALO_TOKEN"
```

## Set (or clear) the collection new conversations auto-load {#post-api-tlon-set-default}

`POST /api/tlon/set-default`  (Reversible)  (Assistant)

- kbId can be null to clear the default.

**Request body**

```json
{
  "kbId": "<kbId — a uuid from GET /api/tlon>"
}
```

**Response**

```ts
{success:true,data:<updated entry or null>}
```

**Example**

```bash
curl -sX POST "$HALO_URL/api/tlon/set-default" \
  -H "Authorization: Bearer $HALO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kbId":"<kbId — a uuid from GET /api/tlon>"}'
```
