Docs
Dashboard

Built-in modules

Knowledge base

A help centre with a navigation tree, freshness dates and helpful/not-helpful voting.

A built-in module: collections holding sections holding articles, each with a byline, a freshness date and a helpful/not-helpful tally. Nothing is configured first. Every project has these endpoints whether or not anything is written.

Like the blog, it is release-independent: ?release= is neither needed nor honoured.

Two things it has that a blog does not

Order is the answer. Every listing comes back in the order somebody dragged it into: collection, then section, then the manual order inside it. There is deliberately no sort parameter anywhere in this module. A help centre in date order is not a help centre.

Freshness. Whether an answer is still believed to be true. A help article that was correct in March and is wrong now is worse than no article, so an article records when somebody last confirmed it.

The tree

GET /v1/kb/tree

Visible collections with their sections nested inside, in reading order, with an article count on every node. One request draws a whole sidebar.

{
  "data": [
    {
      "id": "kbc_…",
      "slug": "getting-started",
      "title": "Getting started",
      "description": "The first ten minutes.",
      "image": null,
      "sections": [
        { "id": "kbs_…", "slug": "accounts", "title": "Accounts", "description": null, "article_count": 4 }
      ],
      "article_count": 9,
      "seo": { "title": "Getting started", "description": "…", "og_image": null, "canonical_url": null, "noindex": false }
    }
  ],
  "meta": { "module": "kb", "release_independent": true, "limit": 20, "offset": 0, "has_more": false, "next_offset": null }
}

Sections are nested rather than served flat with a collection_id on each. A sidebar is a tree, and regrouping a flat list to draw one is work already done for you.

Collections page; the sections inside them do not. A section only means anything as part of the collection that holds it, and splitting a node from its children would make the tree unbuildable.

Hidden collections and sections are omitted entirely: absent, not marked.

List articles

GET /v1/kb/articles

Published articles in reading order. body is omitted; fetch one article for it. Each item carries its resolved breadcrumb, byline, freshness and votes.

Parameter
limit1–100, default 20
offsetUse meta.next_offset
collectionA collection slug. Everything beneath it, sections included.
sectionA section slug.
qCase-insensitive substring of title or summary
media_urlspermanent to refuse expiring image URLs

An unknown collection or section is a 404 naming the real ones, not an empty list and never the whole knowledge base.

One article

GET /v1/kb/articles/{slug}

The whole article: rendered body, its toc, the resolved breadcrumb and byline, freshness and the vote tally.

The article shape

{
  "id": "kba_…",
  "slug": "install-the-cli",
  "title": "Install the CLI",
  "summary": "Get the CLI on your machine.",
  "collection": { "id": "kbc_…", "slug": "getting-started", "title": "Getting started" },
  "section": { "id": "kbs_…", "slug": "accounts", "title": "Accounts" },
  "authors": [{ "id": "aut_…", "slug": "ada", "name": "Ada", "bio": "…", "social": {} }],
  "tags": ["setup", "cli"],
  "published_at": "2026-09-01T14:30:35.486Z",
  "updated_at": "2026-09-01T23:14:10.566Z",
  "word_count": 420,
  "reading_time_minutes": 3,
  "freshness": { "last_reviewed_at": "2026-09-01T23:14:10.566Z", "is_stale": false, "days_until_review": 180 },
  "votes": { "helpful": 12, "not_helpful": 3, "total": 15, "score": 80 },
  "seo": { "title": "…", "description": "…", "og_image": null, "canonical_url": null, "noindex": false }
}

section can be null

An article belongs to exactly one collection and at most one section. section is null when it sits directly in its collection, which an “Overview” or “Start here” article usually does.

The key is always present, so check the value, not the key. A breadcrumb built blindly as collection › section › title renders ”› null” on the commonest kind of overview page.

freshness

Always present, never null as an object.

  • last_reviewed_at: when somebody last confirmed this is still correct.
  • is_stale: reviewed, and the review has lapsed.
  • days_until_review: negative once overdue, null when never reviewed.

A never-reviewed article is last_reviewed_at: null with is_stale: false, because “nobody has checked” is not the same claim as “this is out of date”.

Nothing is hidden on the strength of it: a stale article is still served. Show a “last reviewed” notice, or demote it in your own search. That is your call.

votes

Always present, zeroed for an article nobody has voted on. Served in listings as well as on one article, so a card can print “12 of 15 found this helpful” without a second request.

Voting

POST /v1/kb/articles/{slug}/vote

The only write on /v1/kb, and the only endpoint in ZevContent a reader’s browser calls directly.

{ "helpful": true }
{
  "data": { "helpful": 13, "not_helpful": 3, "total": 16, "score": 81, "your_vote": true },
  "meta": { "module": "kb", "release_independent": true }
}
  • helpful must be a real JSON boolean. The string "true" is refused rather than guessed at, because on the one endpoint a stranger can write to, a body that records the opposite of what it says is not acceptable.
  • One vote per visitor per article. Voting again changes the vote rather than adding one, so a tally cannot be inflated by clicking twice.
  • No address is stored. A repeat voter is recognised by a keyed digest that includes the article id, so it cannot be reversed into an address or joined across articles into a reading history.
  • It draws on the separate vote budget of 30 a minute. See Rate limits.

Notes

  • Everything except the vote is read-only and works with any key kind.
  • Only published articles are served. A draft is a 404 that says it is a draft; an archived article disappears from /v1 the moment it is archived while staying in the dashboard, so a help centre never serves a retired answer.
  • The tree is the navigation and the article list is the content: two requests, not more. GET /v1/kb/tree draws the sidebar, GET /v1/kb/articles?collection={slug} fills the page, and both page independently.
  • tags are free-form labels, browsable in the dashboard. There is no public tag filter on this module.
  • There is no ?author= filter here. kb_article_count on an author tells you how many they have written; if you need a page listing them, ask. It is an addition, not a workaround.

Updated at, Wednesday, September 2, 2026