Skip to main content
Help Centers

Edit an article body (find/replace)

Surgically edit an article's body with a find/replace instruction, without re-uploading the whole HTML. Designed for AI agents doing high-precision, byte-stable edits (e.g. renaming a product across many articles).

POST
/v2/help_center/articles/{id}/edit_body

Two-tier matching

Matching runs against the article's stored body and is tried in two tiers:

  1. Text nodes only (mode: "text", preferred): oldText is matched only inside visible text — markup, tags, and attributes are never searched or altered, so the result is byte-identical everywhere outside the replaced spans. Matching is entity-aware, so oldText: "A & B" matches a stored A & B.
  2. Raw markup fallback (mode: "html"): only used when tier 1 finds nothing. oldText is matched as a literal substring over the raw body string (markup included), so you can swap an attribute value or tag. Because this can introduce markup, the full result is re-run through the XSS sanitizer before it is stored.

Do not include <img> tags in oldText — their stored src differs from the API representation and will not match.

Uniqueness rule

By default the edit requires oldText to match exactly once and returns 400 if it is ambiguous. To edit multiple occurrences, either set replaceAll: true or assert the exact count with expectedReplacements (which takes precedence over replaceAll).

Dry-run workflow

Set dryRun: true to preview: the response reports the match count, the matching mode, and previews (each changed span framed by surrounding context) without writing. Recommended flow for bulk edits: dry-run across the target articles first, confirm the counts and previews, then re-issue each call with dryRun: false.

Target

target: "draft" (default) edits the unpublished draft. target: "live" edits the published content directly — this records a history snapshot and purges the public edge cache, mirroring a publish.

Retry semantics

Calls are not idempotent — after a timeout, re-read the article (or dry-run) before retrying; with the default unique-match mode a duplicate retry fails safely unless newText still contains oldText.

Version Availability

This endpoint is only available in API version 2026-01-01.nova and newer.

Authorizationstringheaderrequired

API key as Bearer token. Use: Authorization: Bearer sk_...

idstringrequired

Article unique identifier

Required string length: 1 - 16
Pattern: ^[a-zA-Z0-9]+$
Example: 1234567
Featurebase-Versionenum<string>header

API version for this request. Defaults to your organization's configured API version if not specified.

Available options: 2026-08-19.orbit, 2026-01-01.nova, 2025-12-12.clover
Example: 2026-08-19.orbit

Body

application/json
dryRunbooleandefault:false

Preview the edit without writing. Returns the match count, matching mode, and previews (changed spans with surrounding context) so an agent can verify the edit before committing. Recommended workflow: dry-run across a set of articles first, confirm the counts and previews, then re-issue each call with dryRun: false.

Example: true
expectedReplacementsinteger

Assert the exact number of matches. When set, the edit replaces all matches only if the count is exactly this value, otherwise it returns 400. Takes precedence over replaceAll. Use this to make bulk edits safe: dry-run first to learn the count, then commit with the expected number.

Required range: 1 <= x
Example: 3
localeenum<string>

Locale of the body to edit. Defaults to the help center's default locale.

Available options: ar, am, fa, ht, lo, my, pa, ps, so, tl, ur, he, ta, te, mr, gu, kn, ml, ha, jv, yo, ig, uz, ne, sd, az, si, km, kk, bn, bs, pt-BR, bg, ca, hr, cs, da, nl, en, et, fi, fr, de, el, hi, hu, id, it, ja, ko, lv, lt, ms, mn, nb, pl, pt, ro, ru, sr, zh-CN, sk, sl, es, sw, sv, th, zh-TW, tr, uk, vi
Example: en
newTextstringrequired

Replacement text. Keep this to a fragment, not a whole document (max 10000 chars). May be an empty string to delete the matched fragment. When the match came from tier 2 (raw markup), the full resulting body is re-run through the XSS sanitizer, so any dangerous markup in newText is stripped. Images inserted via edit_body are not uploaded to Featurebase storage — use articles.update with formatter 'raw' for image changes.

Maximum string length: 10000
Example: New Product Name
oldTextstringrequired

Exact fragment to find in the stored article body. Keep this to a fragment, not a whole document (max 10000 chars). Tier 1 matches only inside TEXT nodes (markup, tags, and attributes are never searched or altered) and is entity-aware, so oldText: "A & B" matches a stored A &amp; B. If tier 1 finds nothing, tier 2 falls back to a literal substring match over the raw body string (markup included). Do NOT include <img> tags — their stored src differs from the API representation and will not match.

Required string length: 1 - 10000
Example: Old Product Name
replaceAllbooleandefault:false

Replace every match. When omitted (or false) and expectedReplacements is not set, the edit requires exactly one match and returns 400 if oldText is ambiguous (found more than once). Provide more surrounding context in oldText to make a unique match, or set this to true.

Example: false
targetenum<string>default:draft

Which body to edit: the unpublished draft (default) or the published live content. Editing live records a history snapshot and purges the public edge cache, mirroring a publish.

Available options: draft, live
Example: draft

Response

application/json

Success

dryRunbooleanrequired

Whether this was a preview (no write performed)

Example: false
idstringrequired

The edited article id

Example: 1234567
localestringrequired

Locale that was edited

Example: en
matchesintegerrequired

Number of matches found for oldText

Example: 1
modeenum<string>required

Which matching tier applied. text = text-node-only, byte-stable (markup untouched). html = raw-markup fallback (result re-sanitized). Tier 1 (text) is always attempted first.

Available options: text, html
Example: text
objectenum<string>required

Object type identifier

Available options: article.body_edit
Example: article.body_edit
previewsobject[]required

Up to 20 before/after windows, one per replaced match, for verification. Preview strings are raw untrusted text (tier-2 previews show pre-sanitization content) — never render them as HTML.

replacedintegerrequired

Number of matches actually replaced (0 when dryRun is true)

Example: 1
targetenum<string>required

Which body was edited

Available options: draft, live
Example: draft