Skip to content
The July 2026 results are in: WriteHuman takes #1, Undetectable AI moves up to #2.Read the full analysis →
HumanizerBench

Methodology

HumanizerBench is operated by WriteHuman (writehuman.ai). We disclose this openly. The benchmark is fully reproducible: every input, every humanized output, every detector verdict, and the scoring algorithm itself are published per cycle in our public repository. Read our fairness policy or why we built this.

Sample selection

Each cycle tests humanizers against writing prompts spanning six categories:

The full template set, with target word counts and the placeholder tokens each contains, is published per cycle as templates.json. Source-LLM outputs (the texts each humanizer is asked to rewrite) are published as samples.json, and per-cycle sample counts are recorded in cycle.json and on leaderboard.json.

Each template contains [BRACKETED] placeholder tokens ([TOPIC], [ROLE], [FIELD], etc.). At cycle creation we fill every token from a curated value bank using a deterministic hash seeded by a per-cycle secret nonce. The value banks are versioned in the benchmark source and frozen into every cycle as banks.json; using banks rather than free-typed values eliminates founder discretion in what gets tested, and the seeded hash means anyone with the published banks and algorithm can re-derive the exact prompts after the cycle closes.

Transparency & verification

Because cycle names are predictable (e.g. January 2026, then February 2026), seeding placeholder selection on the cycle name would let a humanizer pre-compute next month's prompts and tune against them. We use a commit-reveal scheme instead:

The scripts/verify-cycle.ts script in the public repo re-runs the frozen algorithm against the published nonce, banks, and templates, then asserts that the resulting prompts match prompts.json exactly and that sha256(nonce) matches the committed hash from cycle start. It also runs the full score replay described below. Run it locally:

git clone https://github.com/HumanizerBench/humanizerbench
cd humanizerbench
npm install
npm run verify                    # verify every cycle
npm run verify -- "January 2026"  # verify one cycle (quote names with spaces)

Tested humanizers

The active humanizer set is listed on the leaderboard. Each humanizer has a dedicated detail page under /humanizers/[slug]. WriteHuman is included on the leaderboard and is held to exactly the same methodology, scoring, and penalty rules as every other humanizer.

How we configure each humanizer

Humanizers are not standardized: they expose different modes, models, and strength settings, and there is no single configuration that maps cleanly across tools. To keep the comparison fair and give each tool its best shot, we apply the same selection policy to all of them:

The plan tier and the exact settings we used for each humanizer are recorded per cycle in leaderboard.json under humanizers[i].plan_tier_used and humanizers[i].settings_used, so the configuration behind every score is auditable.

Detectors

We test every humanized output against five independent commercial AI detectors: GPTZero, Originality.ai, Copyleaks, Winston AI, and ZeroGPT. We do not operate, fine-tune, or coordinate with the detectors. For each humanizer output we submit the same text to all five and record each detector's own human-likelihood score on a common [0, 1] scale, where 1 means "human" (the humanizer evaded the detector) and 0 means "AI" (the detector caught it). Detectors that natively report an AI-probability are inverted (human = 1 − ai) and detectors that report a 0–100 score are divided by 100; we do not otherwise calibrate, re-scale, or weight the detectors against one another — each detector's score enters the per-test median exactly as the vendor returned it. The full per-test verdicts are published per cycle as detector-scores.json, where this value is the raw_score field.

Scoring formula

The composite score for each humanizer is computed from four sub-scores, each in [0, 1]. The base composite is bounded [0, 100]; per-test penalties (defined below) are then subtracted and the result clamped to a final composite ∈ [0, 100]:

composite_raw =
    42 * bypass_rate
  + 32 * meaning_preservation
  + 16 * readability
  + 10 * consistency_across_categories

composite = max(0, composite_raw - sum_of_penalty_deltas)

Weights and penalty rules are inlined verbatim into the published scoring.js file every cycle, so the methodology version on a given leaderboard.json pins the exact constants that ran. The composite weights above (42 / 32 / 16 / 10) have been stable since launch; the current methodology is methodology_version 1.2.0 — readability moved to a language-model quality rating, and each per-penalty cap was raised to 10. Every version's rationale is recorded in the published change log, and the exact constants are documented inline in the scoring code.

Each sub-score has a precise algorithmic definition, given below. Aggregate per humanizer always means "over the humanizer's set of tests in this cycle with status = "complete""; flagged and failed tests feed only the penalty rules. Tests reference the published row shapes in tests.json and detector-scores.json.

bypass_rate

For each test, compute the median of all raw_score values across the five detectors in detector-scores.json (median, not mean, because some detectors binarize their output near 0 or 1, and a single outlier would otherwise swing the mean by 0.25). The humanizer's bypass_rate is the mean of those per-test medians.

meaning_preservation

For each test, the cosine similarity of the OpenAI text-embedding-3-large embeddings of input_text and output_text, clamped to [0, 1]. This raw cosine is the per-test value published in tests.json as tests[i].meaning_preservation, and it is what the severe_meaning_drift penalty checks.

Cosine between any two on-topic English passages empirically floors around 0.85, so faithful rewrites cluster in a narrow high band and the raw value barely separates humanizers — even though meaning carries 32% of the composite. When forming the humanizer aggregate we therefore rescale each per-test value from [0.75, 1.0] onto [0, 1] (clamped) and take the mean, so genuine differences in meaning preservation register in the score. The rescale is monotonic — it never reorders humanizers, only spreads them — and the published scoring.js applies the identical transform, so an auditor re-derives the same aggregate from the raw per-test values.

readability

A language model reads each output and rates its writing quality — clarity, fluency, and naturalness — returning a value in [0, 1] where higher is better. Each output is rated once (the rating is cached by text hash, so it is stable across re-runs) and the per-test value is published in tests[i].readability; the humanizer aggregate is the mean. The published scoring.js simply averages these published per-test ratings and never calls a model, so an auditor reproduces the leaderboard from the raw files via npm run verify without any model access.

Before methodology_version 1.1.0, readability was a grammar- and spelling-error rate measured with LanguageTool rather than a model quality rating. That heuristic rated almost every output as flawless and barely separated humanizers, so it was replaced with the direct writing-quality rating described above; detector-bypass and meaning-preservation scoring were unaffected.

consistency_across_categories

For each writing category present in the humanizer's tests, compute the mean of per-test bypass medians within that category. The humanizer's consistency score is max(0, 1 - stddev(category_means)). Consistency needs enough categories to be meaningful, so a cycle that tested fewer than three categories scores a neutral 0.5 rather than a free 1.0 — otherwise a tool tested in a single category would top the consistency term over one tested broadly with mild variance. With three or more categories, a humanizer with identical category means scores 1; one that crushes blog posts but flunks academic essays sees its consistency score dragged down.

Penalties

A humanizer's composite score is reduced by one or more penalties when its output exhibits known quality issues. Penalties deduct from composite_raw; the final composite is clamped to [0, 100]. Per-humanizer penalty deltas are published in leaderboard.json under humanizers[i].penalties_applied, so every deduction is auditable.

Each penalty has a fixed deduction per occurrence and a cap on its total impact, so no single category can dominate the final score. The maximum possible total penalty across all codes is −50.0 (out of 100).

Penalty Trigger Per / Cap
Meaning drift (severe_meaning_drift)
The output's meaning drifted significantly from the original input.
Per test where tests[i].meaning_preservation < 0.85. −1.0 / −10.0
Length inflation (length_inflation)
The output ran much longer than the input, a common trick that pads text to dilute the AI signal.
Per test where word_count(output_text) / word_count(input_text) > 1.4. −1.0 / −10.0
Length deflation (length_deflation)
The output came back much shorter than the input. The rewrite dropped content instead of paraphrasing it.
Per test where word_count(output_text) / word_count(input_text) < 0.6. −1.0 / −10.0
Identical to input (identical_to_input)
The tool returned the input mostly unchanged. No real humanization happened.
Per flagged test with failure_reason_code = "identical_to_input". −2.0 / −10.0
Refusal (refusal_in_output)
The tool refused to humanize the input, often due to a content-policy block.
Per flagged test with failure_reason_code ∈ {"refusal_in_output", "refused_input"}. −1.0 / −10.0

Unavailable tools are excluded, not penalized. If at least half of a humanizer's attempted tests are flagged as site or access failures (site down, paywall, captcha, no output generated, or a ToS block), the tool could not be meaningfully exercised that cycle. Rather than rank it on the handful of runs that happened to go through — or deduct points for it being offline — we drop it from the ranking and record it separately under leaderboard.json's unavailable_humanizers, with its attempt counts. Output-quality failures (identical-to-input, refusal, truncation) are treated differently: the tool ran, so they keep the per-occurrence penalties above.

Reproducing the leaderboard from raw data

Anyone can re-derive every score on the leaderboard from the published cycle bundle. The shortest path:

  1. git clone https://github.com/HumanizerBench/humanizerbench and cd in.
  2. npm install (only dev deps are needed).
  3. npm run verify -- <cycle>. Under the hood this loads scoring.js from the cycle bundle, runs it against samples.json, tests.json, and detector-scores.json, and asserts that the per-humanizer results match leaderboard.json within 1e-4.

The published scoring.js is a self-contained ESM module with no external dependencies. To verify by hand without our script, import it and pass the three JSON arrays:

import { computeLeaderboard } from "./data/cycles/January 2026/scoring.js";
import samples from "./data/cycles/January 2026/samples.json" with { type: "json" };
import tests from "./data/cycles/January 2026/tests.json" with { type: "json" };
import detectorScores from "./data/cycles/January 2026/detector-scores.json" with { type: "json" };

const result = computeLeaderboard({ samples, tests, detectorScores });
console.log(result.humanizers[0]); // top-ranked humanizer for the cycle

Re-deriving a single sub-score by hand also works. To check one humanizer's bypass_rate:

  1. From tests.json, keep rows where humanizer_slug = h and status = "complete". Call this list T_h.
  2. For each test in T_h, gather every raw_score in detector-scores.json with that test_id. Take the median.
  3. The humanizer's bypass_rate is the arithmetic mean of those medians.
  4. Compare to leaderboard.json.humanizers[?].scores.bypass_rate.

Floating-point comparisons should use an absolute tolerance of 1e-4; verify-cycle.ts uses the same.

Versioning

Every cycle's leaderboard.json records three version stamps:

Stamp Bumped when
methodology_version Anything on this page changes: formula weights, sub-score definitions, penalty rules.
scoring_version The scoring script changes (typically alongside methodology_version).
prompt_set_version A template is added/removed/edited, OR a value bank is changed, OR the placeholder-selection algorithm changes.

Adding or removing a humanizer or detector does not bump a stamp: each cycle's leaderboard.json and detector-scores.json already record exactly which humanizers and detectors were tested, so that part of the audit trail comes from the data itself rather than a separate version number.

Past cycles are immutable. Each cycle bundle (templates, banks, algorithm, scoring code, raw data, leaderboard) is frozen at publish time, so a methodology change today never alters a cycle that ran yesterday; it just changes the version stamps on the next cycle.

Methodology integrity

WriteHuman does not manually adjust rankings. Rankings are computed mechanically from the published raw data using the published scoring.js. The integrity argument is structural rather than reputational: if a ranking didn't come from the published algorithm running on the published data, npm run verify would catch it, and anyone in the world can run that script.

How to contribute or dispute

If you've found an error or want to dispute a score, see our fairness & corrections policy for the contact address and the response-time commitment.