Build an Idempotent KB Push Script

What a doc-as-code push script must do: slug-based lookup, PATCH-or-POST upserts, categories before articles, and the frontmatter contract.

7 min read

Build an Idempotent KB Push Script

To push articles to the KB safely and repeatably, your script must supply the idempotency itself: list existing articles by slug first, PATCH the ones that already exist, and POST only the ones that don’t. The v1 API doesn’t enforce this on its own — POST /api/v1/kb/articles always creates a new article and appends -1, -2 to the slug if it collides.

This article covers the upsert logic, the requirements checklist, and the frontmatter contract that a doc-as-code push script needs. For when to use the pattern at all, see Doc-as-code with the KB API.

The upsert logic

Idempotency in doc-as-code means “running the script twice with the same inputs produces the same outputs.” The script achieves it by:

  1. Listing existing articles by slug before pushing.
  2. If a slug matches an existing article, PATCH it instead of POST.
  3. If the slug doesn’t match anything, POST to create.

This pattern is robust against partial runs — a script that crashes halfway through can re-run, find the articles it already created, and update them on the second pass instead of creating duplicates.

See What happens when I push the same article twice? for the exact behavior.

What the script needs

  • Authentication — A tenant API key with knowledge:read (for listing) and knowledge:write (for create/update). Generate in Settings → API Keys.
  • Frontmatter parser — Any Markdown frontmatter library. The script reads title, slug, summary, category, etc. from the frontmatter and sends them to the API.
  • Categories upserted before articles — Articles fail to create without an existing category. The script must ensure categories first, collect IDs, then push articles.
  • Slug-based lookup — List articles, build a slug → ID map, decide create vs. update per article.
  • Markdown-to-HTML conversion — The API accepts Markdown in content; the renderer handles the HTML conversion server-side. No client-side conversion required.

The Atender vault uses a 600-line Python script that does all of this in one pass. It’s a useful reference but the same shape is easy to build in any language.

Frontmatter as metadata contract

The piece that makes doc-as-code workable is the frontmatter contract. Every article carries metadata at the top:

---
title: "How to update your payment method"
slug: "update-payment-method"
category: "Billing"
type: "how-to"
summary: "Open Account, go to Billing, click Change."
keywords: [payment, credit card, billing]
ux_path: "Account → Billing → Payment methods"
roles: [admin]
status: "published"
---

The push script reads this and translates it into the API’s payload shape. The script becomes a thin transformation layer between the human-friendly frontmatter and the API’s JSON.

Verify it worked

Re-run the script with no source changes: it should report updates (or no-ops), not create any new articles, and the live KB should show no -1/-2 slug duplicates. Then check the pushed articles render correctly in the KB editor.

See also

Tags

How To