Skip to content
DaneshyarDocs
Sign in
  • Introduction
  • Quickstart
  • API keys
  • Authentication
  • Apps, workspaces, resources
  • Ingestion
  • Chat and sessions
  • Citations
  • Skills
  • Conventions
  • Workspaces
  • Resources
  • Chat
  • Skills
  • Errors
  • Customer support assistant
  • Internal knowledge base
  • Documentation chatbot
  • Embedded widgetOn the roadmap: Soon
  • TypeScript SDKOn the roadmap: Soon
  1. Docs
  2. Examples
  3. Documentation chatbot

Documentation chatbot

Answer questions about your own product docs, with citations back to the page.

Answer questions about your own product documentation, with footnotes that link back to the page and position the answer came from.

Architecture

Same shape as the help centre — one shared workspace, proxied through your server — but it uses response.parts instead of citations, because a docs answer reads better with per-sentence footnotes than a list at the bottom.

  1. Upload each docs page as its own resource, titled with its slug.
  2. Ask through your own route, passing a visitor id you already have.
  3. Turn each part's source back into a URL using the title and metadata.

Setup

Keep your own slug in the resource title. It is the only field that survives into a citation, so it is what lets you rebuild a link without a second lookup.

If your slugs can change, store a mapping instead of parsing the title — a citation for a renamed page would otherwise point nowhere.

The code

Note the filter: parts that carry no source are ordinary prose the model wrote to connect the cited spans, and they should not become footnotes.

JavaScript
const BASE = "https://api.daneshyar.info/api/v1";
const KEY = process.env.DANESHYAR_API_KEY;

const WORKSPACE = "8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f";

/**
 * Answer questions about your own docs, and link each source back to the page
 * it came from. The trick is keeping your own slug in the resource title, so a
 * citation can be turned back into a URL without a second lookup.
 */
export async function answerDocsQuestion({ visitorId, question }) {
  const res = await fetch(`${BASE}/workspaces/${WORKSPACE}/chat/`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      external_user_id: visitorId,
      message: question,
    }),
  });

  const { data } = await res.json();
  const reply = data.assistant_message;

  // response.parts attaches each source to the span it supports — use it when
  // you want footnotes rather than a flat list under the answer.
  const footnotes = reply.response.parts
    .filter((part) => part.source)
    .map((part) => ({
      text: part.text,
      title: part.source.resource_title,
      // metadata carries the position: page for a PDF, start_time for audio.
      page: part.source.metadata?.page ?? null,
      href: `/docs/${slugFromTitle(part.source.resource_title)}`,
    }));

  return { answer: reply.content, footnotes };
}

function slugFromTitle(title) {
  return title.replace(/\.[^.]+$/, "").toLowerCase().replace(/\s+/g, "-");
}

What you get

Readers get an answer where each claim carries a link to the exact page and position behind it — which is the difference between a chatbot people trust and one they double-check elsewhere.

PreviousInternal knowledge baseNextEmbedded widget

On this page

  • Architecture
  • Setup
  • The code
  • What you get

Every example on this site runs against the live API. If one doesn't, tell us — that's a bug in the docs.

ServicesAboutTalk to us