API

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.

Base URLhttps://knownota.com/api

Keys 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 10 words; under 50 words the result carries shortTextNote.

curl
curl -X POST https://knownota.com/api/detect \
  -H "Authorization: Bearer <key>" \
  -H "Content-Type: application/json" \
  -d '{"text": "<at least 10 words of prose; results under 50 words are often wrong>"}'
response
{
  "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.000334,        // held-out humans ≥250 words, this checkpoint
  "fprNote": null,                // set (and measuredFpr null) below 250 words
  "examMinWords": 250,            // the frozen exam's floor, not the scoring floor
  "modelVersion": "nota-v0.1",
  "minWords": 10,
  "shortTextNote": null,          // set on scored texts under 50 words
  "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
curl -X POST https://knownota.com/api/evidence \
  -H "Content-Type: application/json" \
  -d '{"text": "…"}'
response (abridged)
{
  "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 /provenance

The Watermark Check: does the text carry an AI-inserted or tool-inserted invisible watermark? What decodes without a vendor key (AI Text Mark TMK2, C2PA text manifests, StegCloak, classic SNOW, unnamed Unicode carriers), which deployed schemes only their issuers can read, and correlation hints. Four tiers that stay four tiers — a decoded frame, an issuer statement, an unreadable route and a correlation are different kinds of knowledge, and none of them is an authorship verdict.

curl
curl -X POST https://knownota.com/api/provenance \
  -H "Content-Type: application/json" \
  -d '{"text": "…"}'
response (abridged)
{
  "read":  [{ "scheme": "AI Text Mark (TMK2)", "status": "found",   // | absent | unreadable
              "detail": "TMK2 frame decoded…" }],
  "vendor": [],                       // issuer statements — never requested by Nota
  "route": [{ "scheme": "SynthID-Text", "vendor": "Google DeepMind",
              "support": "vendor-only",  // | coming-soon | caller-key | none-shipped
              "detail": "…", "detector": "https://…" }],
  "hints": [],                        // correlations, never identification
  "caveats": ["A negative result is not proof the text is unmarked…"]
}

A carrier is not an identity, and a decoded frame is not proof of origin unless it authenticates. Nota never sends your text to model vendors and holds no vendor production keys — nothing here detects Gemini or Claude locally.

POST /characters

The observed panel: hidden characters, look-alike letters, direction overrides and invisible carriers — the facts in the bytes; the Watermark Check names what they carry. Deterministic, position-exact, free — no model, no quota. Never enters the score.

curl
curl -X POST https://knownota.com/api/characters \
  -H "Content-Type: application/json" \
  -d '{"text": "…"}'
response (abridged)
{
  "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
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
result (abridged)
{
  "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.

Reference

Routes

Method · pathAuthBodyNotes
POST /detectoptional{ text }413 above 200,000 chars. Abstains under 10 words; shortTextNote under 50. Meters Nota words when authed.
POST /evidenceoptional{ text }Signals + reference envelope.
POST /charactersoptional{ text }Hidden characters, look-alike letters, carriers — deterministic, free, not in the score.
POST /overlap · GET /jobspaid{ text }Source check: cached result, or a job to poll. 250+ words. Never a plagiarism verdict.
POST /reportpaid{ text }Signed JSON of a detect result (Ed25519). GET /report/key for the public key.
POST /keys · GET /keys · DELETE /keys/:idsession{ name }Keys are kn_live_… Bearer tokens, hashed at rest, shown once; revoking stops a key immediately. Keys cannot manage keys.
GET /mekey or session—Plan, words used / words in period, 30-day usage — a quick way to check a key works.
Rules for integrators

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 }.

Get a key