JSON in, JSON out. The caveats come along.
Four endpoints — the same four panels as the detector: the model reading, the writing measurements, the character check and the source check. Auth is optional on the first three; send Authorization: Bearer … to meter against your plan. Every detect response carries abstained, modelVersion and measuredFpr — show them wherever you show probability.
https://knownota.com/apiKeys look like kn_live_…, are created on the account page (Professional and above), shown once, and stored only as a hash. A key meters against the plan of the account that owns it. Cap: 200,000 characters (413 above).
POST /detect
Machine-likeness under the shipped model. Abstains under 250 words.
curl -X POST https://knownota.com/api/detect \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"text": "<at least 250 words of prose>"}'{
"available": true,
"reason": null,
"abstained": false,
"probability": 0.81,
"band": "high", // low | moderate | high | very_high
"verdict": "consistent_with_machine_generation",
"threshold": 0.96,
"measuredFpr": 0.0066, // held-out humans ≥250 words, this checkpoint
"modelVersion": "nota",
"minWords": 250,
"wordCount": 412,
"caveat": "Consistent with machine generation under this model — not proof of authorship."
}If abstained is true, probability, band and verdict are null — show reason. Map verdict to the human sentence; null → “Inconclusive”.
POST /evidence
Writing statistics against a human envelope. Never averaged into a score — deliberately.
curl -X POST https://knownota.com/api/evidence \
-H "Content-Type: application/json" \
-d '{"text": "…"}'{
"wordCount": 412, "sentenceCount": 21, "paragraphCount": 5,
"notes": ["…"],
"reference": { "humanCorpus": {...}, "aiCorpus": {...}, "caveat": "…" },
"signals": [{
"name": "sentence_length_variation",
"label": "Sentence length variation",
"what": "One sentence a non-expert understands.",
"value": 12.4, "unit": "stdev words",
"direction": "lower-suggests-machine", // | higher-suggests-machine | context-only
"minWords": 250, "available": true,
"failureMode": "When this misleads — always shown.",
"position": "toward-machine", // | toward-human | unremarkable | no-reference
"separation": 0.46, "separationParaphrased": 0.28,
"hits": [], "spans": [[10, 24]]
}]
}If separation is below 0.20 render the row as context only, not as a lean. There is no aggregate evidence score.
POST /characters
The observed panel: hidden characters, look-alike letters, direction overrides and watermark carriers. Deterministic, position-exact, free — no model, no quota. Never enters the score.
curl -X POST https://knownota.com/api/characters \
-H "Content-Type: application/json" \
-d '{"text": "…"}'{
"inspected": 1475, "clean": false,
"totals": { "obfuscation": 2, "carrier": 0, "residue": 1 },
"findings": [{
"tier": "obfuscation", // | carrier | residue
"label": ""prоposal" mixes scripts — 1 Cyrillic letter drawn like Latin",
"codepoint": "U+043E passes for "o"",
"count": 1,
"samples": [{ "offset": 4, "excerpt": "The prоposal was…" }]
}],
"caveat": "…"
}obfuscation is evidence the text was altered to evade tools — not of who wrote it. residue (NBSPs, soft hyphens) arrives innocently from word processors; never surface it as a flag. clean: true is not evidence of anything.
POST /overlap Individual and up
The source check: quote-searches distinctive passages on the indexed web and scholarly connector and aligns matched spans. Returns a cached result instantly for a document checked in the last 30 days, otherwise a job to poll.
curl -X POST https://knownota.com/api/overlap \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"text": "…"}'
# → { "cached": true, "result": {…} } or { "job": { "id": "…", "status": "queued" } }
curl https://knownota.com/api/jobs -H "Authorization: Bearer <key>"
# → poll until jobs[].status is "done"; the result is on the job{
"totalWords": 563, "matchedWords": 562, "nearWords": 0, "coverage": 0.998,
"windowsSearched": 8, "windowsHit": 8,
"sources": [{
"url": "https://…", "title": "…", "domain": "…",
"matchedWords": 562, "windowHits": 8,
"spans": [[0, 3410]] // character offsets in your text
}],
"scope": "indexed web + scholarly connector",
"caveat": "Pages and matched spans — never a plagiarism verdict."
}Overlap is evidence, not a verdict: quoting, common phrasing and shared sources all produce it. Show the spans and the sources; do not print a single “plagiarism %” as the answer.
Routes
| Method · path | Auth | Body | Notes |
|---|---|---|---|
| POST /detect | optional | { text } | 413 above 200,000 chars. Abstains under 250 words. Meters Nota words when authed. |
| POST /evidence | optional | { text } | Signals + reference envelope. |
| POST /characters | optional | { text } | Hidden characters, look-alike letters, carriers — deterministic, free, not in the score. |
| POST /overlap · GET /jobs | paid | { text } | Source check: cached result, or a job to poll. 250+ words. Never a plagiarism verdict. |
| POST /report | paid | { text } | Signed JSON of a detect result (Ed25519). GET /report/key for the public key. |
| POST /keys · GET /keys · DELETE /keys/:id | session | { name } | Keys are kn_live_… Bearer tokens, hashed at rest, shown once; revoking stops a key immediately. Keys cannot manage keys. |
| GET /me | key or session | — | Plan, words used / words in period, 30-day usage — a quick way to check a key works. |
Show abstained, measuredFpr and modelVersion wherever you show probability. Do not render a red/green boolean. Do not average evidence into the score. Errors are always { "error": string }.