> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Knowledge

> Upload documents, search them semantically, and ground your agents in your own content.

The knowledge store holds **documents** (uploaded files, pasted text,
or Google Drive imports) 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](/api-reference/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`.

<Note>
  All **write** operations on this page require the `admin` role (or
  an `sk_live_` key, which carries full access). Reads are available
  to every member.
</Note>

## 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-drive`           | Import files from Google Drive                     |
| `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

```json theme={null}
{
  "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                                                                                                                                                  |
| `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`), `source_origin` (`upload` or
`google_drive`), `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

<CodeGroup>
  ```bash cURL theme={null}
  curl 'https://api.thunderphone.com/v1/knowledge/documents?status=ready' \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

| Query param | Description                                |
| ----------- | ------------------------------------------ |
| `q`         | Substring match on name or content         |
| `status`    | `pending`, `processing`, `ready`, `failed` |

Returns a plain array of [document objects](#knowledge-document-object),
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):

<CodeGroup>
  ```bash cURL (files) theme={null}
  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'
  ```

  ```bash cURL (text) theme={null}
  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…"}'
  ```
</CodeGroup>

**JSON** — `{name, content}` creates a `source_type="text"` document.

```json Response (201 Created) theme={null}
{
  "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](#knowledge-document-object) 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.

<CodeGroup>
  ```bash cURL theme={null}
  curl 'https://api.thunderphone.com/v1/knowledge/documents/8b2f4a1e-…/original?download=1' \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns either the file stream directly or a short-lived signed URL:

```json theme={null}
{ "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.

```json Response theme={null}
{ "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 from Google Drive

Imports Google Docs (exported to text) and supported Drive files. You
supply a short-lived Drive **access token** obtained client-side (the
dashboard uses the Google Picker); ThunderPhone downloads the selected
files server-side.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/knowledge/documents/import-drive \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "access_token": "ya29.…",
      "files": [
        { "id": "1AbC…", "name": "Refund policy", "mimeType": "application/vnd.google-apps.document" }
      ]
    }'
  ```
</CodeGroup>

| Field          | Type   | Required | Description                                                                                                                         |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `access_token` | string | yes      | Google OAuth token with Drive read scope                                                                                            |
| `files`        | array  | yes      | `{id, name, mimeType}` per file. Google Docs plus `.txt`/`.md`/`.csv`/`.pdf`/`.docx` MIME types are supported; others are `skipped` |

```json Response (201 when anything imported, else 200) theme={null}
{
  "results": [
    {
      "id": "1AbC…",
      "name": "Refund policy",
      "mimeType": "application/vnd.google-apps.document",
      "status": "imported",
      "document_id": "8b2f4a1e-…",
      "document": { /* document object */ },
      "warnings": []
    }
  ]
}
```

Per-file `status` is `imported`, `skipped` (unsupported type /
oversize), or `error` — failures never abort the other files.

***

## Search

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

<CodeGroup>
  ```bash cURL theme={null}
  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?"}'
  ```
</CodeGroup>

| Field          | Type          | Required | Description                                          |
| -------------- | ------------- | -------- | ---------------------------------------------------- |
| `query`        | string        | yes      | ≤ 4000 chars                                         |
| `document_ids` | array of UUID | no       | Restrict the search scope; omit to search everything |

```json Response theme={null}
{
  "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`.

***

## Related

<CardGroup cols={2}>
  <Card title="Agents" icon="robot" href="/api-reference/agents">
    Attach documents and bases via `knowledge_document_ids` / `knowledge_base_ids`.
  </Card>

  <Card title="Calls" icon="phone" href="/api-reference/calls">
    Knowledge lookups appear in the call history as tool calls.
  </Card>
</CardGroup>
