TurfAITurfAI User Guide
Modules

Chatbot

A deployable, embeddable "chat with your documents" surface.

What it is

The Chatbot turns the Knowledge Base into a deployable, embeddable "chat with your documents" experience. It is composed entirely from existing workflow blocks — ingestion (documents and URLs) plus a RAG query path — so customers customise it by editing the workflow. It ships an authenticated chat page, a sidebar chat, multi-tenant chatbot management, and an embeddable widget that runs on any external website.

When to use it

Use it to expose a knowledge base to end users — internal staff (authenticated /chat) or external website visitors (embeddable widget with a per-chatbot API key). For backend programmatic Q&A from your own services, call the public chat API directly. Each chatbot queries its owner's documents (optionally scoped to one collection), so one tenant's content never leaks into another's answers.

How it works

Process flow — the three composing workflow templates (chatbot-ingestion, chatbot-url-ingestion, chatbot-query):

Call flow — a public widget query and the checks DMS runs before it ever reaches RAG:

Sub-features

  • Authenticated chat (/chat) — three-panel page: sessions, message thread, sources.
  • Sidebar chat — floating button on every authenticated page (ChatWindow).
  • Chatbot management — named chatbots, per-chatbot API key (cb_ prefix), branding, welcome message, allowed origins, rate limit, active toggle.
  • Embeddable widget — Shadow DOM isolation, localStorage sessions, source-citation badges, "Powered by TurfAI" footer.
  • KB Assembly view — table of all sources (documents + URLs) with status and reindex.
  • Settings (5 tabs) — General, Branding, Security, Embed, Knowledge Base.
  • Auto-create — instantiating a chatbot template creates a Chatbot record + endpoint.

Configuration parameters

Reconciled against dms/src/api/chatbot/content-types/chatbot/schema.json.

ParameterTypeDefaultDescription
namestring (required)Display name
slugstring (unique, required)URL-safe id used in the embed code + public API. Lowercase alphanumeric + hyphens
activebooleantrueEnable / disable; an inactive chatbot is invisible to config and chat
welcome_messagetext"Hi! How can I help you today?"Shown when the widget opens
brandingJSON{ "primary_color": "#2563eb" }Holds primary_color and optional logo_url (there are no standalone primary_color / logo_url columns)
allowed_originsJSON array[] (empty = all origins)CORS allowlist of domains permitted to embed the widget
rate_limitinteger60 (min 1, max 1000)Requests per minute, per chatbot
query_countinteger0Total queries processed (incremented per successful chat)
last_query_atdatetimeTimestamp of the most recent query
api_key_rotated_atdatetimeset on createLast key rotation; NULL on legacy rows surfaces as "never rotated"
transcripts_enabledbooleantrueWhen true, each query+answer is persisted for owner review; set false for GDPR-sensitive deployments
collectionrelation → CollectionOptional scope: queries only search this collection's indexed documents
ownerrelation → User(caller)Whose documents are queried and who controls settings

The API key is never stored. Only api_key_hash (SHA-256, marked private) lives on the row. The plaintext cb_… key is returned once — at create and at regenerate-key — in the response body and shown in a one-time-reveal dialog. There is no api_key column, no rate_limit_per_minute, no knowledge_base_url, and no last_queried_at; scoping is done via the collection relation, and the rate field is rate_limit / the timestamp is last_query_at.

Tips & best practices

  • Knowledge-base hygiene: keep one chatbot pointed at one collection so unrelated documents can't leak into answers. Confirm sources are Indexed in KB Assembly before going live — a chatbot with a collection but no indexed docs returns "No indexed documents found…" by design rather than hallucinating.
  • Branding: set branding.primary_color to your brand colour and branding.logo_url to a hosted logo; both are served by the public config endpoint and applied by the widget before the first message.
  • CORS / allowed_origins: lock the widget to the exact domains that should embed it (e.g. ["https://www.acme.com"]). Leave it empty only for local testing — an empty array allows every origin.
  • Rate limiting: the default 60/min is per chatbot, enforced in-memory. Raise it for high-traffic sites (max 1000), but keep it tight on public widgets to blunt scraping and abuse. Clients should honour the retryAfter: 60 hint on a 429.
  • Key rotation: rotate via regenerate-key on a schedule and whenever a key may have leaked — rotation invalidates the old key immediately and stamps api_key_rotated_at. A "never rotated" badge (NULL timestamp) is your cue to rotate.

Concrete examples

1. Embed widget — declarative

<script src="https://app.turfai.com/widget.js"
        data-chatbot="my-kb"
        data-api="https://api.turfai.com"></script>

Programmatic init (when you need to mount it yourself):

<script src="https://app.turfai.com/widget.js"></script>
<script>
  window.TurfAIChat.init({
    chatbotSlug: 'my-kb',
    apiUrl: 'https://api.turfai.com'
  });
</script>

The widget mounts a floating button (bottom-right), opens a Shadow-DOM-isolated chat overlay, pulls branding from GET /api/chatbots/my-kb/config, and persists the session in localStorage keyed by slug.

2. Public chat request + response with sources

POST /api/chatbots/my-kb/chat
Headers: { "X-Chatbot-Key": "cb_abc123...", "Content-Type": "application/json" }

{ "query": "What is the remote work policy?", "session_id": "optional-for-multi-turn" }
{
  "answer": "Based on the company handbook, remote work is allowed up to 3 days a week [1].",
  "sources": [
    {
      "document_id": 42,
      "document_title": "Company Handbook 2025",
      "page_number": 15,
      "similarity_score": 0.92,
      "signed_url": "https://storage.googleapis.com/...?X-Goog-Expires=3600...",
      "file_url": "gs://bucket/documents/handbook.pdf"
    }
  ],
  "confidence": 0.82,
  "session_id": "sess_abc123"
}

Reuse the returned session_id on the next call to keep multi-turn context.

3. The three composing workflows (seed templates, all built from existing task types):

TemplateFlowTask types
chatbot-ingestionWebhook → Decision (OCR?) → OCR → RAG Enable → Emailwebhook_trigger, decision_task, ocr, rag_enable_task, email_send_task
chatbot-url-ingestionWebhook → Scrape → LLM cleanup → RAG Enable → Emailwebhook_trigger, metadata_scraping, llm_task, rag_enable_task, email_send_task
chatbot-queryWebhook → RAG Query → LLM format → REST responsewebhook_trigger, rag_query_task, llm_task, rest_api_task

To customise behaviour (classification routing, email escalation, multilingual support), edit these workflows rather than the chatbot record.

Limitations

  • The public chat path is RAG-only; Data Shield does not tokenise it in v0.5 — do not expect PII masking on public chat answers.
  • Queries run as the chatbot owner's context — the owner's documents (optionally one collection) are searched, never the visitor's.
  • Rate limiting is in-memory per DMS instance, so limits are approximate behind multiple replicas.
  • A chatbot whose api_key_hash is NULL (legacy row) cannot authenticate — the operator must regenerate the key.

Troubleshooting

SymptomLikely causeFix
Widget chat fails / browser blocks the requestHost origin not in allowed_originsAdd the exact origin (scheme + host) to allowed_origins, or empty the array for local testing
429 Rate limit exceeded (retryAfter: 60)More than rate_limit requests/min for this chatbotBack off for 60s; raise rate_limit (max 1000) if legitimately high-traffic
Answer is "No indexed documents found…"Assigned collection has no docs with rag_enabled + completed statusIndex docs (KB Assembly → reindex) and confirm status is Indexed before querying
401 Invalid or missing API keyWrong / rotated key, or api_key_hash is NULL on a legacy rowSend the current cb_… key; if the row was never rotated, regenerate the key
404 Chatbot not found or inactiveWrong slug, or active: falseCheck the slug; toggle active: true in settings
Widget never appearswidget.js not loaded, or data-chatbot slug missing/typoVerify the script URL resolves; confirm data-chatbot matches an active slug; check the browser console

How to test

End-to-end happy path:

  1. Create a chatbotPOST /api/chatbots with name + slug (or instantiate a chatbot template, which auto-creates the record). Capture the one-time cb_… key from the response.
  2. Index a document — ingest a PDF via the ingestion webhook; poll until it shows Indexed in KB Assembly (chunk_count > 0).
  3. Public chat via APIcurl -H "X-Chatbot-Key: <key>" -H "Content-Type: application/json" -d '{"query":"<a question answered by the doc>"}' /api/chatbots/<slug>/chat; confirm a non-empty answer and a sources[] entry with the right document_title, page_number, similarity_score, and a working signed_url.
  4. Embed the widget — drop the <script src=".../widget.js" data-chatbot="<slug>" data-api="..."> snippet on a test HTML page served from an allowed origin; confirm the floating button opens the chat.
  5. Verify branding + sources — the widget header shows your primary_color/logo_url and welcome_message (from the public config endpoint), and assistant messages render source-citation badges.
  6. Negative checks — open the widget from an origin not in allowed_origins (request blocked); fire more than rate_limit requests in a minute (429 with retryAfter).
  • Knowledge Base / RAG — the indexing + query layer this surface is built on.
  • RAG concept — chunking, embeddings, and retrieval explained.
  • Data Shield — note: public chat is NOT tokenised by Data Shield in v0.5.
  • Coming soon: Data Shield tokenisation of the chat path; transcript review UI (transcripts_enabled).

Dependencies

  • DMS chatbot controller/service — CRUD, public chat, config, rate limiting, key hashing.
  • RAG query service — answers and citations (called with the owner's JWT).
  • Ingestion workflows — OCR, web scrape, LLM cleanup, RAG Enable, email (see Node catalog).
  • GCS — source documents and 1-hour signed URLs on citations.

APIs used

MethodPathAuthPurpose
GET / POST/api/chatbotsJWTList / create chatbots (create returns the one-time api_key)
GET / PUT / DELETE/api/chatbots/:idJWTGet / update / delete a chatbot
POST/api/chatbots/:id/regenerate-keyJWTRotate API key (returns new one-time api_key)
GET/api/chatbots/:slug/confignonePublic branding / welcome message
POST/api/chatbots/:slug/chatX-Chatbot-KeyPublic chat query
OPTIONS/api/chatbots/:slug/chatnoneCORS preflight

See the synced feature reference for the full chatbot surface.

On this page