Repository navigation
API Reference Knowledge
What the assistant knows: the website it learned, uploaded files, hand-written knowledge and business details. Part of the API reference: setup, shared types and errors are there.
-
Knowledge base status:
GET /knowledge/status -
Find the website pages:
POST /knowledge/discover -
Learn the website:
POST /knowledge/crawl -
Try failed pages again:
POST /knowledge/crawl/retry -
Cancel a crawl:
POST /knowledge/runs/:id/cancel -
List learned pages:
GET /knowledge/pages -
Chunks of a page:
GET /knowledge/pages/:id/chunks -
Search the knowledge base:
POST /knowledge/search -
Suggest starter questions:
POST /knowledge/suggest-questions -
List hand-written knowledge:
GET /knowledge/manual -
Add or replace hand-written knowledge:
POST /knowledge/manual -
Remove hand-written knowledge:
DELETE /knowledge/manual/:id -
Questions to answer:
GET /knowledge/gaps -
Answer a question:
POST /knowledge/gaps/:id/answer -
Dismiss a question, or open it again:
PATCH /knowledge/gaps/:id -
List uploaded files:
GET /knowledge/files -
Upload a file:
POST /knowledge/files -
Remove an uploaded file:
DELETE /knowledge/files/:id -
Business details:
GET /knowledge/facts -
Set business details:
PUT /knowledge/facts -
Read business details from the site:
POST /knowledge/facts/detect
GET /knowledge/status · scope knowledge:read
The latest crawl, page counts, chunks and today's AI usage.
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/status" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/status`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as KnowledgeBaseStatusResponse;Response 200
{
"site": "acme",
"connector": "workers-ai",
"browserRendering": false,
"workflow": true,
"schedule": "weekly",
"run": {
"id": "run_1",
"status": "done",
"trigger": "dashboard",
"total": 42,
"done": 42,
"failed": 0,
"chunks": 310,
"error": null,
"startedAt": 1760000000000,
"finishedAt": 1760000090000
},
"pages": {
"indexed": 40,
"unchanged": 2
},
"chunks": 310,
"lastIndexedAt": 1760000090000,
"usage": {
"day": "2025-10-09",
"neurons": 1240.5,
"messages": 64,
"budget": 9000,
"freeAllocation": 10000,
"messagesLeft": 400,
"state": "ok"
}
}type KnowledgeBaseStatusResponse = {
site: string;
connector: string;
browserRendering: boolean;
workflow: boolean;
schedule: string;
run: {
id: string;
status: string;
trigger: string;
total: number;
done: number;
failed: number;
chunks: number;
error: string | null;
startedAt: number;
finishedAt: number;
};
pages: Record<string, number>;
chunks: number;
lastIndexedAt: number;
usage: {
day: string;
neurons: number;
messages: number;
budget: number;
freeAllocation: number;
messagesLeft: number;
state: string;
};
};POST /knowledge/discover · scope knowledge:write
Reads robots.txt and the sitemaps, and suggests which pages to learn.
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/discover" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'type FindWebsitePagesRequest = Record<string, never>;
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/discover`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({} satisfies FindWebsitePagesRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as FindWebsitePagesResponse;Response 200
{
"site": "acme",
"origin": "https://acme.example",
"reachable": true,
"sitemaps": [
"https://acme.example/sitemap.xml"
],
"warnings": [],
"urls": [
{
"url": "https://acme.example/pricing",
"category": "pricing",
"suggested": true,
"status": "indexed",
"title": "Pricing",
"error": null,
"selected": true
}
]
}type FindWebsitePagesResponse = {
site: string;
origin: string;
reachable: boolean;
sitemaps: string[];
warnings: string[];
urls: Array<{
url: string;
category: string;
suggested: boolean;
status: string;
title: string;
error: string | null;
selected: boolean;
}>;
};POST /knowledge/crawl · scope knowledge:write
Crawls the given pages (or the selected ones) in the background. Follow progress with GET /knowledge/status.
| Body field | Required | Description |
|---|---|---|
urls |
no | The pages to learn. Without it, the selected pages. |
include |
no | Glob patterns of pages to add, e.g. ["/services/**"]. |
exclude |
no | Glob patterns of pages to leave out. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/crawl" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://acme.example/pricing",
"https://acme.example/services"
]
}'type LearnWebsiteRequest = {
/** The pages to learn. Without it, the selected pages. */
urls?: string[];
/** Glob patterns of pages to add, e.g. `["/services/**"]`. */
include?: string;
/** Glob patterns of pages to leave out. */
exclude?: string;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/crawl`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
urls: [
'https://acme.example/pricing',
'https://acme.example/services'
]
} satisfies LearnWebsiteRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as LearnWebsiteResponse;Response 202
{
"site": "acme",
"runId": "run_2",
"total": 2
}type LearnWebsiteResponse = {
site: string;
runId: string;
total: number;
};POST /knowledge/crawl/retry · scope knowledge:write
Crawls the pages that failed (or the given ones among them) again, in the background, without changing which pages are selected. Pages that failed for a passing reason are also retried by themselves after a crawl. 409 while a crawl is running.
| Body field | Required | Description |
|---|---|---|
urls |
no | Only these failed pages. Without it, every failed page. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/crawl/retry" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://acme.example/industries"
]
}'type TryFailedPagesAgainRequest = {
/** Only these failed pages. Without it, every failed page. */
urls?: string[];
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/crawl/retry`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
urls: [
'https://acme.example/industries'
]
} satisfies TryFailedPagesAgainRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as TryFailedPagesAgainResponse;Response 202
{
"site": "acme",
"runId": "run_3",
"total": 1
}type TryFailedPagesAgainResponse = {
site: string;
runId: string;
total: number;
};POST /knowledge/runs/:id/cancel · scope knowledge:write
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/runs/ID/cancel" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/runs/${id}/cancel`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as CancelCrawlResponse;Response 200
{
"cancelled": true
}type CancelCrawlResponse = {
cancelled: boolean;
};GET /knowledge/pages · scope knowledge:read
| Parameter | In | Required | Description |
|---|---|---|---|
status |
query | no |
discovered, indexed, unchanged, failed… |
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/pages" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/pages`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ListLearnedPagesResponse;Response 200
{
"site": "acme",
"pages": [
{
"id": "pg_1",
"url": "https://acme.example/pricing",
"finalUrl": "https://acme.example/pricing",
"title": "Pricing",
"category": "pricing",
"status": "indexed",
"httpStatus": 200,
"error": null,
"selected": 1,
"source": "crawl",
"crawledAt": 1760000000000,
"chunks": 6
}
]
}type ListLearnedPagesResponse = {
site: string;
pages: Array<{
id: string;
url: string;
finalUrl: string;
title: string;
category: string;
status: string;
httpStatus: number;
error: string | null;
selected: number;
source: string;
crawledAt: number;
chunks: number;
}>;
};GET /knowledge/pages/:id/chunks · scope knowledge:read
The passages the assistant searches, as stored.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/pages/ID/chunks" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/pages/${id}/chunks`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ChunksPageResponse;Response 200
{
"chunks": [
{
"id": "ch_1",
"headingPath": "Pricing › Drains",
"ordinal": 0,
"content": "Blocked drains: $180–$250 including the first hour.",
"tokens": 14
}
]
}type ChunksPageResponse = {
chunks: Array<{
id: string;
headingPath: string;
ordinal: number;
content: string;
tokens: number;
}>;
};POST /knowledge/search · scope knowledge:read
What the assistant would find for a question (hybrid search, then the reranker). A few neurons.
| Body field | Required | Description |
|---|---|---|
query |
yes | The question (up to 1000 characters). |
k |
no | How many passages to return. |
rerank |
no |
false to skip the reranker. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/search" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "blocked drain price",
"k": 3
}'type SearchKnowledgeBaseRequest = {
/** The question (up to 1000 characters). */
query: string;
/** How many passages to return. */
k?: number;
/** `false` to skip the reranker. */
rerank?: string;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/search`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'blocked drain price',
k: 3
} satisfies SearchKnowledgeBaseRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as SearchKnowledgeBaseResponse;Response 200
{
"site": "acme",
"query": "blocked drain price",
"neurons": 2.1,
"chunks": [
{
"id": "ch_1",
"pageId": "pg_1",
"url": "https://acme.example/pricing",
"title": "Pricing",
"headingPath": "Pricing › Drains",
"category": "pricing",
"content": "Blocked drains: $180–$250…",
"ordinal": 0,
"score": 0.91
}
],
"trace": {
"vector": 8,
"keyword": 5,
"fused": 10,
"reranked": true,
"threshold": 0.2,
"errors": [],
"ms": {
"embed": 40,
"vector": 30,
"keyword": 6,
"rerank": 120
}
}
}type SearchKnowledgeBaseResponse = {
site: string;
query: string;
neurons: number;
chunks: Array<{
id: string;
pageId: string;
url: string;
title: string;
headingPath: string;
category: string;
content: string;
ordinal: number;
score: number;
}>;
trace: {
vector: number;
keyword: number;
fused: number;
reranked: boolean;
threshold: number;
errors: string[];
ms: Record<string, number>;
};
};POST /knowledge/suggest-questions · scope knowledge:read
Four questions a customer might ask, written from what the site taught the assistant.
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/suggest-questions" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'type SuggestStarterQuestionsRequest = Record<string, never>;
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/suggest-questions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({} satisfies SuggestStarterQuestionsRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as SuggestStarterQuestionsResponse;Response 200
{
"questions": [
"How much is a blocked drain?",
"Do you do emergency callouts?",
"Which suburbs do you cover?",
"Can I book online?"
],
"source": "model"
}type SuggestStarterQuestionsResponse = {
questions: string[];
source: string;
};GET /knowledge/manual · scope knowledge:read
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/manual" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/manual`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ListHandWrittenKnowledgeResponse;Response 200
{
"entries": [
{
"id": "holiday-hours",
"title": "Holiday hours",
"content": "Closed 25–26 December.",
"updatedAt": 1760000000000
}
]
}type ListHandWrittenKnowledgeResponse = {
entries: Array<{
id: string;
title: string;
content: string;
updatedAt: number;
}>;
};POST /knowledge/manual · scope knowledge:write
Searchable at once. The same id replaces an entry.
| Body field | Required | Description |
|---|---|---|
title |
yes | Up to 200 characters. |
content |
yes | Markdown or plain text, up to 100,000 characters. |
id |
no | Lowercase letters, digits and -. The same id replaces an entry. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/manual" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "holiday-hours",
"title": "Holiday hours",
"content": "We are closed on 25 and 26 December."
}'type AddReplaceHandWrittenKnowledgeRequest = {
/** Up to 200 characters. */
title: string;
/** Markdown or plain text, up to 100,000 characters. */
content: string;
/** Lowercase letters, digits and `-`. The same id replaces an entry. */
id?: string;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/manual`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: 'holiday-hours',
title: 'Holiday hours',
content: 'We are closed on 25 and 26 December.'
} satisfies AddReplaceHandWrittenKnowledgeRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as AddReplaceHandWrittenKnowledgeResponse;Response 200
{
"id": "holiday-hours",
"chunks": 1
}type AddReplaceHandWrittenKnowledgeResponse = {
id: string;
chunks: number;
};DELETE /knowledge/manual/:id · scope knowledge:write
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
site |
query | no | The site (a key always uses its own). |
Request
curl -X DELETE "$HELPPUFF_URL/api/v1/knowledge/manual/ID" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/manual/${id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as RemoveHandWrittenKnowledgeResponse;Response 200
{
"deleted": true
}type RemoveHandWrittenKnowledgeResponse = {
deleted: boolean;
};GET /knowledge/gaps · scope knowledge:read
What visitors asked about the business that the assistant could not answer and the knowledge base does not cover, found after each conversation (off-topic questions, and ones the instructions say not to answer, are left out). Most asked first. Answer one with POST /knowledge/gaps/:id/answer; a new one also sends knowledge.gap to webhooks and, to whoever chose it, an email.
| Parameter | In | Required | Description |
|---|---|---|---|
status |
query | no |
open (the default), answered, dismissed or all. |
limit |
query | no | 1–200, default 100. |
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/gaps" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/gaps`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as QuestionsToAnswerResponse;Response 200
{
"gaps": [
{
"id": "gap_4f3e2d1c0b9a8f7e6d5c",
"question": "Do you convert cooktops from natural gas to LPG?",
"count": 3,
"firstAskedAt": 1760000000000,
"lastAskedAt": 1760000000000,
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "open",
"answerId": null,
"answeredBy": null
}
],
"counts": {
"open": 4,
"answered": 9,
"dismissed": 1
}
}type QuestionsToAnswerResponse = {
gaps: Array<{
id: string;
question: string;
count: number;
firstAskedAt: number;
lastAskedAt: number;
conversationId: string | null;
status: string;
answerId: string | null;
answeredBy: string | null;
}>;
counts: Record<string, number>;
};POST /knowledge/gaps/:id/answer · scope knowledge:write
The answer becomes hand-written knowledge (/knowledge/manual, the question as its title), searchable at once; the question is marked answered. Answering again replaces it.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
| Body field | Required | Description |
|---|---|---|
answer |
yes | The answer, up to 20,000 characters. |
title |
no | The entry's title (default: the question). |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/gaps/ID/answer" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"answer": "No: we only work on natural gas cooktops. For LPG, call a licensed gas fitter."
}'type AnswerQuestionRequest = {
/** The answer, up to 20,000 characters. */
answer: string;
/** The entry's title (default: the question). */
title?: string;
};
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/gaps/${id}/answer`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
answer: 'No: we only work on natural gas cooktops. For LPG, call a licensed gas fitter.'
} satisfies AnswerQuestionRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as AnswerQuestionResponse;Response 200
{
"gap": {
"id": "gap_4f3e2d1c0b9a8f7e6d5c",
"question": "Do you convert cooktops from natural gas to LPG?",
"count": 3,
"firstAskedAt": 1760000000000,
"lastAskedAt": 1760000000000,
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "answered",
"answerId": "gap-4f3e2d1c0b9a8f7e6d5c",
"answeredBy": "Olivia"
},
"entry": {
"id": "gap-4f3e2d1c0b9a8f7e6d5c",
"chunks": 1
}
}type AnswerQuestionResponse = {
gap: {
id: string;
question: string;
count: number;
firstAskedAt: number;
lastAskedAt: number;
conversationId: string;
status: string;
answerId: string;
answeredBy: string;
};
entry: {
id: string;
chunks: number;
};
};PATCH /knowledge/gaps/:id · scope knowledge:write
status: dismissed (not something to answer; it stays out of the list, asked again or not) or open.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
| Body field | Required | Description |
|---|---|---|
status |
yes |
dismissed or open. |
Request
curl -X PATCH "$HELPPUFF_URL/api/v1/knowledge/gaps/ID" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "dismissed"
}'type DismissQuestionOpenItAgainRequest = {
/** `dismissed` or `open`. */
status: string;
};
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/gaps/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'dismissed'
} satisfies DismissQuestionOpenItAgainRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as DismissQuestionOpenItAgainResponse;Response 200
{
"id": "gap_4f3e2d1c0b9a8f7e6d5c",
"question": "Do you convert cooktops from natural gas to LPG?",
"count": 3,
"firstAskedAt": 1760000000000,
"lastAskedAt": 1760000000000,
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "dismissed",
"answerId": null,
"answeredBy": null
}type DismissQuestionOpenItAgainResponse = {
id: string;
question: string;
count: number;
firstAskedAt: number;
lastAskedAt: number;
conversationId: string | null;
status: string;
answerId: string | null;
answeredBy: string | null;
};GET /knowledge/files · scope knowledge:read
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/files" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/files`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ListUploadedFilesResponse;Response 200
{
"files": [
{
"id": "0b8f…",
"name": "price-list.pdf",
"kind": "pdf",
"size": 182044,
"status": "indexed",
"error": null,
"chunks": 12,
"truncated": 0,
"createdAt": 1760000000000,
"updatedAt": 1760000000000
}
]
}type ListUploadedFilesResponse = {
files: Array<{
id: string;
name: string;
kind: string;
size: number;
status: string;
error: string | null;
chunks: number;
truncated: number;
createdAt: number;
updatedAt: number;
}>;
};POST /knowledge/files · scope knowledge:write
The raw bytes as the body, the file name as ?name=. PDF, Word (.docx), Markdown or text, up to 10 MB. Read and learned in the background.
| Parameter | In | Required | Description |
|---|---|---|---|
name |
query | yes | The file name, with its extension. |
site |
query | no | The site (a key always uses its own). |
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/files?name=price-list.pdf" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @price-list.pdfimport { readFile } from 'node:fs/promises';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/files?${new URLSearchParams({ name: 'price-list.pdf' })}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/octet-stream',
},
body: await readFile('price-list.pdf'),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as UploadFileResponse;The body is the file's bytes.
Response 202
{
"id": "0b8f…",
"name": "price-list.pdf",
"kind": "pdf",
"size": 182044,
"status": "queued"
}type UploadFileResponse = {
id: string;
name: string;
kind: string;
size: number;
status: string;
};DELETE /knowledge/files/:id · scope knowledge:write
And everything learned from it.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
site |
query | no | The site (a key always uses its own). |
Request
curl -X DELETE "$HELPPUFF_URL/api/v1/knowledge/files/ID" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/files/${id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as RemoveUploadedFileResponse;Response 200
{
"deleted": true,
"chunks": 12
}type RemoveUploadedFileResponse = {
deleted: boolean;
chunks: number;
};GET /knowledge/facts · scope knowledge:read
Phone, email, address, hours, service areas: read from the site (crawl) or set by you (owner).
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/knowledge/facts" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/facts`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as BusinessDetailsResponse;Response 200
{
"facts": [
{
"key": "phone",
"value": "1300 000 000",
"source": "owner",
"sourceUrl": "owner"
}
]
}type BusinessDetailsResponse = {
facts: Array<{
key: string;
value: string;
source: string;
sourceUrl: string;
}>;
};PUT /knowledge/facts · scope knowledge:write
Yours are never overwritten by a crawl. An empty value removes one. Re-indexed at once.
| Body field | Required | Description |
|---|---|---|
facts |
yes | Keys name, phone, email, address, hours, serviceAreas and others; an empty value removes one. |
Request
curl -X PUT "$HELPPUFF_URL/api/v1/knowledge/facts" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"facts": {
"phone": "1300 000 000",
"hours": "Mon–Fri 8am–5pm"
}
}'type SetBusinessDetailsRequest = {
/**
* Keys `name`, `phone`, `email`, `address`, `hours`, `serviceAreas` and
* others; an empty value removes one.
*/
facts: Record<string, string>;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/facts`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
facts: {
phone: '1300 000 000',
hours: 'Mon–Fri 8am–5pm'
}
} satisfies SetBusinessDetailsRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as SetBusinessDetailsResponse;Response 200
{
"facts": [
{
"key": "phone",
"value": "1300 000 000"
},
{
"key": "hours",
"value": "Mon–Fri 8am–5pm"
}
]
}type SetBusinessDetailsResponse = {
facts: Array<{
key: string;
value: string;
}>;
};POST /knowledge/facts/detect · scope knowledge:write
Request
curl -X POST "$HELPPUFF_URL/api/v1/knowledge/facts/detect" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'type ReadBusinessDetailsFromSiteRequest = Record<string, never>;
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/knowledge/facts/detect`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({} satisfies ReadBusinessDetailsFromSiteRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ReadBusinessDetailsFromSiteResponse;Response 200
{
"found": 4,
"facts": [
{
"key": "phone",
"value": "1300 000 000",
"source": "site"
}
]
}type ReadBusinessDetailsFromSiteResponse = {
found: number;
facts: Array<{
key: string;
value: string;
source: string;
}>;
};Start
Tutorials
- All tutorials
- Order tracking
- Send chats to your CRM
- Nearest store
- Verified account changes
- Logged-in customer support
- Product and stock
- Hot leads to Slack
- Knowledge base or tool?
- Testing and monitoring
- Why the AI never books
Configure
- Configuration
- Configuration reference
- CLI reference
- Knowledge base
- Prompt and instructions
- Tools: your APIs in the chat
- Agent files: import and export
- Signed-in visitors
- The widget
- The playground
- Leads and callbacks
- Jobs
- Photos and files
- Bookings and the calendar
- Integrations
- Live chat
- Telegram
- Webhooks
- Email notifications
API
API reference
- Overview
- Account
- Chat
- Conversations
- Live chat
- Labels and notes
- Leads
- Callbacks
- Jobs
- Appointments
- Integrations
- Knowledge
- Prompt
- Tools
- Settings
- Webhooks
- Analytics
- Team
- API keys
- Audit
Providers
- Overview
- Models and providers
- Custom model
- Custom knowledge base
- Workers AI (default)
- AI models: speed, quality, cost
- Cloudflare AI Search
- OpenAI
- Gemini
- Anthropic Claude
- Retell
- Your own API
Operate
- Deployment
- Turn on Turnstile
- Upgrading
- Costs and limits
- Cloudflare Free plan limits
- Security
- Troubleshooting
Build