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).
Two-tier matching
Matching runs against the article’s stored body and is tried in two tiers:
- Text nodes only (
mode: "text", preferred):oldTextis 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, sooldText: "A & B"matches a storedA & B. - Raw markup fallback (
mode: "html"): only used when tier 1 finds nothing.oldTextis 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.
Body ParametersJSON
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.
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 & 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.
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.
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.
Edit an article body (find/replace)
curl https://do.featurebase.app/v2/help_center/articles/$ID/edit_body \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $FEATUREBASE_API_KEY" \
-d '{
"newText": "New Product Name",
"oldText": "Old Product Name",
"dryRun": true,
"expectedReplacements": 3,
"locale": "en",
"target": "draft"
}'{
"id": "1234567",
"dryRun": false,
"locale": "en",
"matches": 1,
"mode": "text",
"object": "article.body_edit",
"previews": [
{
"after": "…see the New Product Name dashboard for…",
"before": "…see the Old Product Name dashboard for…"
}
],
"replaced": 1,
"target": "draft"
}Returns Examples
{
"id": "1234567",
"dryRun": false,
"locale": "en",
"matches": 1,
"mode": "text",
"object": "article.body_edit",
"previews": [
{
"after": "…see the New Product Name dashboard for…",
"before": "…see the Old Product Name dashboard for…"
}
],
"replaced": 1,
"target": "draft"
}