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

  1. Create a voice once per author

    Build it from the author's published posts and store the id with the author record.

  2. Rewrite with strict fidelity

    Use the blog preset, protect anything legal or commercial with preserve, and ask for a diff.

  3. Branch on the outcome

    Pass → send to the editor. fidelity_failed → retry once, then hand the original to a person.

  4. 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() };
}

Tip:

Drafts over 5,000 words should use async: true. See Async jobs & webhooks.

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 to fidelity: "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: value lines (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.