Reading the result

The API rates every sentence on its own. This is how to read the verdict, the shares and the positions.

Verdict

ValueMeaning
noneNo signs of AI found.
someSome passages stand out.
mostlyAt least half of the text is in the two upper levels.

There is deliberately no “human” class: a text without flags is not proven to be written by a person. The API returns no raw probability either.

Levels

bandLabelMeaning
very_likely_aiVery likely AIThe sentence carries clear patterns of a language model.
likely_aiLikely AISeveral sentences in a row read as machine-written.
possibly_aiPossibly AIA weaker hint. It does not count towards ai_share.
not_flaggedNot flaggedOnly with all_sentences: the sentence carries no flag. It does not say who wrote the sentence.

A flag is a hint, not proof. Show it to your users as a reason to look at the passage again.

All sentences

With "all_sentences": true, sentences holds every rated sentence in text order. Whitespace between sentences and fragments shorter than three characters belong to no entry. Sentences without a flag carry the level not_flagged. That does not mean a person wrote the sentence: the API flags strictly, a single sentence only on a very clear signal and otherwise several conspicuous sentences in a row. Verdict, shares and billing stay the same.

Excerpt
"sentences": [
  { "start": 0, "end": 118, "band": "not_flagged" },
  { "start": 119, "end": 247, "band": "likely_ai" },
  { "start": 248, "end": 391, "band": "likely_ai" },
  { "start": 392, "end": 470, "band": "not_flagged" }
]

Use it when you show the whole text sentence by sentence or want to work with the same sentence split.

Positions

start and end count from 0; end is behind the last character. You choose the unit with offset_unit:

ValueCountsFits
codepointUnicode code pointsPython, Go (runes), Rust (chars)
utf16UTF-16 code unitsJavaScript, Java, C#

The two differ only for characters outside the basic plane, such as emojis.

Highlighting passages

parts, last = [], 0
for s in result["sentences"]:
    parts.append(html.escape(text[last:s["start"]]))
    parts.append(f'<mark class="{s["band"]}">{html.escape(text[s["start"]:s["end"]])}</mark>')
    last = s["end"]
parts.append(html.escape(text[last:]))
marked_html = "".join(parts)
let out = "", last = 0;
for (const s of result.sentences) {            // request with offset_unit: "utf16"
  out += escapeHtml(text.slice(last, s.start));
  out += `<mark class="${s.band}">${escapeHtml(text.slice(s.start, s.end))}</mark>`;
  last = s.end;
}
out += escapeHtml(text.slice(last));