Guides
Rewrite a blog post end-to-end
From draft to published post: voice, baseline detection, rewrite, fidelity handling and review.
This guide builds the pipeline most content teams end up with: a draft comes in, gets rewritten in the author's voice with every fact locked, and lands in front of an editor with a diff and a before/after detection score. Code is TypeScript on Node 20+; the same calls work from any language.
The pipeline
Create a voice once per author
Build it from the author's published posts and store the id with the author record.
Rewrite with strict fidelity
Use the
blogpreset, protect anything legal or commercial withpreserve, and ask for a diff.Branch on the outcome
Pass → send to the editor.
fidelity_failed→ retry once, then hand the original to a person.Review and publish
Show the editor the diff, the fidelity checks and the before/after scores.
1. Create the author's voice
Collect 5–10 posts the author wrote without AI help. Pass the raw text.
const res = await fetch("https://api.intactvoice.com/v1/voices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Dana — company blog",
samples: posts.map((p) => p.text), // 2–20 samples, ≥ 1,200 words total
platforms: ["blog"],
}),
});
const voice = await res.json();
await db.authors.update(author.id, { voice_id: voice.id }); // e.g. "voice_8f2k1x"2. Rewrite the draft
async function rewriteDraft(draft: string, voiceId: string) {
const res = await fetch("https://api.intactvoice.com/v1/rewrite", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
text: draft,
voice_id: voiceId,
platform: "blog",
strength: "standard",
fidelity: "strict",
preserve: ["Terms apply.", "Pro plan"],
return_diff: true,
}),
});
return { status: res.status, body: await res.json() };
}3. Handle the outcome
A strict rewrite has three outcomes worth handling explicitly. Everything else is a normal error (see Errors).
let result = await rewriteDraft(draft, author.voice_id);
if (result.status === 422 && result.body.error?.code === "fidelity_failed") {
// A fact moved (e.g. "14 days" -> "14 weeks"). Not billed. One retry usually fixes it.
result = await rewriteDraft(draft, author.voice_id);
}
if (result.status === 200) {
await review.create({
original: draft,
rewritten: result.body.text,
diff: result.body.diff,
checks: result.body.fidelity.checks,
before: result.body.signals.source.ai_likelihood,
after: result.body.signals.output.ai_likelihood,
});
} else {
// Still failing: hand the original to an editor with the changed facts attached.
await review.escalate({ original: draft, reason: result.body.error });
}Warning:
Don't fall back tofidelity: "standard" automatically. It returns text with changed facts marked as warnings, which is only safe when a person reads every warning.4. Review the diff
With return_diff: true the response includes token-level operations you can render as tracked changes:
[
{
"op": "replace",
"from": "Many businesses struggle to create useful blog posts because",
"to": "Most blog posts go wrong before anyone writes a word:"
},
{
"op": "equal",
"text": " the writer "
},
{
"op": "replace",
"from": "receives only a topic.",
"to": "gets a topic and nothing else."
}
]Show reviewers the fidelity.checks list next to the diff. Seeing “14 days ✓” is faster than re-reading the policy paragraph.
Content that tends to be flagged
Rewrites rebuild a draft from its facts with a new plan, which is what most detectors key on. Some content still tends to be flagged whatever we do, because people rarely write it that way: the format itself reads as generated.
- Schedules and spec lists written as rows of
Label: valuelines (class timetables, feature grids, pricing tiers). - Formulaic advice topics AI writes constantly, such as "how I price my freelance work" or "my checklist for buying a used car". The set of facts itself is familiar.
- Templated social formats: emoji or hashtag posts, numbered threads, and closing morals ("six months in and I'm glad I did it").
- Very short texts under about 150 words, where there is too little to rebuild.
Each rewrite returns structure.status: strong (rebuilt with a new plan), weak (much of the draft's order survived, or the format is templated) or not_replanned. Weak results come with a note. Detector results vary by tool and change over time; treat scores as a signal, not a guarantee.
5. Read the before/after scores
signals.source and signals.output are Detect API scores for the draft and the rewrite. They're a quick check that the rewrite moved away from stock AI patterns, not a promise about any third-party detector.
Note:
For a writer whose own style scores high on generic detectors, compare against their baseline instead. See Calibrating detection to a writer.