Skip to main content

Knowledge Base

The SendSeven Knowledge Base is an AI-powered system that stores your organization's documents, FAQs, and website content. When queried, it uses retrieval-augmented generation (RAG) to find relevant information and generate accurate answers with source citations.

Required Scopes​

ScopePurpose
knowledge_base:readSearch and list documents and FAQ entries
knowledge_base:createCreate documents and folders; preview, start, cancel and re-run website crawls
knowledge_base:updateEdit entries and resolve flags on FAQ entries
knowledge_base:deleteDelete documents; merge FAQ entries
knowledge_base:adminEverything above, including website crawls (crawl routes accept knowledge_base:create or knowledge_base:admin)
knowledge_base:manageApprove or reject AI corrections, retry their analysis, revert FAQ revisions

Key Capabilities​

  • AI-powered search -- ask natural language questions and receive synthesized answers
  • Document management -- upload text documents, FAQs, and files
  • Website crawling -- automatically import content from your website
  • Folder organization -- organize documents into folders (each folder becomes a separate search corpus)
  • Source scores -- every source carries a match score, so you can decide when an answer is safe to send

Pricing​

Knowledge Base search is free. The AI work around it (answers written from the results, adding knowledge) is paid in AI credits at EUR 0.01 per credit on every plan that includes the Knowledge Base. AI credits are billed separately from messages, but included messages left over in a month also cover AI credits (see AI credits: invoice and included pool). There is no separate Knowledge Base allowance:

PlanBase feeIncluded pool / monthPer AI credit beyond the pool
ProfessionalEUR 792,500EUR 0.01
ScaleEUR 19910,000EUR 0.01
EnterpriseEUR 49940,000EUR 0.01

What the Knowledge Base costs in AI credits:

AI credits
A search (POST /knowledge-base/search, /kb in the inbox, bot and agent lookups)Free
The AI answer written from the resultsCharged by the AI work it does
Adding knowledge (crawl, upload, re-import)1 AI credit per 10 pages

The Knowledge Base is not included on Basic or API Only. For the full price list, see AI Credits.


Search the Knowledge Base​

Ask a natural language question and receive an AI-generated answer with source references.

POST /api/v1/knowledge-base/search

Request Body​

FieldTypeRequiredDescription
querystringYesThe question or search query
limitintegerNoMax source documents to consider (default: 5, max: 20)
folder_idstringNoRestrict search to a specific folder

curl​

curl -X POST "https://api.sendseven.com/api/v1/knowledge-base/search" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"query": "What is your return policy for electronics?",
"limit": 5
}'

Response​

{
"query": "What is your return policy for electronics?",
"answer": "Electronics can be returned within 30 days of purchase in their original packaging. Refunds are processed within 5-7 business days after we receive the item.",
"citations": [
{"index": 1, "title": "Return Policy - Electronics", "url": "https://www.example.com/returns", "kind": "website", "score": 0.95, "excerpt": "Electronics may be returned within 30 days of purchase in original packaging..."}
],
"sources": [
{
"title": "Return Policy - Electronics",
"url": "https://www.example.com/returns",
"kind": "website",
"document_id": "8c1f2a3b-...",
"entry_id": null,
"score": 0.95,
"excerpt": "Electronics may be returned within 30 days of purchase in original packaging...",
"text": "Electronics may be returned within 30 days of purchase in original packaging..."
},
{
"title": "How long does a refund take?",
"url": null,
"kind": "faq_entry",
"document_id": null,
"entry_id": "3d9e7c10-...",
"score": 0.78,
"excerpt": "Refunds are processed within 5-7 business days...",
"text": "Refunds are processed within 5-7 business days..."
}
],
"source_count": 2
}
FieldNotes
answerThe AI answer, written only from the sources found. When nothing relevant is found, a short "could not find" message.
sources[]The passages the answer was written from, best match first.
sources[].kindwebsite (a crawled page), document (an uploaded or written document) or faq_entry.
sources[].titleThe document title or, for an FAQ entry, its question. null when the document has no readable title.
sources[].urlThe public web address of a crawled page, else null. This is the only field to link to.
sources[].alternate_urlsOnly on crawled pages whose website declares language versions (hreflang): the declared versions on the same domain as {language: url}, for example {"en": "https://www.example.com/en/returns", "de": "https://www.example.com/de/retouren", "x-default": "https://www.example.com/returns"}. Use it to link your customer to the page in their language. Missing on other sources.
sources[].content_languageOnly on crawled pages: the language of the imported page, when known.
sources[].document_id, sources[].entry_idThe Knowledge Base document or the FAQ entry the passage came from.
sources[].scoreHow closely the passage matches the query, 0 to 1.
sources[].excerptA short extract of the passage (text carries the same value for older clients).
citations[]The same passages, numbered (index) in the order they were given to the AI.
source_countNumber of sources.

Treat other keys in a source as internal and subject to change.

tip

Before you send an answer to a customer automatically, check that source_count is greater than 0 and that the best score is high enough for your use case. Route weak matches to a human agent.

Errors: besides the common errors, 403 with detail.code trial_ai_credits_exhausted when a free trial has used up its AI credits (see Free trial limits), and 429 above 60 requests per minute.

Python​

import requests

BASE_URL = "https://api.sendseven.com/api/v1"
HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
}

result = requests.post(
f"{BASE_URL}/knowledge-base/search",
headers=HEADERS,
json={"query": "How do I cancel my subscription?", "limit": 5},
).json()

print(f"Answer: {result['answer']}")
for source in result["sources"]:
print(f" Source: {source['title']} (score: {source['score']})")

JavaScript​

const BASE_URL = "https://api.sendseven.com/api/v1";
const HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
};

const response = await fetch(`${BASE_URL}/knowledge-base/search`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
query: "What payment methods do you accept?",
limit: 3,
}),
});
const result = await response.json();
console.log(result.answer);
for (const source of result.sources) {
console.log(` Source: ${source.title} (score: ${source.score})`);
}

Manage Documents​

List Documents​

GET /api/v1/knowledge-base/documents
ParameterTypeDefaultDescription
folder_idstringFilter by folder
document_typestringFilter by document type (e.g. faq, document, crawled)
searchstringSearch by title or source URL
pageinteger1Page number
page_sizeinteger20Items per page (max 100)
curl -X GET "https://api.sendseven.com/api/v1/knowledge-base/documents?page=1&page_size=20" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"

A document that could not be indexed has status: "failed" and a human-readable last_error (English). When the file itself is the cause, last_error_code names it: empty, no_text (scanned image without selectable text), pdf_encrypted, pdf_damaged, not_utf8, too_large, unsupported or unknown. Retrying the same file cannot succeed for these codes, so POST /knowledge-base/documents/{id}/retry returns 422 for them; upload a fixed file instead. For any other failure last_error_code is null and a retry may help.

Create a Document​

POST /api/v1/knowledge-base/documents
FieldTypeRequiredDescription
titlestringYesDocument title
typestringYesfaq or document
contentstringYesDocument content (plain text or HTML)
folder_idstringNoTarget folder ID
curl -X POST "https://api.sendseven.com/api/v1/knowledge-base/documents" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"title": "Product Warranty Information",
"type": "document",
"content": "All products sold by Acme Store come with a standard 12-month warranty. This warranty covers manufacturing defects and hardware failures under normal use.",
"folder_id": "folder_policies"
}'

Delete a Document​

DELETE /api/v1/knowledge-base/documents/{document_id}
curl -X DELETE "https://api.sendseven.com/api/v1/knowledge-base/documents/doc_warranty_info" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

Folders​

Folders organize documents. Each folder acts as a separate search corpus, allowing targeted search with the folder_id parameter.

List Folders​

GET /api/v1/knowledge-base/folders

Requires knowledge_base:read. Returns the folder tree: items holds the root folders, each with nested children, and total is the number of folders.

FieldWhereDescription
document_counteach folderDocuments stored directly in this folder (subfolders not included)
faq_entry_counteach folderFAQ entries stored directly in this folder, in any status (subfolders not included)
document_totaltop levelAll documents of the workspace, including documents in no folder. Matches total of GET /documents without folder_id.
faq_entry_totaltop levelAll FAQ entries of the workspace, including entries in no folder. Matches total of GET /faq-entries without folder_id.

Because the totals also count items that sit in no folder, they can be higher than the sum of the per-folder counts. Conversation summaries are not part of the Knowledge Base: their folders are not in the tree, and they are never counted in document_count or document_total or listed by GET /documents.

curl "https://api.sendseven.com/api/v1/knowledge-base/folders" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
{
"items": [
{
"id": "folder_general",
"name": "General",
"slug": "general",
"parent_id": null,
"is_system": true,
"document_count": 12,
"faq_entry_count": 0,
"is_ai_searchable": true,
"children": []
},
{
"id": "folder_managed_faq",
"name": "Managed FAQ",
"slug": "managed-faq",
"parent_id": null,
"is_system": true,
"document_count": 0,
"faq_entry_count": 38,
"is_ai_searchable": true,
"children": []
}
],
"total": 2,
"document_total": 12,
"faq_entry_total": 38
}

Create a Folder​

POST /api/v1/knowledge-base/folders
curl -X POST "https://api.sendseven.com/api/v1/knowledge-base/folders" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Documentation",
"description": "Technical documentation and user guides for all products"
}'

Website Crawl​

Trigger a website crawl to automatically import content from a URL. Crawled pages land in the Websites system folder. Requires the knowledge_base:create scope (knowledge_base:admin is also accepted). Checking a crawl's status needs knowledge_base:read.

POST /api/v1/knowledge-base/documents/crawl
FieldTypeRequiredDescription
urlstringYesStarting URL to crawl. End the URL with / to limit the crawl to that folder (example.com/de/ excludes example.com/desk).
follow_linksbooleanNoFollow links and crawl multiple pages (default true). Set false to index only the single URL.
page_limitintegerNoMaximum pages when follow_links is true (1–2000, default 200)
max_depthintegerNoMaximum crawl depth when follow_links is true (1–10)
title_prefixstringNoPrefix added to imported document titles (max 100 chars)
rate_limitnumberNoSeconds between requests (0.1–10.0)
refreshbooleanNoDelete existing documents in the crawl scope (the host of url, under its path) before crawling (default false)
exclude_pathsstring[]NoPath prefixes to leave out (max 50, each starts with /, max 200 chars). See Exclude rules.
exclude_query_paramsstring[]NoQuery parameters to leave out (max 20), as name or name=value. See Exclude rules.
languagestringNoContent language to keep, for example de, en or pt-br. Used with translation_handling.
translation_handlingstringNokeep_all (default) imports every language version. skip_translations skips pages that are translations of a page in language. Requires language. See Multilingual websites.

The crawl stays on the host of url and under its path. The path is a plain prefix: https://docs.acme.com/help imports /help, /help/... and also /helpdesk. End the URL with / to limit the crawl to that folder: https://docs.acme.com/help/ imports /help/... and /help itself, but not /helpdesk. Re-crawls and refresh use the same scope. Exclude rules narrow that scope further.

curl -X POST "https://api.sendseven.com/api/v1/knowledge-base/documents/crawl" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"url": "https://docs.acme.com",
"follow_links": true,
"page_limit": 100,
"max_depth": 3,
"exclude_paths": ["/blog/archive"],
"exclude_query_params": ["print"]
}'
{
"job_id": "b3f1c2d4-5678-90ab-cdef-1234567890ab",
"status": "queued",
"message": "Website crawl job started. Use the poll_url to check progress.",
"poll_url": "/api/v1/knowledge-base/documents/crawl/{job_id}/status"
}
info

Crawls run asynchronously. Poll GET /api/v1/knowledge-base/documents/crawl/{job_id}/status for progress, or watch it in the SendSeven dashboard under Knowledge Base.

Exclude rules​

Exclude rules apply to sitemap URLs, to every link the crawler follows and to every page before it is imported. They are plain prefixes and names, never regular expressions.

RuleMatchesDoes not match
exclude_paths: ["/en"]/en, /en/, /en/pricing/enterprise, /blog/en
exclude_paths: ["/blog/archive"]/blog/archive/2024/post/blog/archived, /blog
exclude_query_params: ["lang"]any URL with ?lang=..., whatever the valueURLs without lang
exclude_query_params: ["lang=en"]?lang=en (value is case-insensitive)?lang=de
  • Paths match on whole path segments and ignore upper and lower case. Each path must start with /; a trailing / is ignored. / on its own is rejected, because it would exclude the whole site.
  • Query parameters are given as name (any value) or name=value (that value only). Names may contain letters, digits, _, ., -, [ and ] (max 64 chars).
  • Duplicates are removed. You can send up to 50 paths and 20 query parameters.

Invalid rules return 422 with detail.error_code set to invalid_exclude_rule and a readable detail.message:

{
"detail": {
"error_code": "invalid_exclude_rule",
"message": "Excluding '/' would exclude the whole website."
}
}

The same error is returned for a malformed language, for translation_handling: "skip_translations" without a language, and for rules that would exclude the start URL itself (nothing would be imported).

Check a URL before crawling​

POST /api/v1/knowledge-base/documents/crawl/preview

Requires knowledge_base:create (or knowledge_base:admin). Send {"url": "https://example.com"}. The response is always 200: valid tells you whether the URL can be crawled (otherwise error_code is one of invalid_url, invalid_hostname, dns_unresolvable, blocked, unreachable, timeout or http_error). It also reports the page title, the final URL after redirects, the sitemap (sitemap.page_count, estimated_pages) and your workspace page limit (max_pages_limit).

language_analysis describes the languages of the website and suggests crawl settings. It is null when the URL is invalid or the analysis did not finish in time; it never blocks the preview.

FieldDescription
is_multilingualtrue when at least two language versions are part of the crawl scope
signalsHow languages were detected: sitemap_hreflang, html_hreflang, path_prefix, subdomain, query_param, html_lang
languages[]One entry per language, sorted by page_count (highest first): code, page_count (pages in the sitemap, null without a sitemap), location (root, path_prefix, subdomain, query_param or other), path_prefix, host, query_param, is_default
default_languageThe site's default language (x-default, else the start page's declared language)
tenant_languageYour workspace language
recommended_languageThe language SendSeven suggests keeping
recommendation_reasonmost_pages, tenant_language, default_language, only_language or null
pages_without_counterpartPages in other languages that have no version in the recommended language. skip_translations keeps them, because they may hold unique information.
already_scopedtrue when the URL already covers one language (for example example.com/de). No settings are needed.
recommended_settingslanguage, exclude_paths, exclude_query_params and translation_handling: "skip_translations", ready to send to the crawl endpoint. null when nothing needs to change.
alternative_languageSet only when your workspace language was recommended although another language has more pages: that more complete language. Otherwise null.
alternative_reasonmost_pages when alternative_language is set, else null
alternative_page_differenceHow many more pages the alternative language has than the recommended one
alternative_settingsCrawl settings for the alternative language, same shape as recommended_settings

Subdomain languages (de.example.com) are reported for information only: a crawl never leaves the host of its start URL.

How the recommendation is chosen:

  1. Keep the language with the most pages. That version is usually the most complete one, so the least information is lost; the other versions are mostly translations of it.
  2. If your workspace language has at least 90% of the largest page count, keep your workspace language instead.
  3. Without page counts (no sitemap), keep your workspace language if the site offers it, else the site's default language.

The settings are always expressed as exclude rules plus skip_translations, never as a narrower start URL. Pages outside the language folders (imprint, legal pages) are therefore still imported.

Example for https://sendseven.com in an English workspace. Each language has its own prefix; the sitemap lists 451 German pages under /de, 421 English under /en, 325 French under /fr and further languages. English has more than 90% of the German page count, so English is recommended and German, with 30 more pages, is offered as the alternative (response shortened, signals illustrative):

{
"valid": true,
"final_url": "https://sendseven.com/",
"language_analysis": {
"is_multilingual": true,
"signals": ["sitemap_hreflang", "path_prefix"],
"languages": [
{ "code": "de", "page_count": 451, "location": "path_prefix", "path_prefix": "/de" },
{ "code": "en", "page_count": 421, "location": "path_prefix", "path_prefix": "/en" },
{ "code": "fr", "page_count": 325, "location": "path_prefix", "path_prefix": "/fr" }
],
"tenant_language": "en",
"recommended_language": "en",
"recommendation_reason": "tenant_language",
"pages_without_counterpart": 49,
"already_scoped": false,
"recommended_settings": {
"language": "en",
"exclude_paths": ["/ar", "/de", "/es", "/fr", "/it", "/pl", "/pt", "/tr"],
"exclude_query_params": [],
"translation_handling": "skip_translations"
},
"alternative_language": "de",
"alternative_reason": "most_pages",
"alternative_page_difference": 30,
"alternative_settings": {
"language": "de",
"exclude_paths": ["/ar", "/en", "/es", "/fr", "/it", "/pl", "/pt", "/tr"],
"exclude_query_params": [],
"translation_handling": "skip_translations"
}
}
}

Both options exclude the prefixes of every other language. skip_translations additionally skips pages in another language whose version in the kept language is reachable in the crawl scope, for example translated pages outside the language folders. Pages that exist only in another language and lie outside the excluded prefixes are kept; pages_without_counterpart (49 here) counts the pages in other languages that have no English version. In a German workspace the same site recommends German (recommendation_reason: "most_pages") with the alternative_settings shown above, and all alternative_* fields are null.

To use the suggestion, copy recommended_settings (or alternative_settings, if you prefer the more complete language) into the crawl request next to url.

Crawl status​

GET /api/v1/knowledge-base/documents/crawl/{job_id}/status (scope knowledge_base:read) reports progress (status, total_pages, pages_crawled, pages_failed, documents_created, errors) and what the filters did:

FieldDescription
excluded_by_rulesPages left out because they matched an exclude rule
skipped_translationsPages skipped because their version in language is part of the crawl
skipped_translation_samplesUp to 20 examples: {url, language, counterpart_url}
languageThe language this crawl keeps (null when none was chosen)
translation_handlingkeep_all or skip_translations (null for crawls without filters)
filters_auto_appliedtrue when the exclude rules and language were applied automatically from the language recommendation (by the onboarding website scan) rather than chosen by a user
alternative_languageFor an automatically applied crawl: the more complete language that was not imported because your workspace language was recommended. null otherwise.
alternative_page_differenceHow many more pages alternative_language has than the imported language

For a multilingual website, the website scan during onboarding applies the recommended language automatically. Its crawl reports filters_auto_applied: true; to switch to the alternative language, re-crawl with alternative_settings from a new preview.

{
"job_id": "b3f1c2d4-5678-90ab-cdef-1234567890ab",
"status": "completed",
"base_url": "https://example.com",
"pages_crawled": 118,
"documents_created": 118,
"excluded_by_rules": 342,
"skipped_translations": 12,
"skipped_translation_samples": [
{
"url": "https://example.com/blog/hello",
"language": "en",
"counterpart_url": "https://example.com/blog/hallo"
}
],
"language": "de",
"translation_handling": "skip_translations"
}

Re-crawl with saved rules​

Exclude rules and language settings are saved with the crawled website and reused by every re-crawl, so you set them once.

  • GET /api/v1/knowledge-base/documents/crawled-domains (scope knowledge_base:read) lists crawled websites. Each entry carries crawl_filters (exclude_paths, exclude_query_params, language, translation_handling), or null when none were set.
  • POST /api/v1/knowledge-base/documents/recrawl (scope knowledge_base:create, body history_id, mode, ...) and POST /api/v1/knowledge-base/folders/{folder_id}/recrawl (scope knowledge_base:create) accept the same four fields.

Each of the four fields is handled on its own:

You sendResult
field omittedthe saved value is reused
"exclude_paths": [] / "exclude_query_params": []the saved rules are cleared
"language": nullthe saved language is cleared
a new valuereplaces the saved value and is saved for future re-crawls

The combined result is validated like a new crawl, so it can return 422 invalid_exclude_rule too. After a successful re-crawl, pages that are now excluded or skipped as translations are removed from the Knowledge Base. To clean up a website that was imported in all languages, re-crawl it with language and translation_handling: "skip_translations".

Multilingual websites​

Import one language of a multilingual website. A Knowledge Base answer draws on up to 5 sources. When several of them are translations of the same article, they crowd out the other pages that would have helped, and answers get worse. Your bots and AI agents answer in the customer's language anyway, so one language version is usually enough.

  • Language under its own prefix (example.com/de): crawl https://example.com/de/ (the trailing / keeps /desk and similar paths out), or crawl the root and exclude the other prefixes.
  • Main language at the root without a prefix (example.com in German, plus /en, /fr, /it): crawl https://example.com and exclude /en, /fr and /it. German pages, including pages outside any language folder, are imported.
  • Language as a query parameter (?lang=en): exclude lang=en, lang=fr and so on, keeping the value you want.

Add "language" and "translation_handling": "skip_translations" as a safety net for translations the exclude rules miss. A page is skipped only when both are true:

  1. its own language (from hreflang, the declared page language or a language prefix) is known and differs from language, and
  2. it links a version of itself in language (hreflang) that is part of the same crawl.

Pages that exist only in another language, and pages whose language cannot be determined, are kept, so no unique content is lost.

Importing one language does not mean customers only get links in that language. The crawl stores the language versions a page declares (hreflang on the page or in the sitemap, same domain only). When a bot, Conversational Agent or AI-drafted reply cites a crawled page, the link points to the declared version in the reply language, falling back to a regional variant, then x-default, then the imported page. The search API returns these versions as sources[].alternate_urls. Pages imported earlier get them with their next re-crawl.

curl -X POST "https://api.sendseven.com/api/v1/knowledge-base/documents/crawl" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"exclude_paths": ["/en", "/fr", "/it"],
"language": "de",
"translation_handling": "skip_translations"
}'

If you keep all languages anyway, SendSeven still recognises pages that are translations of each other. When a question matches several language versions of the same page, the answer uses only the best matching one, so other relevant pages keep their place in the answer.

FAQ Entries​

FAQ entries are the curated question-and-answer pairs in the Managed FAQ folder — the answers an Assistant works from most directly. Unlike uploaded documents they follow a review lifecycle (draft → published → needs_review → archived), and only published entries are retrievable. For the concepts behind this, see the Knowledge Base for Bots guide.

List FAQ entries​

GET /api/v1/knowledge-base/faq-entries
ParameterTypeDescription
statusstringdraft, published, needs_review, or archived. Accepts a comma-separated list (e.g. draft,needs_review) for a union.
originstringmanual, auto_conversation, migrated, or correction.
folder_idstringRestrict to one KB folder.
flagged_onlybooleanOnly entries with flagged_count > 0.
review_onlybooleanThe review queue: entries needing attention (draft/needs_review, or flagged). Overrides status/flagged_only.
searchstringSubstring match on question/answer.
sort_bystringuses, quality, flags, retrieved, created_at, updated_at, or sort_order.
page / page_sizeintegerPagination (page_size max 200).

Each entry includes its status, origin, question, answer, and the usage counters times_in_sent_reply (Sent), times_retrieved (Retrieved), duplicate_count (Sources — a provenance count, not a duplicate warning), and flagged_count. rag_file_name is null when the entry is not indexed for retrieval.

{
"items": [
{
"id": "faq_a1b2c3",
"status": "published",
"origin": "auto_conversation",
"question": "What is your return policy?",
"answer": "You can return any unused item within 30 days...",
"times_in_sent_reply": 12,
"times_retrieved": 47,
"duplicate_count": 3,
"flagged_count": 0,
"rag_file_name": "faq_a1b2c3.md"
}
],
"pagination": { "page": 1, "page_size": 50, "total": 1 }
}

Create FAQ entries​

POST /api/v1/knowledge-base/faq-items
POST /api/v1/knowledge-base/faq-items/batch

Both need knowledge_base:create. Send question and answer, and optionally:

FieldDefaultDescription
folder_idthe Managed FAQ folderKnowledge Base folder for the entry
statuspublisheddraft, published, needs_review or archived. Only published entries are retrievable.
languagenoneLanguage tag, for example de
allow_duplicatefalseWhen false, an entry that nearly matches an existing one is rejected with 409 and {"duplicate_detected": true, "existing_entry_id": "...", "similarity": 0.93}

The batch endpoint takes {"items": [...]} with 1 to 50 entries in the same shape and returns {"created_count": 2, "items": [...]} (201). It is all-or-nothing: if one entry is a near-duplicate, nothing is saved and the 409 body also carries item_index, the position of that entry in your list. Published entries become retrievable shortly after the call returns.

document_id is a legacy field that appends entries to an older compiled FAQ document. Leave it out. In a batch, set it on every item or on none; a mix returns 400.

Review queue​

The review queue collects every open Knowledge Base task in one ordered list, so you can work through them one at a time: AI corrections waiting for a decision, flagged FAQ entries, entries that need review, drafts and documents that failed to process. Both endpoints are read-only and need knowledge_base:read. You act on an item with the existing endpoints listed below.

Queue summary​

GET /api/v1/knowledge-base/review-queue/summary
{
"total": 47,
"corrections": 3,
"entries": 42,
"breakdown": { "draft": 40, "needs_review": 1, "flagged": 3 },
"failed_documents": 2,
"merge_execution_enabled": false
}
FieldNotes
totalcorrections + entries + failed_documents. Use it for a badge.
correctionsCorrections that need a person: proposed, failed, and open or analyzing ones that have not moved for 60 minutes (stalled).
entriesDistinct FAQ entries that are draft or needs_review, or flagged. The same set as GET /knowledge-base/faq-entries?review_only=true.
breakdownOverlapping counts per reason. A flagged draft counts under both draft and flagged, so the parts can add up to more than entries.
failed_documentsKnowledge Base documents with status failed.
merge_execution_enabledfalse while merging duplicates is preview-only (see Find and merge duplicate entries).

GET /api/v1/knowledge-base/faq-entries/review-count (count + breakdown) still answers for older clients but is no longer part of the public API. Use the summary instead.

Queue items​

GET /api/v1/knowledge-base/review-queue?page=1&page_size=50

page_size is at most 100. The order is fixed: corrections (oldest first), flagged entries (most flags first), entries that need review, drafts (oldest first), then failed documents (oldest first). Each FAQ entry appears once, even a flagged draft. pagination.total equals summary.total, and the response also carries the current summary.

{
"items": [
{
"id": "correction:5b0c...",
"type": "correction",
"reasons": ["correction_proposed"],
"created_at": "2026-10-20T09:12:00Z",
"correction": { "id": "5b0c...", "status": "proposed", "...": "..." },
"entry": null, "entry_context": null, "document": null
},
{
"id": "faq_entry:3d9e...",
"type": "faq_entry",
"reasons": ["flagged", "draft"],
"entry": { "id": "3d9e...", "status": "draft", "question": "...", "answer": "...", "...": "..." },
"entry_context": {
"flags": [{"source": "rating", "verdict": "wrong", "reason": "outdated", "comment": "...", "question": "...", "correction_id": null, "correction_status": null, "flagged_by_name": "Jane", "created_at": "..."}],
"draft_origin": "auto_conversation",
"duplicate_of_entry_ids": []
}
},
{
"id": "failed_document:9a1f...",
"type": "failed_document",
"reasons": ["ingestion_failed"],
"document": { "id": "9a1f...", "title": "Price list 2026.pdf", "document_type": "uploaded_doc", "folder_id": "...", "last_error": "...", "source_url": null, "updated_at": "..." }
}
],
"pagination": { "page": 1, "page_size": 50, "total": 47 },
"summary": { "total": 47, "...": "..." }
}
typereasonsAct with
correctioncorrection_proposed, routed_to_skill_suggestion, no_changes_proposed, partially_applied, analysis_failed, analysis_stalledPOST /kb-corrections/{id}/approve or /reject; for analysis_failed and analysis_stalled, retry the analysis. See Feedback loop.
faq_entryflagged, needs_review, draftEdit or publish with PUT /knowledge-base/faq-items/{id}, delete it, or resolve flags.
failed_documentingestion_failed, deletion_failedingestion_failed: POST /knowledge-base/documents/{id}/retry or delete it (when document.last_error_code is set, retry returns 422: upload a fixed file). deletion_failed: delete it again (retry returns 409).

entry_context.flags lists up to 5 reasons an entry was flagged, newest first: source is rating (a team member rated an answer that used the entry) or report. question is the customer question the AI answered, null after 90 days. draft_origin is manual, auto_conversation, migrated, correction or agent_gap. duplicate_of_entry_ids lists live entries with the same question, as merge candidates.

Retry a correction analysis​

POST /api/v1/kb-corrections/{correction_id}/retry-analysis

Requires knowledge_base:manage. No request body. Re-runs the AI analysis of a correction that is failed, or open/analyzing without progress for 60 minutes (stalled). The correction goes back to open and the analysis starts again. Returns 202 with the correction; poll GET /api/v1/kb-corrections/{correction_id} until it is proposed (or applied in automatic mode).

Statusdetail.codeMeaning
202Analysis restarted.
404correction_not_foundUnknown correction.
409not_retryableThe correction is neither failed nor stalled.

Correction statuses: open (reported, not analyzed yet), analyzing, proposed (waiting for review), applied, rejected, failed (analysis or apply failed; last_error says why).

Resolve flags​

Clears an entry's flags after a reviewer has looked at it: sets flagged_count to 0 and writes an audit revision, without touching the content, status, or the corpus. Requires knowledge_base:update.

POST /api/v1/knowledge-base/faq-entries/{entry_id}/resolve-flags

Editing or re-publishing an entry clears its flags automatically, so this endpoint is only needed when you want to keep an entry as-is and just dismiss its flags.

Find and merge duplicate entries​

Duplicate clusters (live entries whose normalised question matches) are candidates for merging into one:

GET /api/v1/knowledge-base/faq-entries/duplicate-clusters
POST /api/v1/knowledge-base/faq-entries/merge
FieldTypeDefaultDescription
winner_idstring—The entry that survives the merge.
loser_idsstring[]—Entries folded into the winner, then archived.
dry_runbooleantruePreview only. Set false to execute the destructive merge.
keep_loser_questions_as_variantsbooleantrueKeep loser phrasings as retrievable alternate questions on the winner.

dry_run: true (the default) previews the merge — the combined stats, the question variants that would be kept, and the RAG files that would be removed — and changes nothing.

warning

Merge execution (dry_run: false) is gated off in production. While the server-side flag is disabled, an execute request returns 403 before anything is deleted — this is intentional, not a transient error. Until execution is enabled, use dry_run: true to preview, then consolidate by hand. Requires knowledge_base:delete.

Error Responses​

Most errors return detail as a message string. See Error Handling for the response shapes.

StatusdetailDescription
400message stringInvalid request, for example "Content is required for FAQ/manual documents"
401"Invalid or expired API token"Token is invalid or expired
403"Missing required permission(s): ..."Token lacks the required knowledge_base:* scope for this operation (see the scope table above). A 403 is also returned when merge execution is attempted while it is gated off
403{"code": "feature_disabled", "feature": ...}The Knowledge Base is not included in your plan
404"Document not found", "Folder not found", "Crawl job not found"Unknown ID, or it belongs to another workspace
409"Folder is being deleted"The target folder is being deleted
413"Content exceeds ... bytes limit."Edited document content is too large
422{"error_code": "invalid_exclude_rule", "message": ...}Invalid crawl exclude rules, language or translation handling (details)
422list of field errorsInvalid request body
429{"error": "Rate limit exceeded: ..."} (no detail wrapper)Crawl, recrawl, crawl preview or search called too often (see Rate Limits)

Next Steps​