Managing the Knowledge Base
The knowledge base (KB) is where oHallo’s agents look for answers. When a customer asks a question, the planning agent searches the KB for relevant entries and uses them to compose a response. You can manage KB entries through the dashboard, but the API is the right choice when you need to import data in bulk, sync from another system, or automate content updates.
When to use the API
Section titled “When to use the API”| Scenario | Approach |
|---|---|
| Add or edit a few entries manually | Use the oHallo dashboard |
| Import hundreds of FAQ entries from a spreadsheet | Use the API with a script |
| Sync product documentation from your CMS on a schedule | Use the API in a cron job |
| Approve or reject learning loop proposals | Use the API or the dashboard |
| Keep KB entries in sync with your help centre | Use the API with webhooks |
Creating a KB entry
Section titled “Creating a KB entry”Each KB entry has a title, content, and scope. The scope controls which workspaces can access the entry.
curl -s -X POST https://api.ohallo.eu/api/kb-entries \ -H "Authorization: Bearer sf_live_v1_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "title": "Return policy for electronics", "content": "Electronics can be returned within 30 days of purchase. Items must be in original packaging with all accessories. Opened software and digital products are non-refundable. Refunds are processed within 5 business days.", "scope": "workspace", "workspaceId": "ws_abc123" }' | jq .Response:
{ "id": "kb_entry_001", "title": "Return policy for electronics", "content": "Electronics can be returned within 30 days of purchase...", "scope": "workspace", "workspaceId": "ws_abc123", "status": "active", "createdAt": "2026-03-25T10:00:00Z"}Entry fields
Section titled “Entry fields”| Field | Required | Description |
|---|---|---|
title | Yes | A short, descriptive title. Agents use this for relevance matching. |
content | Yes | The full content of the entry. This is what agents read and use in responses. |
scope | Yes | "organisation" (available to all workspaces) or "workspace" (available to one workspace). |
workspaceId | If scope is workspace | The workspace this entry belongs to. |
Writing effective content: Be specific and factual. Include concrete details like time periods, amounts, and conditions. Vague entries like “We have a flexible return policy” give agents nothing useful to work with.
Listing and searching entries
Section titled “Listing and searching entries”List all entries:
curl -s https://api.ohallo.eu/api/kb-entries \ -H "Authorization: Bearer sf_live_v1_your_key_here" | jq .Search by keyword:
curl -s "https://api.ohallo.eu/api/kb-entries?search=return+policy" \ -H "Authorization: Bearer sf_live_v1_your_key_here" | jq .Response:
{ "entries": [ { "id": "kb_entry_001", "title": "Return policy for electronics", "content": "Electronics can be returned within 30 days of purchase...", "scope": "workspace", "status": "active", "createdAt": "2026-03-25T10:00:00Z", "updatedAt": "2026-03-25T10:00:00Z" } ], "total": 1}Updating an entry
Section titled “Updating an entry”Update an existing entry by its ID. Only the fields you include are changed:
curl -s -X PATCH https://api.ohallo.eu/api/kb-entries/kb_entry_001 \ -H "Authorization: Bearer sf_live_v1_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "content": "Electronics can be returned within 30 days of purchase. Items must be in original packaging with all accessories. Opened software and digital products are non-refundable. Refunds are processed within 3 business days via the original payment method." }' | jq .Handling learning loop proposals
Section titled “Handling learning loop proposals”When oHallo’s AI resolves a conversation and detects a knowledge gap, it creates a proposal — a suggested KB entry or update. Proposals are not active until a human approves them.
List pending proposals:
curl -s "https://api.ohallo.eu/api/kb-entries?status=proposed" \ -H "Authorization: Bearer sf_live_v1_your_key_here" | jq .Response:
{ "entries": [ { "id": "kb_entry_042", "title": "Warranty coverage for refurbished items", "content": "Refurbished items carry a 12-month warranty from the date of purchase. The warranty covers manufacturing defects but does not cover cosmetic damage or battery degradation.", "scope": "workspace", "status": "proposed", "proposalSource": "learning_loop", "conversationId": "conv_xyz789", "createdAt": "2026-03-25T14:00:00Z" } ], "total": 1}Approve a proposal:
curl -s -X PATCH https://api.ohallo.eu/api/kb-entries/kb_entry_042 \ -H "Authorization: Bearer sf_live_v1_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "status": "active" }' | jq .Reject a proposal:
curl -s -X PATCH https://api.ohallo.eu/api/kb-entries/kb_entry_042 \ -H "Authorization: Bearer sf_live_v1_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "status": "rejected", "rejectionReason": "This information is incorrect -- refurbished items have a 6-month warranty, not 12 months." }' | jq .The rejection reason is fed back into the learning loop, so the AI avoids making the same mistake in future proposals.
Example: syncing FAQ data from a JSON file
Section titled “Example: syncing FAQ data from a JSON file”Here is a complete TypeScript script that reads FAQ entries from a JSON file and syncs them to oHallo’s KB. It creates new entries and updates existing ones based on a matching externalId convention in the title.
import { readFileSync } from 'fs'
const API_BASE = 'https://api.ohallo.eu'const API_KEY = process.env.OHALLO_API_KEYconst WORKSPACE_ID = process.env.OHALLO_WORKSPACE_ID
if (!API_KEY || !WORKSPACE_ID) { console.error('Set OHALLO_API_KEY and OHALLO_WORKSPACE_ID environment variables') process.exit(1)}
interface FaqEntry { id: string question: string answer: string}
const headers = { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json',}
async function fetchExistingEntries(): Promise<Array<{ id: string; title: string }>> { const res = await fetch(`${API_BASE}/api/kb-entries?limit=500`, { headers }) if (!res.ok) throw new Error(`Failed to fetch entries: ${res.status}`) const data = await res.json() return data.entries}
async function createEntry(faq: FaqEntry): Promise<void> { const res = await fetch(`${API_BASE}/api/kb-entries`, { method: 'POST', headers, body: JSON.stringify({ title: `[FAQ-${faq.id}] ${faq.question}`, content: faq.answer, scope: 'workspace', workspaceId: WORKSPACE_ID, }), }) if (!res.ok) throw new Error(`Failed to create entry FAQ-${faq.id}: ${res.status}`) console.log(`Created: FAQ-${faq.id} -- ${faq.question}`)}
async function updateEntry(entryId: string, faq: FaqEntry): Promise<void> { const res = await fetch(`${API_BASE}/api/kb-entries/${entryId}`, { method: 'PATCH', headers, body: JSON.stringify({ title: `[FAQ-${faq.id}] ${faq.question}`, content: faq.answer, }), }) if (!res.ok) throw new Error(`Failed to update entry FAQ-${faq.id}: ${res.status}`) console.log(`Updated: FAQ-${faq.id} -- ${faq.question}`)}
async function sync() { const faqs: FaqEntry[] = JSON.parse(readFileSync('faqs.json', 'utf-8')) const existing = await fetchExistingEntries()
let created = 0 let updated = 0
for (const faq of faqs) { const marker = `[FAQ-${faq.id}]` const match = existing.find((e) => e.title.startsWith(marker))
if (match) { await updateEntry(match.id, faq) updated++ } else { await createEntry(faq) created++ } }
console.log(`Sync complete: ${created} created, ${updated} updated`)}
sync().catch((err) => { console.error('Sync failed:', err.message) process.exit(1)})Example faqs.json input file:
[ { "id": "001", "question": "What are your business hours?", "answer": "Our support team is available Monday to Friday, 9:00 to 18:00 CET. We respond to emails within 4 business hours." }, { "id": "002", "question": "How do I reset my password?", "answer": "Click 'Forgot password' on the login page. Enter your email address and follow the link in the reset email. The link expires after 1 hour." }, { "id": "003", "question": "Do you ship internationally?", "answer": "We ship to all EU countries and the UK. Standard delivery takes 3-5 business days within the EU. Express delivery (1-2 business days) is available for an additional fee." }]Run the sync:
OHALLO_API_KEY=sf_live_v1_your_key_here \OHALLO_WORKSPACE_ID=ws_abc123 \npx tsx sync-faqs.tsNext steps
Section titled “Next steps”- Quickstart — if you have not set up API access yet
- Building a Custom Agent — create an agent that uses your KB entries
- What is MCP? — connect agents to your live business data