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. Internal knowledge base

Internal knowledge base

One workspace per team, answering staff questions from internal policy documents.

Staff ask questions of the policies, handbooks and contracts their team owns — and cannot reach another team's.

Architecture

One workspace per team. Because retrieval never crosses a workspace, the access rule is enforced by the boundary itself rather than by a filter you have to remember to apply.

  1. Map each team to a workspace id in your own configuration.
  2. Resolve the signed-in employee's team before choosing the workspace.
  3. Pass their employee id as external_user_id, so history is per person.

Setup

Create one workspace per team and upload that team's documents into it. Keep the mapping in your own config rather than deriving it from workspace titles, which are editable.

Someone who moves teams should get the new team's workspace on their next question. Their old history stays under the old workspace — sessions belong to a workspace, not to a person.

The code

The team lookup happens before any request, so an unknown team fails fast instead of asking the wrong corpus:

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

/**
 * One workspace per team. Staff in Engineering never retrieve HR's documents,
 * because retrieval cannot cross a workspace boundary — you don't filter, the
 * boundary does it.
 */
const TEAM_WORKSPACES = {
  engineering: "8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f",
  people: "1d4c7b90-3e58-4a21-bf06-9c2e5d8a7f43",
};

export async function askAsEmployee({ employeeId, team, question }) {
  const workspaceId = TEAM_WORKSPACES[team];
  if (!workspaceId) throw new Error(`Unknown team: ${team}`);

  const res = await fetch(`${BASE}/workspaces/${workspaceId}/chat/`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      // A stable employee id — not an email, which changes on a name change.
      external_user_id: employeeId,
      message: question,
    }),
  });

  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.message ?? body.detail ?? `HTTP ${res.status}`);
  }

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

/** Their own history, and only theirs. */
export async function historyFor({ employeeId, team }) {
  const url = new URL(`${BASE}/workspaces/${TEAM_WORKSPACES[team]}/chat/sessions/`);
  url.searchParams.set("external_user_id", employeeId);

  const res = await fetch(url, { headers: { "X-API-Key": KEY } });
  const { data } = await res.json();
  return data.results;
}

What you get

Every employee gets answers from exactly the documents their team is allowed to see, with their own conversation history — and adding a team is one workspace and one config line.

PreviousCustomer support assistantNextDocumentation chatbot

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