API reference

Every FAQ & Knowledge Base endpoint — which are public, which need a session, the permission each admin route gates on, the two rate limits, and the four tables behind them.

4 min readUpdated 3 August 2026api, endpoints, permissions, tables

Two surfaces. /api/faq is reader-facing and mostly public; /api/admin/faq carries an explicit permission on every route. Both prefixes sit behind the knowledge_base licence gate — see Install.

Conventions that will catch you out

The platform pins the HTTP status at 200 and puts the real outcome in the body. Read the body, always. A client that branches on the status code will treat a refused save as a successful one.

tags and relatedFaqIds are DataTypes.JSON. That is a native JSON column on MySQL, where the driver returns a parsed array, and LONGTEXT on MariaDB, where it returns a raw string. Every endpoint normalises both to an array before answering, but a consumer reading the tables directly must handle either shape — this is the usual cause of an integration that works in development and breaks in production.

faqs is soft-deleted. Filter deletedAt if you query it yourself.

Reader endpoints

Public — no session required

List published articles. Without page or limit, returns a bare array rather than a paginated envelope
Distinct categories across published articles
One published article. Increments its view counter and embeds related articles and a helpful count
Search published articles and record the query
Public statistics: totals, popular articles, trending searches, category stats, recently answered questions

GET /api/faq accepts page, limit (capped at 100), search, category and pagePath. Supplying neither page nor limit returns every matching row as a plain array — a backward-compatibility path, and one that will hurt on a large library.

GET /api/faq/{id} answers 404 for an unpublished article, not 403. Whether an id exists is not something an anonymous caller needs to learn. Related articles are capped at ten and filtered to published ones, so the list can be shorter than what was configured. helpfulCount is counted from the feedback table at read time, not stored on the row.

POST /api/faq/search takes { query, category }. The query must be at least 2 characters; at most 50 results are returned, ordered by the article's order field. Queries longer than 3 characters are recorded to faq_searches with their result count. The route resolves no session, so the recorded userId is always null. Full behaviour in How search behaves.

GET /api/faq/stats deliberately omits the submitter's name and email from the recently-answered list. Those never leave the admin side.

Session required

Vote an article helpful or not, with an optional comment
Submit a question. Gated by the ask_faq KYC feature
Route Limit
POST /api/faq/{id}/feedback 20 per hour, per user
POST /api/faq/question 5 per 24 hours, per user

Feedback is one row per user per article, enforced by a unique index; a repeat vote updates the existing row. A repeat vote with no comment keeps the previous comment. Comments are capped at 1,000 characters.

The question body is { email, question }. The email must be valid and at most 254 characters; the question is 10–500 characters. The stored submitter name is taken from the account, not from the request.

Admin endpoints

Articles

List articles with filters and header counts
Create an article
Bulk update — same field values applied to every id
Bulk delete. Every id is validated as a UUID first
One article in full
Update one article
Soft-delete one article

GET /api/admin/faq accepts page, limit (capped at 100), search, category, status (active, inactive or all), pagePath and tags. search matches question, answer and category. tags is comma-separated and behaves as an AND — an article must carry every tag listed.

The response carries a counts object with active, inactive and total. Those are computed across every row matching your filters except the status filter, so the header counters cannot contradict the tab you are on.

Bulk update copies an explicit whitelist: question, answer, image, category, tags, status, order, pagePath, relatedFaqIds. Anything else in data is ignored rather than written.

On create, order is auto-assigned as MAX(order) + 1 for the page path — under a row lock inside a transaction, so two simultaneous creates cannot collide — only when the field is absent. An explicit 0 is honoured as position zero.

Pages and ordering

Available page paths, scanned from the Next.js app directory
Article count per page path, across the whole table
Publish or unpublish every article on a page path
Delete every article on a page path
Move or reorder one article. Rewrites the destination page's whole order sequence in a transaction

GET /api/admin/faq/page reads the filesystem. It skips _- and .-prefixed directories plus api, admin and auth, strips [locale], and filters out /admin, /auth, /api, /error, /404 and /500.

POST /api/admin/faq/reorder takes { faqId, targetId, targetPagePath }. A null targetId appends to the end; a supplied one inserts before that article.

PUT /api/admin/faq/page/status and DELETE /api/admin/faq/page match on pagePath alone. Narrowing the admin list by category, tag or search does not scope them. Re-enabling a page also republishes articles that were deliberately unpublished, because the per-article status is not remembered.

Categories and tags

Distinct categories across all articles, published or not
Distinct tags used anywhere in the table

Both gate on view.faq.category. There is no separate tag permission.

Questions

The submitted-question queue, paginated, filterable and searchable
Answer a question. Sets ANSWERED, emails the asker, notifies them in-app if the address matches an account
Set a question's state to PENDING, ANSWERED or REJECTED

The list accepts page, limit (default 25, max 100), status and search. search matches the question text, the submitter name and the email — server-side, so it reaches the whole table. The counts object counts the whole table too, so the tab badges cannot contradict the list.

Answering is committed before either notification is attempted. A mail outage cannot lose the answer; the failure is logged instead.

Feedback

All feedback, paginated, with the article embedded on each row
Feedback for one article
Write a feedback row through the admin API

The list accepts page, limit (default 25, max 100), filter (all, helpful, unhelpful, withComments) and search, which matches the comment text and the article's question.

Analytics

The dashboard report: totals, feedback split, category distribution, top searches, daily feedback, the question queue with the age of the oldest pending item, and the failing-answer set

Takes no parameters — it is not windowed, and never has been. The timeframe control on the dashboard windows one chart in the browser.

viewsOverTime is retained for compatibility and is not a view time-series: it is SUM(views) bucketed by the month each article was created. Do not plot it. See The Knowledge Base dashboard.

percentageChange in the feedback comparison is null when the previous month recorded none of that verdict. Render that as "no comparison", never as a gain.

AI

Draft an article from a topic
Rewrite an existing answer
Answer a question from the published library
Suggest tags
Suggest related articles from a 200-article shortlist
Summarise content

All six require DEEPSEEK_API_KEY and return 500 without it. Input bounds: topic 500 characters, question 2,000, content 20,000 — exceeded, the request is refused with a 400 stating both lengths before anything is billed. Details and the cost warnings are in AI-assisted authoring.

Permission keys

Key Used by
access.faq Dashboard, Manage and AI screens; the analytics endpoint
view.faq Article list and detail, page list, page counts
create.faq Article creation and all six AI endpoints
edit.faq Article update, bulk update, reorder, per-page status
delete.faq Article delete, bulk delete, per-page delete
view.faq.category Category list and tag list
access.faq.question The Questions screen and the operations queue entry
view.faq.question The question list
edit.faq.question Answering, and changing a question's state
access.faq.feedback The Feedback screen
view.faq.feedback Feedback lists
create.faq.feedback Writing feedback through the admin API

Tables

Table Holds
faqs Articles. Soft-deleted. Indexed on category, pagePath, order, status. views is a lifetime counter with no event history
faq_feedbacks One row per user per article, unique on the pair, with an optional comment
faq_questions Reader questions — name, email, question, answer, and a PENDING/ANSWERED/REJECTED state
faq_searches One row per recorded search: query, resultCount, category. userId is always null. Deleted outright rather than soft-deleted — these are analytics records

There is no WebSocket channel and no cron job in this addon. Nothing here polls, schedules or streams.