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:
- Listing existing articles by slug before pushing.
- If a slug matches an existing article,
PATCHit instead ofPOST. - If the slug doesn’t match anything,
POSTto 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) andknowledge: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.