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 should still 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 API accepts slug on create and update, so when your script sends the frontmatter slug, the slug you write is the slug you get. If a live article already uses that slug, POST /api/v1/kb/articles returns 409 and writes nothing. Only a slug derived from the title (because you didn’t send one) gets a -2/-3 suffix when 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.

A 409 on re-run is a second, safer signal that the article already exists: nothing was written, so nothing is duplicated. Don’t rely on it alone, though. The lookup-then-PATCH flow is still the recommended approach, because it updates the article instead of just failing.

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 & MCP.
  • 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.

If you send tags, the API accepts either tag ids or tag slugs. An unknown tag returns a 400 and nothing is changed, so a typo in a tag fails the whole request instead of leaving a half-updated article.

Verify it worked

Re-run the script with no source changes: it should report updates (or no-ops), not create any new articles. If your script sends the frontmatter slug, a re-run that wrongly tries to POST an existing article gets a 409 and writes nothing, so treat any 409 in the output as a sign your slug lookup missed an article. The live KB should show no -2/-3 slug duplicates. Then check the pushed articles render correctly in the KB editor.

See also

Tags

How To