@faqapp/core
v1.3.0
Published
Official TypeScript SDK for TheFAQApp — manage and deliver FAQ content via the v1 API
Maintainers
Readme
@faqapp/core
Official TypeScript SDK for TheFAQApp — manage and deliver FAQ content via the REST API.
Install
npm install @faqapp/core
# or
pnpm add @faqapp/coreQuick Start
import { FAQClient } from "@faqapp/core";
const faq = new FAQClient({
apiKey: "your-api-key",
organizationSlug: "your-org",
});
// List questions (paginated)
const page = await faq.questions.list();
console.log(page.data); // Question[]
console.log(page.pagination); // { page, limit, total, pages }
// Get a single question by slug
const { data: question } = await faq.questions.get("how-to-reset");
// Search
const { data: results } = await faq.search.query("how do I...");
// Auto-paginate across all pages
for await (const q of faq.questions.list().iter()) {
console.log(q.question);
}API Reference
Questions
faq.questions.list(params?) // List questions (paginated)
faq.questions.get(slug) // Get question by slug
faq.questions.create(data) // Create a question
faq.questions.update(slug, data) // Update a question
faq.questions.delete(slug) // Delete a question
faq.questions.bulk({ questions }) // Bulk-create questions
faq.questions.export(params?) // Export all questions
faq.questions.import(data) // Import questions (supports dry-run)
faq.questions.revisions(slug) // List a question's revisions
faq.questions.restoreRevision(slug, id) // Restore a prior revisionCategories
faq.categories.list(params?) // List categories (paginated)
faq.categories.get(slug) // Get category by slug
faq.categories.create(data) // Create a category
faq.categories.update(slug, data) // Update a category
faq.categories.delete(slug) // Delete a category
faq.categories.merge(params) // Merge one category into another
faq.categories.reorder(ids) // Reorder categories
faq.categories.export(params?) // Export all categories
faq.categories.runAudit() // Run a category audit
faq.categories.audit() // Read the latest audit resultTranslations
faq.translations.list("questions", slug) // List translations
faq.translations.create("questions", slug, "nl", { question, answer }) // Create translation
faq.translations.upsert("questions", slug, "nl", { question, answer }) // Upsert translation
faq.translations.delete("questions", slug, "nl") // Delete translation
faq.translations.aiTranslate("questions", slug, { targetLanguages: ["nl", "de"] })Languages
faq.languages.get() // Read the organization's language settings
faq.languages.update(params) // Update default/enabled languagesSearch
faq.search.query("search term") // Simple string search
faq.search.query({ q: "billing", limit: 5 }) // Search with optionsPublished answers
const { data } = await faq.answers.ask({ question: "What is your refund policy?", lang: "en" });The API key is a server-side secret. Keep
FAQClientout of browser or client-rendered code; callfaq.answers.askfrom your server or API routes only.
POST /api/v1/:organizationSlug/answers requires a read-scoped API key and uses the normal request quota (one request; standard POST quota enforcement). It retrieves published content only, without embeddings, LLM synthesis, or inference cost. Questions must be 1–2000 characters after trimming; lang is optional (2–16 characters).
data is a FaqAnswer: either { status: "answered", answer, sources } or { status: "not_covered", answer: null, sources: [] }. Each source has the original question id, question, and literal answer. Answers are returned unchanged, including any stored markup; render that markup only through your usual sanitizer.
Launch matching is conservative lexical retrieval, not semantic or generative answering. Normalized exact questions win. English filler words and simple plurals also allow close paraphrases such as “what comes with the free plan” matching “What is included in the free plan?”. Non-exact matches require at least two meaningful terms, a distinguishing term beyond generic words such as “plan”, every query term in the approved question, and at least 80% coverage of that question's meaningful terms. Unknown substantive terms, negation, equally ranked candidates, conflicting answers, an empty corpus, or a truncated candidate pool return not_covered. An explicit language searches only published translations in that language; it never silently substitutes another language.
For an already-approved local corpus, the same pure matcher is available without an API request:
import { selectFaqAnswer, type FaqAnswerSource } from "@faqapp/core";
const entries: FaqAnswerSource[] = [{ id: "q1", question: "Refund policy?", answer: "Within 30 days." }];
const answer = selectFaqAnswer("Refund policy?", entries);The local helper does not check authorization or publication state: callers must supply only entries they are allowed to show. FaqEntry remains the existing aggregate-document type; use FaqAnswerSource for cited answers.
AI
faq.ai.generateFromUrl(params) // Generate FAQs from a URL
faq.ai.generateFromTopic(params) // Generate FAQs from a topic
faq.ai.analyzeWebsite(params) // Analyze a website for FAQ opportunities
faq.ai.getConfig() // Read AI provider configuration
faq.ai.updateConfig(params) // Update AI provider configuration
faq.ai.usage() // Read AI usage for the current periodIntake
faq.intake.start(params) // Start an anonymous URL-to-FAQ intake
faq.intake.get(id) // Read an intake's status/result
faq.intake.claim(id, params?) // Claim an intake into the organizationPlan
faq.plan.limits() // Read plan limits and current usage
faq.plan.downgradeCleanup(params?) // Housekeeping for a plan downgradeWebhooks
faq.webhooks.list() // List webhooks
faq.webhooks.get(id) // Get webhook by ID
faq.webhooks.create({ url, events }) // Create webhook
faq.webhooks.update(id, data) // Update webhook
faq.webhooks.delete(id) // Delete webhook
faq.webhooks.test(id) // Send test event
faq.webhooks.deliveries(id) // List deliveries
faq.webhooks.replayDelivery(webhookId, deliveryId) // Replay deliveryAPI Keys
faq.apiKeys.list() // List API keys
faq.apiKeys.create({ name, scopes }) // Create API key
faq.apiKeys.revoke(id) // Revoke API keyOrganization
faq.organization.get() // Get organization info
faq.organization.update(params) // Update organization infoPublished FAQs
faq.faqs.list(params?) // Read the published-FAQs aggregate documentPagination
List methods return a PagePromise that resolves to a Page with built-in pagination:
const page = await faq.questions.list({ limit: 10 });
console.log(page.data); // Question[]
console.log(page.pagination); // { page, limit, total, pages }
console.log(page.hasNextPage());
// Iterate item-by-item across all pages
for await (const question of faq.questions.list().iter()) {
console.log(question.question);
}
// Iterate page-by-page
for await (const p of faq.questions.list().iterPages()) {
console.log(p.data.length, "questions on page", p.pagination.page);
}Error Handling
import { FAQNotFoundError, FAQValidationError, FAQRateLimitError } from "@faqapp/core";
try {
await faq.questions.get("nonexistent");
} catch (err) {
if (err instanceof FAQNotFoundError) {
console.log("Not found:", err.message);
} else if (err instanceof FAQValidationError) {
console.log("Validation errors:", err.errors);
} else if (err instanceof FAQRateLimitError) {
console.log("Rate limited, retry after:", err.retryAfter);
}
}For React
Use @faqapp/react for React hooks and SSR helpers. It works in any React
renderer — TanStack Start, Next.js, Vite, Remix:
npm install @faqapp/core @faqapp/reactLicense
MIT
