<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ProofAI | AI Content Detector</title>
    <description>The latest articles on DEV Community by ProofAI | AI Content Detector (@proofai).</description>
    <link>https://dev.to/proofai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4185945%2F0f15d478-66d1-4661-bf45-998c1f612486.jpg</url>
      <title>DEV Community: ProofAI | AI Content Detector</title>
      <link>https://dev.to/proofai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/proofai"/>
    <language>en</language>
    <item>
      <title>Watermarking vs AI Detection: SynthID, C2PA and Classifiers Compared</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Thu, 05 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/watermarking-vs-ai-detection-synthid-c2pa-and-classifiers-compared-2d16</link>
      <guid>https://dev.to/proofai/watermarking-vs-ai-detection-synthid-c2pa-and-classifiers-compared-2d16</guid>
      <description>&lt;p&gt;There are two very different ways to answer "was this made by AI?":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mark it at creation time.&lt;/strong&gt; The AI system adds a watermark or signed metadata when it generates content, and anyone with the right tool can check for it later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect it after the fact.&lt;/strong&gt; A classifier looks at the content itself and estimates whether it was generated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In 2026 both approaches are moving fast, partly because regulators now expect them. This post compares &lt;strong&gt;watermarking (SynthID)&lt;/strong&gt;, &lt;strong&gt;provenance (C2PA)&lt;/strong&gt; and &lt;strong&gt;detection classifiers&lt;/strong&gt;, with code, so you can decide what your product actually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EU:&lt;/strong&gt; Article 50 of the EU AI Act applies from &lt;strong&gt;2 August 2026&lt;/strong&gt;. Providers of generative AI must support machine-readable marking of AI-generated audio, image, video and text, and deployers have disclosure duties for deepfakes. The Commission has also published a voluntary Code of Practice on transparency of AI-generated content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;India:&lt;/strong&gt; amendments to the IT Rules, in force since &lt;strong&gt;20 February 2026&lt;/strong&gt;, require synthetically generated content to be prominently labelled and to carry permanent metadata or provenance markers, and they cut takedown deadlines for government and court orders to three hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry:&lt;/strong&gt; at Google I/O in May 2026, Google said C2PA verification and SynthID detection are coming to Search and Chrome, and OpenAI announced it would add SynthID watermarks to its images alongside C2PA credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach 1: Watermarking (SynthID)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; while generating, the model nudges its choices in a pattern derived from a secret key. For images and audio the signal is embedded in the pixels or waveform. For text, &lt;strong&gt;SynthID Text&lt;/strong&gt; adjusts token sampling so that a detector with the right keys can later measure the pattern statistically.&lt;/p&gt;

&lt;p&gt;Google open-sourced SynthID Text in 2024, and it's available in Hugging Face Transformers. Generating watermarked text looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install transformers torch accelerate
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SynthIDTextWatermarkingConfig&lt;/span&gt;

&lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/gemma-2-2b-it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# any causal LM you have access to
&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;watermarking_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SynthIDTextWatermarkingConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;654&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;836&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;597&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# keep your real keys secret
&lt;/span&gt;    &lt;span class="n"&gt;ngram_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a short note about watermarking.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;watermarking_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;watermarking_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;do_sample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skip_special_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detecting the watermark needs a detector configured with the same keys (the Transformers docs describe training a Bayesian detector for this).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; strong signal when present, low false-positive rate, survives light edits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; it only exists if the generator added it. Open-weight models run locally usually don't. Heavy paraphrasing or translation weakens text watermarks, and checking them depends on whoever holds the keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Provenance (C2PA Content Credentials)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; the tool that creates or edits a file attaches a &lt;strong&gt;cryptographically signed manifest&lt;/strong&gt; describing its origin and edit history, for example "generated by model X" or "captured on camera Y, then cropped".&lt;/p&gt;

&lt;p&gt;You can inspect it with the open-source &lt;code&gt;c2patool&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;c2patool
c2patool image.jpg            &lt;span class="c"&gt;# prints the manifest as JSON, if present&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; rich, verifiable information (who, what, when, which edits), and it's an open standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; metadata is easily stripped by screenshots, re-encoding and many upload pipelines. &lt;strong&gt;A missing manifest proves nothing.&lt;/strong&gt; It's also mostly used for images, video and audio, not plain text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Detection classifiers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; a model trained on large sets of human-made and AI-made content estimates the probability that a new piece was generated. Text detectors use signals like perplexity, burstiness and learned style patterns. Image detectors look for pixel, noise and frequency artifacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://ai.proofai.in/api/v1/detect &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Origin: YOUR_DOMAIN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'content=Text you want to check, ideally 50+ words...'&lt;/span&gt;
&lt;span class="c"&gt;# → { "ai_probability": 85, "verdict": "Likely AI", "segments": [...] }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; works on &lt;em&gt;any&lt;/em&gt; content, whatever generated it and whether or not it was watermarked. That includes content from open-source models and content with stripped metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; probabilistic, so false positives and false negatives happen (short texts and non-native English writing are known weak spots). Classifiers also need retraining as new models appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Watermark (SynthID)&lt;/th&gt;
&lt;th&gt;Provenance (C2PA)&lt;/th&gt;
&lt;th&gt;Detection classifier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Needs cooperation from the generator&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works on open-source / local models&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives screenshots&lt;/td&gt;
&lt;td&gt;Often (images)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tells you &lt;em&gt;who / what&lt;/em&gt; made it&lt;/td&gt;
&lt;td&gt;Model family only&lt;/td&gt;
&lt;td&gt;Yes, in detail&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Present / absent&lt;/td&gt;
&lt;td&gt;Signed manifest&lt;/td&gt;
&lt;td&gt;Probability score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False positives&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Real, must be managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text support&lt;/td&gt;
&lt;td&gt;Yes (SynthID Text)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which one should you use?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;All of them, in layers.&lt;/strong&gt; A practical verification flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check provenance.&lt;/strong&gt; Valid C2PA credentials give the strongest, most detailed evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check watermarks&lt;/strong&gt; where you have a detector for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a classifier&lt;/strong&gt; on everything else. In practice, that's most content on the internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat every result as evidence for a human decision&lt;/strong&gt;, especially for high-stakes cases like exams, hiring, journalism or KYC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Watermarks and provenance will cover more content over time, but they'll never cover content from models that don't take part. That's why detection classifiers remain necessary, and why they need to be built and presented responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Watermarking, provenance and detection answer slightly different questions. Watermarks and C2PA ask "did a participating tool label this?" Detection asks "does this content look generated?" Good products use both, and they're honest about what each signal can and can't prove.&lt;/p&gt;

&lt;p&gt;That's the approach we take at &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=watermark_vs_detection" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt;: multi-format detection for text, documents and images, returning a confidence score rather than a verdict.&lt;/p&gt;

&lt;p&gt;This post wraps up our first series on AI content detection for developers. &lt;strong&gt;What should we cover next: benchmarks, video and deepfake detection, or a deeper dive into C2PA?&lt;/strong&gt; Tell us in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Screening AI-Written Resumes Fairly: Adding AI Detection to a Hiring Pipeline</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/screening-ai-written-resumes-fairly-adding-ai-detection-to-a-hiring-pipeline-2dbl</link>
      <guid>https://dev.to/proofai/screening-ai-written-resumes-fairly-adding-ai-detection-to-a-hiring-pipeline-2dbl</guid>
      <description>&lt;p&gt;Recruiters are getting buried in polished, AI-written applications. If you build HR or applicant-tracking tools, you've probably been asked to "add AI detection".&lt;/p&gt;

&lt;p&gt;It's a reasonable request, but it's also one of the easiest places to build something unfair. In this post we'll look at what the data says, build a small Python pipeline that scans resumes and &lt;strong&gt;flags them for human review&lt;/strong&gt; (not rejection), and cover the guardrails that hiring tools need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data says
&lt;/h2&gt;

&lt;p&gt;A Checkr survey of 1,000 US business leaders, run in April 2026, found that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;73%&lt;/strong&gt; of companies have encountered AI-generated resumes, cover letters or assessments, and 29% say it happens frequently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;62%&lt;/strong&gt; agree AI has made traditional resume screening obsolete&lt;/li&gt;
&lt;li&gt;Their top concern is AI-generated resumes that &lt;strong&gt;exaggerate or fabricate experience&lt;/strong&gt; (36%), ahead of AI-completed skills tests (20%) and real-time AI help in video interviews (17%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;71%&lt;/strong&gt; would invest in AI detection tools for hiring &lt;em&gt;if they proved reliable&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Checkr sells background checks, so read vendor surveys with that in mind. The trend matches what recruiters tell us, though.)&lt;/p&gt;

&lt;h2&gt;
  
  
  First, decide what you're actually detecting
&lt;/h2&gt;

&lt;p&gt;Using AI to &lt;em&gt;polish&lt;/em&gt; a resume isn't cheating. Many good candidates use it to fix grammar or formatting. The real hiring risks are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fabricated experience&lt;/strong&gt;: claims that aren't true&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mass-generated, untailored applications&lt;/strong&gt; sent to hundreds of roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-completed assessments&lt;/strong&gt; that are meant to show the candidate's own skill&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An AI-text score can't prove any of these. What it &lt;em&gt;can&lt;/em&gt; do is tell a recruiter where to look more closely, for example by preparing specific interview questions about a section that reads as generic.&lt;/p&gt;

&lt;p&gt;So the goal of our pipeline is &lt;strong&gt;prioritising human attention&lt;/strong&gt;, not filtering people out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resumes/*.pdf  →  extract text  →  detect AI  →  review_queue.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need &lt;code&gt;pdfplumber&lt;/code&gt; and &lt;code&gt;requests&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdfplumber requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Extract text from PDFs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# extract.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scanned resumes (images of text) will come back empty. Flag those for manual handling instead of guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Score each resume
&lt;/h3&gt;

&lt;p&gt;This uses the same helper as our earlier API tutorial. Any detection API with a probability score will work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# detect.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ai.proofai.in/api/v1/detect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_ai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROOFAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROOFAI_ORIGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Build a review queue, not a reject list
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scan_resumes.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pdf_text&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;detect&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;detect_ai&lt;/span&gt;

&lt;span class="n"&gt;MIN_WORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;          &lt;span class="c1"&gt;# resumes are short; demand enough text
&lt;/span&gt;&lt;span class="n"&gt;REVIEW_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;   &lt;span class="c1"&gt;# tune on your own data
&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resumes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pdf_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manual_check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no extractable text (scanned?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_WORDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_scored&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;only &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detect_ai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Keep the passages that scored highest so reviewers know where to look
&lt;/span&gt;    &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_prob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;REVIEW_THRESHOLD&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# be gentle with rate limits
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_queue.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scanned &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; resumes → review_queue.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output gives recruiters a list of files to look at more closely, with the passages that drove the score. It doesn't reject anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails every hiring integration needs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. No automatic rejection.&lt;/strong&gt; Treat the score like a "look closer" flag. Combine it with structured interviews, work samples and reference checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Watch for bias.&lt;/strong&gt; Detectors have been shown to flag non-native English writing more often (Liang et al., Stanford, 2023). In a global hiring pipeline, that can turn into discrimination against certain groups of candidates. Monitor flag rates across groups where you lawfully can, and adjust thresholds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check the law where you hire.&lt;/strong&gt; Automated tools in hiring are regulated in a growing number of places. For example, New York City's Local Law 144 requires bias audits and notices for automated employment decision tools, and the EU AI Act treats AI systems used in recruitment as high-risk. Talk to your legal team before you ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Be transparent with candidates.&lt;/strong&gt; Say in the job posting or privacy notice that application materials may be checked by automated tools, and give a way to ask for human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Verify claims, not prose.&lt;/strong&gt; The strongest defence against fabricated experience is still old-fashioned: ask the candidate to talk through a project, run a short live exercise and check references.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;AI detection can help recruiters spend their limited time where it matters, but only if it's built as a &lt;strong&gt;prioritisation tool with human decisions at the end&lt;/strong&gt;. The code is the easy part. The guardrails are what make it fair.&lt;/p&gt;

&lt;p&gt;ProofAI has a dedicated &lt;a href="https://proofai.in/solutions/ai-resume-detection?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=hiring_pipeline" rel="noopener noreferrer"&gt;AI resume checker&lt;/a&gt; for recruiters, and the &lt;a href="https://proofai.in/api-docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=hiring_pipeline" rel="noopener noreferrer"&gt;API&lt;/a&gt; if you're building it into an ATS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you work on hiring tools: how is your team handling AI-written applications right now?&lt;/strong&gt; We'd love to compare notes in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team. This post isn't legal advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>career</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AI Detector vs Plagiarism Checker: What's the Difference?</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:50:42 +0000</pubDate>
      <link>https://dev.to/proofai/ai-detector-vs-plagiarism-checker-whats-the-difference-2me6</link>
      <guid>https://dev.to/proofai/ai-detector-vs-plagiarism-checker-whats-the-difference-2me6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; a &lt;strong&gt;plagiarism checker&lt;/strong&gt; asks &lt;em&gt;was this copied?&lt;/em&gt; It compares your text with web pages, journals and earlier submissions and shows matching passages as a similarity percentage. An &lt;strong&gt;AI detector&lt;/strong&gt; asks &lt;em&gt;was this written by an AI model?&lt;/em&gt; It looks at the writing itself, not at other sources. A text can be fully original and still AI-written, or written by you and still copied. That is why many colleges now look at both.&lt;/p&gt;

&lt;p&gt;This guide explains what each tool does, which free plagiarism checkers exist, what Turnitin and DrillBit check, and how to check your own work before you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plagiarism checker&lt;/th&gt;
&lt;th&gt;AI detector&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Question it answers&lt;/td&gt;
&lt;td&gt;Was this copied from somewhere?&lt;/td&gt;
&lt;td&gt;Did an AI model write this?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How it works&lt;/td&gt;
&lt;td&gt;Compares your text with a database of web pages, papers and submissions&lt;/td&gt;
&lt;td&gt;Looks for patterns typical of AI-generated text in the writing itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;Similarity % plus the matching sources&lt;/td&gt;
&lt;td&gt;AI probability plus the passages that look AI-written&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catches&lt;/td&gt;
&lt;td&gt;Copy-paste, close paraphrase, missing citations&lt;/td&gt;
&lt;td&gt;ChatGPT, Gemini or Claude text, even if it is "original"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Misses&lt;/td&gt;
&lt;td&gt;AI-written text that copies nothing&lt;/td&gt;
&lt;td&gt;Copied human text (it is not AI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;Turnitin Similarity, DrillBit, iThenticate, Grammarly, Quetext, Duplichecker&lt;/td&gt;
&lt;td&gt;ProofAI, GPTZero, Turnitin's AI writing report&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why you might need both
&lt;/h2&gt;

&lt;p&gt;Generative AI changed what "original" means. A ChatGPT essay usually does not copy any one source, so it can pass a plagiarism check with a low similarity score. The reverse also happens: a student who wrote every word but forgot quotation marks around a paragraph from a textbook gets a high similarity score while an AI detector shows nothing unusual.&lt;/p&gt;

&lt;p&gt;So the two checks answer different questions. Universities use the plagiarism report for the official similarity rules (in India, the UGC 2018 regulations set levels from up to 10% to above 60% for research work, as quoted in the &lt;a href="https://manuu.edu.in/sites/default/files/Library/MANUU-CodeOfEthics-Research-Publications.pdf" rel="noopener noreferrer"&gt;MANUU code of ethics&lt;/a&gt;), and a separate AI check where their policy covers AI use. Our guide &lt;a href="https://proofai.in/blog/turnitin-vs-drillbit-vs-ithenticate" rel="noopener noreferrer"&gt;Turnitin vs DrillBit vs iThenticate&lt;/a&gt; covers which tool Indian universities use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flc8euvhw0af3vxf2dj68.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flc8euvhw0af3vxf2dj68.webp" alt="Plagiarism checker vs AI detector: what each one checks" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Free plagiarism checkers
&lt;/h2&gt;

&lt;p&gt;If you only need to check for copied text, these are common options. Limits change often, so check the tool's own page before you rely on it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quetext:&lt;/strong&gt; the free plan includes a plagiarism check and an AI content check, each for up to 1,000 words (&lt;a href="https://www.quetext.com/pricing" rel="noopener noreferrer"&gt;Quetext pricing&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grammarly:&lt;/strong&gt; checks text for duplicate content; its Premium plan highlights the passages that need a citation (&lt;a href="https://www.grammarly.com/plagiarism-checker" rel="noopener noreferrer"&gt;Grammarly plagiarism checker&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplichecker:&lt;/strong&gt; a free online plagiarism checker that many students in India use for quick checks of short texts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your university's tool:&lt;/strong&gt; if your institution has Turnitin or DrillBit, that is the report that counts for submission. Ask your library or research cell how to get a check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free checkers search smaller databases than Turnitin or DrillBit, which also compare against earlier student submissions and subscription journals. A low score on a free tool does not guarantee the same score on your university's report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Turnitin and DrillBit check
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Turnitin&lt;/strong&gt; produces a Similarity Report (plagiarism) and, on licences that include it, a separate AI writing report. The AI report only appears when a file has at least 300 words of long-form prose in a supported language (&lt;a href="https://guides.turnitin.com/hc/en-us/articles/28234943089933-File-requirements-for-an-AI-writing-report" rel="noopener noreferrer"&gt;Turnitin&lt;/a&gt;). See &lt;a href="https://proofai.in/blog/can-turnitin-detect-ai-in-a-pdf" rel="noopener noreferrer"&gt;Can Turnitin detect AI in a PDF?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DrillBit&lt;/strong&gt; is the plagiarism checker that the Ministry of Education's ShodhShuddhi programme has offered to Indian higher education institutions since October 2023 (&lt;a href="https://shodhshuddhi.inflibnet.ac.in/about.php" rel="noopener noreferrer"&gt;ShodhShuddhi&lt;/a&gt;). Whether a DrillBit report includes an AI check depends on your institution's setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where ProofAI fits
&lt;/h2&gt;

&lt;p&gt;ProofAI is an &lt;strong&gt;AI detector&lt;/strong&gt;, not a plagiarism checker. It does not compare your text with other sources and cannot produce a similarity report. What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks text, PDF and Word files and images for AI-generated content, free for 10 checks a day without an account (&lt;a href="https://proofai.in/free-ai-detector" rel="noopener noreferrer"&gt;free AI detector&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Shows which sentences look most AI-written, so you can rewrite them in your own voice.&lt;/li&gt;
&lt;li&gt;Works on the files you actually submit, including long PDFs (&lt;a href="https://proofai.in/document-detection" rel="noopener noreferrer"&gt;PDF and Word AI checker&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a plagiarism checker for citations and copied text, and an AI detector for AI-written passages. Neither result is proof on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple routine before you submit
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix citations first:&lt;/strong&gt; quotation marks around every quote, every source in your reference list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a plagiarism check&lt;/strong&gt; with your university's tool or a free checker, and fix any passage that matches a source without a citation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run an AI check&lt;/strong&gt; on the same file. If a passage reads as AI-written, rewrite it with your own examples and reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep your drafts and notes.&lt;/strong&gt; Version history is the best evidence if your work is ever questioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disclose AI use&lt;/strong&gt; where your course or university asks for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What neither tool can do
&lt;/h2&gt;

&lt;p&gt;Plagiarism checkers can flag common phrases and properly cited quotes; AI detectors can flag formal or non-native English writing that a person wrote. Both give a starting point for a human review, never a verdict. If a result looks wrong, ask your teacher or supervisor to look at the highlighted passages with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is an AI detector the same as a plagiarism checker?&lt;/strong&gt;&lt;br&gt;
No. A plagiarism checker finds text copied from other sources; an AI detector estimates whether an AI model wrote the text. They answer different questions, so many institutions use both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a plagiarism checker detect ChatGPT?&lt;/strong&gt;&lt;br&gt;
Usually not on its own, because AI-generated text rarely copies one source word for word. Some tools, like Turnitin, add a separate AI writing report on licences that include it. To check a text for ChatGPT yourself, use a &lt;a href="https://proofai.in/detect-chatgpt-text" rel="noopener noreferrer"&gt;ChatGPT checker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which is the best free plagiarism checker?&lt;/strong&gt;&lt;br&gt;
Quetext's free plan checks up to 1,000 words; Grammarly and Duplichecker also offer free checks. For official submissions, your university's Turnitin or DrillBit report is the one that counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does ProofAI check plagiarism?&lt;/strong&gt;&lt;br&gt;
No. ProofAI is an AI detector for text, documents and images. Use it alongside a plagiarism checker, not instead of one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need both checks for my assignment?&lt;/strong&gt;&lt;br&gt;
If your course has rules on both copying and AI use, yes. Run the plagiarism check for citations and the AI check for AI-written passages before you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A plagiarism checker tells you whether you copied; an AI detector tells you whether a machine wrote it. Check citations first, then check for AI-written passages, keep your drafts, and you will be ready for whichever report your teacher or university runs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://proofai.in/blog/ai-detector-vs-plagiarism-checker" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>education</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Turnitin vs DrillBit vs iThenticate: Which Do Indian Universities Use?</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:50:06 +0000</pubDate>
      <link>https://dev.to/proofai/turnitin-vs-drillbit-vs-ithenticate-which-do-indian-universities-use-48hm</link>
      <guid>https://dev.to/proofai/turnitin-vs-drillbit-vs-ithenticate-which-do-indian-universities-use-48hm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; many Indian universities check theses and dissertations with &lt;strong&gt;DrillBit&lt;/strong&gt;, which the government's ShodhShuddhi programme has provided to higher education institutions since October 2023. &lt;strong&gt;Turnitin&lt;/strong&gt; is used by universities and colleges that buy their own licence, mostly for coursework. &lt;strong&gt;iThenticate&lt;/strong&gt; is Turnitin's tool for researchers, journals and publishers. All three are plagiarism checkers first; AI writing detection is an extra that depends on the product and the licence your institution has.&lt;/p&gt;

&lt;p&gt;If your department tells you to "get a plagiarism report", this guide explains what that report is, which tool is likely behind it, how the UGC similarity levels work, and what an AI detector can and cannot add.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three tools at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DrillBit&lt;/th&gt;
&lt;th&gt;Turnitin&lt;/th&gt;
&lt;th&gt;iThenticate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Made by&lt;/td&gt;
&lt;td&gt;DrillBit (Indian company)&lt;/td&gt;
&lt;td&gt;Turnitin&lt;/td&gt;
&lt;td&gt;Turnitin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main use in India&lt;/td&gt;
&lt;td&gt;Theses, dissertations, research papers at universities&lt;/td&gt;
&lt;td&gt;Coursework and assignments where the institution has a licence&lt;/td&gt;
&lt;td&gt;Research manuscripts, journals, publishers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How institutions get it&lt;/td&gt;
&lt;td&gt;Free to eligible higher education institutions through INFLIBNET's ShodhShuddhi programme since 1 Oct 2023, or a direct licence&lt;/td&gt;
&lt;td&gt;Institutional licence&lt;/td&gt;
&lt;td&gt;Institutional or publisher licence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plagiarism (similarity) check&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI writing check&lt;/td&gt;
&lt;td&gt;Part of its "AI content integrity" platform; depends on the institution's setup&lt;/td&gt;
&lt;td&gt;Yes, on licences that include it&lt;/td&gt;
&lt;td&gt;Yes in iThenticate 2.0, as an add-on licence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who usually runs it&lt;/td&gt;
&lt;td&gt;University library or research cell&lt;/td&gt;
&lt;td&gt;Teacher or LMS when you submit&lt;/td&gt;
&lt;td&gt;Researcher, journal or research office&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sources: &lt;a href="https://shodhshuddhi.inflibnet.ac.in/about.php" rel="noopener noreferrer"&gt;ShodhShuddhi (INFLIBNET)&lt;/a&gt;, &lt;a href="https://drillbitglobal.com/" rel="noopener noreferrer"&gt;DrillBit&lt;/a&gt;, &lt;a href="https://www.turnitin.ca/blog/ithenticate-2-0-advancing-research-integrity-with-ai-writing-detection" rel="noopener noreferrer"&gt;Turnitin on AI detection in iThenticate 2.0&lt;/a&gt;, &lt;a href="https://guides.turnitin.com/hc/en-us/articles/28234943089933-File-requirements-for-an-AI-writing-report" rel="noopener noreferrer"&gt;Turnitin AI writing report requirements&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  DrillBit and ShodhShuddhi
&lt;/h2&gt;

&lt;p&gt;ShodhShuddhi is the Ministry of Education's plagiarism-check initiative run by the INFLIBNET Centre in Gandhinagar. It started in 2019 with Ouriginal (formerly Urkund). That contract ended on 30 September 2023, and from &lt;strong&gt;1 October 2023&lt;/strong&gt; INFLIBNET has offered &lt;strong&gt;DrillBit-Extreme&lt;/strong&gt; to higher education institutions under the same programme, with full service from 1 November 2023 (&lt;a href="https://shodhshuddhi.inflibnet.ac.in/about.php" rel="noopener noreferrer"&gt;ShodhShuddhi&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In practice this is why so many PhD scholars and postgraduate students in India now receive a DrillBit report: the university library or research cell runs the thesis through it before submission. ShodhShuddhi access is given to institutions, so ask your library or research cell how to submit your work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turnitin
&lt;/h2&gt;

&lt;p&gt;Turnitin is the best-known plagiarism checker worldwide. In India it is used mainly by institutions that buy a licence, often inside the learning platform where you upload assignments. Depending on the licence, Turnitin can also produce an &lt;strong&gt;AI writing report&lt;/strong&gt;, which is separate from the similarity score. That report only appears when the file meets Turnitin's conditions, such as at least 300 words of long-form prose in a supported language (&lt;a href="https://guides.turnitin.com/hc/en-us/articles/28234943089933-File-requirements-for-an-AI-writing-report" rel="noopener noreferrer"&gt;Turnitin&lt;/a&gt;). Our guide &lt;a href="https://proofai.in/blog/can-turnitin-detect-ai-in-a-pdf" rel="noopener noreferrer"&gt;Can Turnitin detect AI in a PDF?&lt;/a&gt; covers what it reads and skips.&lt;/p&gt;

&lt;h2&gt;
  
  
  iThenticate
&lt;/h2&gt;

&lt;p&gt;iThenticate is Turnitin's product for &lt;strong&gt;research integrity&lt;/strong&gt;: journals, publishers and research offices use it to check manuscripts before publication. Turnitin has added AI writing detection to &lt;strong&gt;iThenticate 2.0&lt;/strong&gt;, available as a licensed add-on, and reports it as a separate percentage from the similarity score (&lt;a href="https://www.turnitin.ca/blog/ithenticate-2-0-advancing-research-integrity-with-ai-writing-detection" rel="noopener noreferrer"&gt;Turnitin&lt;/a&gt;). If a journal asks for an iThenticate report, they usually run it themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the UGC similarity levels work
&lt;/h2&gt;

&lt;p&gt;The UGC (Promotion of Academic Integrity and Prevention of Plagiarism in Higher Educational Institutions) Regulations, 2018 set four levels of similarity for research work. University policies quote them like this (&lt;a href="https://manuu.edu.in/sites/default/files/Library/MANUU-CodeOfEthics-Research-Publications.pdf" rel="noopener noreferrer"&gt;MANUU code of ethics&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Similarity&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level 0&lt;/td&gt;
&lt;td&gt;Up to 10%&lt;/td&gt;
&lt;td&gt;Minor similarities, no penalty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 1&lt;/td&gt;
&lt;td&gt;Above 10% to 40%&lt;/td&gt;
&lt;td&gt;Penalties apply under the regulations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 2&lt;/td&gt;
&lt;td&gt;Above 40% to 60%&lt;/td&gt;
&lt;td&gt;Stronger penalties&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 3&lt;/td&gt;
&lt;td&gt;Above 60%&lt;/td&gt;
&lt;td&gt;The most serious level&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your university's own policy decides exactly what happens at each level, so read it before submitting. These percentages are about &lt;strong&gt;similarity to existing sources&lt;/strong&gt;, not about AI writing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn397qdsdhxnevilkpkzb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn397qdsdhxnevilkpkzb.webp" alt="Which check does what: plagiarism (similarity) vs AI writing, and where DrillBit, Turnitin, iThenticate and ProofAI fit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Plagiarism check vs AI check: not the same thing
&lt;/h2&gt;

&lt;p&gt;A plagiarism checker asks: &lt;strong&gt;was this copied&lt;/strong&gt; from somewhere? An AI detector asks: &lt;strong&gt;was this written by an AI model&lt;/strong&gt;? A thesis can be fully original and still partly AI-written, or written by you and still match published sources too closely. That is why some reports now show two separate numbers.&lt;/p&gt;

&lt;p&gt;ProofAI is an &lt;strong&gt;AI detector&lt;/strong&gt;, not a plagiarism checker. It cannot produce the similarity report your university requires, and it does not replace DrillBit, Turnitin or iThenticate. What it can do is let you check your own chapters for AI-like passages before you submit, for free, and see which sentences look AI-written. You can upload a PDF or Word file to our &lt;a href="https://proofai.in/document-detection" rel="noopener noreferrer"&gt;document checker&lt;/a&gt;, or see how we compare in &lt;a href="https://proofai.in/turnitin-alternative-india" rel="noopener noreferrer"&gt;Turnitin alternatives in India&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you submit your thesis
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask which tool your university uses&lt;/strong&gt; and what similarity level it accepts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix citations first&lt;/strong&gt;: quotes in quotation marks, every source in the reference list. Most high similarity scores come from missing citations, not copying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paraphrase properly&lt;/strong&gt; in your own words and cite the source anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your own writing for AI-like passages&lt;/strong&gt; if you used AI tools for drafting or language help, and rewrite those parts in your voice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep your drafts and notes.&lt;/strong&gt; If a report is questioned, version history is the best proof of your own work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disclose AI use&lt;/strong&gt; where your university or journal asks for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What these tools cannot do
&lt;/h2&gt;

&lt;p&gt;No plagiarism or AI checker is a judge. Similarity tools can match properly cited text or common phrases; AI detectors can wrongly flag human writing, especially formal or non-native English. Results should start a review by a person, not end one. If you think a report is wrong, ask your supervisor or the research cell to look at the highlighted passages with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which plagiarism checker do Indian universities use?&lt;/strong&gt;&lt;br&gt;
Many use DrillBit, provided free to eligible higher education institutions through INFLIBNET's ShodhShuddhi programme since October 2023. Others license Turnitin for coursework, and journals often use iThenticate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is DrillBit better than Turnitin?&lt;/strong&gt;&lt;br&gt;
They serve similar purposes with different databases and licences. What matters for you is which one your university requires, because that is the report it will accept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does DrillBit check for AI writing?&lt;/strong&gt;&lt;br&gt;
DrillBit describes its platform as covering plagiarism detection and AI content integrity. Whether your report includes an AI check depends on your institution's setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use ProofAI instead of a UGC plagiarism report?&lt;/strong&gt;&lt;br&gt;
No. ProofAI checks for AI writing, not similarity to sources, so it cannot replace the plagiarism report your university asks for. Use it to check your own drafts for AI-like passages before submitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What similarity percentage is allowed for a PhD thesis in India?&lt;/strong&gt;&lt;br&gt;
Under the UGC 2018 regulations, up to 10% similarity is Level 0 with no penalty. Your university's policy has the final word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For most Indian theses the report will come from DrillBit through your university, for coursework it may be Turnitin, and for journal papers iThenticate. Get your citations right, keep your drafts, and if you used AI tools along the way, check and rewrite those passages in your own words before the official check.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://proofai.in/blog/turnitin-vs-drillbit-vs-ithenticate" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>education</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How to Spot a Deepfake Video: A Practical Checklist</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:50:03 +0000</pubDate>
      <link>https://dev.to/proofai/how-to-spot-a-deepfake-video-a-practical-checklist-57nj</link>
      <guid>https://dev.to/proofai/how-to-spot-a-deepfake-video-a-practical-checklist-57nj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; slow the video down and look at the face, the mouth and the light first; then take a screenshot of a clear frame and check it, and find where the clip first appeared. No single sign proves a video is fake, but three or four of them together, plus a source you cannot trace, are a strong warning. This checklist takes about five minutes and needs no special software.&lt;/p&gt;

&lt;p&gt;Deepfake videos are no longer a lab curiosity. In India, the government's fact-checking unit has repeatedly flagged AI-generated videos of public figures used to sell fake investment schemes, including clips of the Finance Minister promising impossible monthly returns (&lt;a href="https://www.newsbytesapp.com/news/india/pib-debunks-deepfake-of-nirmala-sitharaman-endorsing-rs25l-monthly-returns/tldr" rel="noopener noreferrer"&gt;NewsBytes on the PIB fact check&lt;/a&gt;). The same tricks show up in fake job interviews, "leaked" clips and family-emergency scams on WhatsApp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Watch the face closely
&lt;/h2&gt;

&lt;p&gt;Researchers at the MIT Media Lab, who ran the public &lt;a href="https://www.media.mit.edu/projects/detect-fakes/overview/" rel="noopener noreferrer"&gt;Detect Fakes&lt;/a&gt; project, point out that high-end deepfakes are almost always changes to the &lt;strong&gt;face&lt;/strong&gt;. Their checklist, as summarised in this &lt;a href="https://guides.library.illinoisstate.edu/evaluating/deepfakes" rel="noopener noreferrer"&gt;Illinois State University library guide&lt;/a&gt;, asks you to look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cheeks and forehead:&lt;/strong&gt; is the skin too smooth or too wrinkly, and does its age match the hair and eyes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eyes and eyebrows:&lt;/strong&gt; do shadows fall where you would expect them?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glasses:&lt;/strong&gt; is there glare, too much glare, and does it move naturally when the head turns?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facial hair and moles:&lt;/strong&gt; do they look real, or slightly painted on?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blinking:&lt;/strong&gt; does the person blink too little or too much?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lips:&lt;/strong&gt; do their size and colour match the rest of the face?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pause the video and step through it frame by frame. Many players let you slow playback to 0.25×, and glitches that are invisible at normal speed often show up between frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Check the mouth and the voice
&lt;/h2&gt;

&lt;p&gt;The mouth is hard to fake. In many deepfakes the teeth look blurred or merge into one white block, and the tongue barely appears. Then listen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do the lips move exactly with the words, especially on "p", "b" and "m" sounds where the lips must close?&lt;/li&gt;
&lt;li&gt;Does the voice sound flat, oddly clean, or without breathing, background noise or room echo?&lt;/li&gt;
&lt;li&gt;Does the accent or the way of speaking match how this person really talks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voice cloning is now cheap, so a familiar voice is not proof on its own. If a "relative" or "boss" asks for money on a video call, hang up and call them back on the number you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Look at hands, light and the background
&lt;/h2&gt;

&lt;p&gt;AI video generators still struggle with physics and small details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hands:&lt;/strong&gt; fingers that merge, change number or bend the wrong way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Light and shadows:&lt;/strong&gt; shadows that flicker, point in different directions, or do not move with the person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text in the scene:&lt;/strong&gt; signs, logos or captions inside the video that change shape between frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges:&lt;/strong&gt; a halo or blur where the face meets the hair, ears or neck, especially when the head turns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objects:&lt;/strong&gt; things that appear, disappear or melt into the background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2riwyioklitjwrf4mg5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2riwyioklitjwrf4mg5.webp" alt="Deepfake video checklist: face, mouth and voice, hands and light, key frames, source" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Check key frames with an image detector
&lt;/h2&gt;

&lt;p&gt;You do not need a video tool to test a video. Pause on two or three clear frames that show the face or the main subject, take screenshots, and upload them to an &lt;a href="https://proofai.in/image-detection" rel="noopener noreferrer"&gt;AI image detector&lt;/a&gt;. Check more than one frame: a single frame can be misleading, and real videos that were heavily compressed (for example after being forwarded many times) are harder for any detector.&lt;/p&gt;

&lt;p&gt;For the spoken words, copy the captions or a transcript into an &lt;a href="https://proofai.in/text-detection" rel="noopener noreferrer"&gt;AI text detector&lt;/a&gt;. A script that reads like generic AI text is one more signal, not proof.&lt;/p&gt;

&lt;p&gt;ProofAI's own &lt;a href="https://proofai.in/video-detection" rel="noopener noreferrer"&gt;video detection is coming soon&lt;/a&gt;; until then, checking frames and the transcript is the most practical route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Trace the source
&lt;/h2&gt;

&lt;p&gt;Most fakes fall apart when you look for where they came from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reverse-search a key frame&lt;/strong&gt; with Google Lens or TinEye. The original clip, often with a different context or date, frequently turns up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the account that posted it:&lt;/strong&gt; how old it is, what else it posts, and whether the real person or organisation shared the same video on their official channels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look for Content Credentials.&lt;/strong&gt; Some cameras, editing apps and AI tools now attach signed "Content Credentials" (the C2PA standard) to files. You can upload a file to the &lt;a href="https://contentcredentials.org/verify" rel="noopener noreferrer"&gt;Content Credentials Verify&lt;/a&gt; tool to read them (&lt;a href="https://opensource.contentauthenticity.org/docs/verify" rel="noopener noreferrer"&gt;how the tool works&lt;/a&gt;). A missing credential does not mean a video is fake, because most software does not add them yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search the claim, not just the video:&lt;/strong&gt; if a minister really announced a new scheme, official sites and major news outlets will say so.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Quick reference: the checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What to look for&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Face&lt;/td&gt;
&lt;td&gt;Skin too smooth, odd shadows, glare on glasses, blinking&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mouth and voice&lt;/td&gt;
&lt;td&gt;Blurred teeth, lips out of sync, flat or too-clean voice&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hands, light, background&lt;/td&gt;
&lt;td&gt;Merging fingers, flickering shadows, changing text&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key frames&lt;/td&gt;
&lt;td&gt;Screenshot 2-3 frames, check with an image detector&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Reverse image search, posting account, official channels, Content Credentials&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What to do if you find a deepfake
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not forward it&lt;/strong&gt;, even to warn people; share a fact-check instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report it on the platform&lt;/strong&gt; (YouTube, Instagram, Facebook and X all have options for manipulated or impersonating media).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report fraud in India&lt;/strong&gt; on the National Cyber Crime Reporting Portal (&lt;a href="https://cybercrime.gov.in" rel="noopener noreferrer"&gt;cybercrime.gov.in&lt;/a&gt;) or by calling the cybercrime helpline &lt;strong&gt;1930&lt;/strong&gt;, especially if money was asked for or lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask PIB Fact Check&lt;/strong&gt; about videos involving the Government of India; they publish debunks of fake clips (&lt;a href="https://organiser.org/2026/07/31/372783/bharat/fact-check-pib-debunks-ai-deepfake-falsely-showing-fm-nirmala-sitharaman-endorsing-rs-70000-a-day-investment-scam/" rel="noopener noreferrer"&gt;example&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;If the video uses &lt;strong&gt;your&lt;/strong&gt; face or voice, keep copies and links, report it to the platform, and see our guide to &lt;a href="https://proofai.in/blog/indias-ai-labelling-rules-explained-what-the-2026-it-rules-mean-for-creators-and-businesses" rel="noopener noreferrer"&gt;India's AI labelling rules&lt;/a&gt; for what platforms must now do.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this checklist cannot do
&lt;/h2&gt;

&lt;p&gt;No checklist and no detector catches every fake. The best deepfakes pass a casual look, and some genuine videos look strange because of poor lighting, filters or heavy compression. Detection tools give probabilities, not verdicts. There is no method that works every time. Treat the signs above as reasons to verify, and the source check as the deciding step.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can you tell if a video is a deepfake just by watching it?&lt;/strong&gt;&lt;br&gt;
Sometimes. Faces, mouths, hands and lighting often give cheap deepfakes away, especially when you slow the video down. Good deepfakes can pass a normal viewing, which is why checking the source matters most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free AI video detector?&lt;/strong&gt;&lt;br&gt;
Several online tools claim to detect AI video, with mixed results. A free route that works today is to screenshot key frames and check them with a free &lt;a href="https://proofai.in/image-detection" rel="noopener noreferrer"&gt;AI image detector&lt;/a&gt;, and to check the transcript with an &lt;a href="https://proofai.in/text-detection" rel="noopener noreferrer"&gt;AI text detector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I check a video I received on WhatsApp?&lt;/strong&gt;&lt;br&gt;
Forwarded videos are heavily compressed, so look first at the source: search for the claim, reverse-search a frame, and check official channels. Do not forward it until you know it is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I do if someone made a deepfake of me?&lt;/strong&gt;&lt;br&gt;
Save the video, links and account names, report it to the platform, and report it on cybercrime.gov.in or by calling 1930 if it is used for fraud, harassment or blackmail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Spotting a deepfake is mostly about slowing down: watch the face and mouth frame by frame, check a few frames with an image detector, and trace the clip back to its source before you believe or share it. A few minutes of checking is usually enough to stop a fake from spreading.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://proofai.in/blog/how-to-spot-a-deepfake-video" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deepfakes</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>education</category>
    </item>
    <item>
      <title>How Voice-Clone Detection Works: Audio Deepfake Detection Explained (with Python)</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/how-voice-clone-detection-works-audio-deepfake-detection-explained-with-python-3nee</link>
      <guid>https://dev.to/proofai/how-voice-clone-detection-works-audio-deepfake-detection-explained-with-python-3nee</guid>
      <description>&lt;p&gt;Voice cloning went from research demo to everyday scam tool in about two years. A few seconds of someone's voice from a Reel or a WhatsApp note can be enough to clone it, and "family emergency" calls using cloned voices are now a common fraud pattern in India and elsewhere.&lt;/p&gt;

&lt;p&gt;So how do you detect a fake voice? In this post we'll look at how audio deepfake detection works under the hood, then write a small Python script that extracts the kinds of features detectors use. It's a learning project, not a production detector, but it'll help you understand what real systems look at and why they still make mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How voice cloning works (briefly)
&lt;/h2&gt;

&lt;p&gt;Most modern voice clones are produced by a pipeline like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Text-to-speech or voice conversion model&lt;/strong&gt; generates an intermediate representation, usually a &lt;strong&gt;mel spectrogram&lt;/strong&gt;, in the target speaker's style.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;neural vocoder&lt;/strong&gt; turns that spectrogram into an audio waveform.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both stages leave subtle traces. Detectors try to find them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What detectors look for
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Spectral artifacts from the vocoder
&lt;/h3&gt;

&lt;p&gt;Neural vocoders are good, but not perfect. They can leave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unnatural energy patterns in the &lt;strong&gt;high frequencies&lt;/strong&gt; (roughly above 4–6 kHz)&lt;/li&gt;
&lt;li&gt;Overly smooth or repetitive &lt;strong&gt;harmonic structure&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Odd behaviour in the &lt;strong&gt;phase&lt;/strong&gt; of the signal, which humans barely notice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Missing "messiness" of real speech
&lt;/h3&gt;

&lt;p&gt;Real speech is full of small imperfections: breaths, mouth clicks, background room tone, tiny variations in pitch and timing. Synthetic speech is often &lt;strong&gt;too clean and too regular&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prosody and timing
&lt;/h3&gt;

&lt;p&gt;Pitch (F0) contours, pause lengths and speaking rate in cloned voices can be flatter or more uniform than a real person under stress, which is ironic given how emotional scam calls try to sound.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Learned features
&lt;/h3&gt;

&lt;p&gt;State-of-the-art systems don't rely on hand-picked rules. They train deep models on large datasets of real and fake audio. The research community benchmarks these on the &lt;strong&gt;ASVspoof&lt;/strong&gt; challenges, which have run since 2015 and include logical-access (TTS and voice conversion) and deepfake tracks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hands-on: extract detector-style features in Python
&lt;/h2&gt;

&lt;p&gt;Let's compute a few features that classic detectors used, and compare a real recording with a synthetic one you generate yourself (for example with any TTS tool, using your own voice or a stock voice, never someone else's without permission).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;librosa numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# audio_features.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mono&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 1) Spectral roll-off: frequency below which 85% of energy sits
&lt;/span&gt;    &lt;span class="n"&gt;rolloff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spectral_rolloff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roll_percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2) High-frequency energy ratio (above 4 kHz)
&lt;/span&gt;    &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_fft&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;freqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fft_frequencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_fft&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hf_ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;freqs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 3) Spectral flatness: how "noise-like" vs "tonal" the signal is
&lt;/span&gt;    &lt;span class="n"&gt;flatness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spectral_flatness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 4) Pitch variability (F0 standard deviation in voiced frames)
&lt;/span&gt;    &lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voiced&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pyin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmax&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f0_std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nanstd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;voiced&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;voiced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 5) Pause structure: share of near-silent frames
&lt;/span&gt;    &lt;span class="n"&gt;rms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;librosa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;silence_ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rms&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolloff_hz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rolloff&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hf_energy_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hf_ratio&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spectral_flatness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;f0_std_hz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f0_std&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;silence_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;silence_ratio&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python audio_features.py real_me.wav tts_me.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll often see differences in high-frequency energy, flatness or pitch variability between the two. But try a few more samples and you'll also notice the problem: &lt;strong&gt;the differences aren't consistent&lt;/strong&gt;. Compression, phone lines, background noise and the specific TTS model all shift these numbers.&lt;/p&gt;

&lt;p&gt;That's exactly why hand-picked features gave way to trained models.&lt;/p&gt;

&lt;h2&gt;
  
  
  From features to a real detector
&lt;/h2&gt;

&lt;p&gt;A production audio deepfake detector typically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Converts audio to a time-frequency representation (spectrograms, LFCCs or raw waveform)&lt;/li&gt;
&lt;li&gt;Feeds it to a neural network trained on large, diverse real and fake datasets&lt;/li&gt;
&lt;li&gt;Outputs a score per segment, so you can see &lt;em&gt;where&lt;/em&gt; in a call the voice looks synthetic&lt;/li&gt;
&lt;li&gt;Is re-trained regularly, because new voice models appear constantly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even then, accuracy drops on &lt;strong&gt;phone-quality audio&lt;/strong&gt;, heavy compression (WhatsApp voice notes), noisy recordings and &lt;strong&gt;voice models it has never seen&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for builders
&lt;/h2&gt;

&lt;p&gt;If you're adding voice verification or fraud checks to an app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't rely on voice alone for authentication.&lt;/strong&gt; Voice biometrics can be spoofed. Combine with device signals, OTPs or call-back flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use detection as a risk signal&lt;/strong&gt;, routing suspicious calls or uploads to extra verification, not as an automatic block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design for out-of-band checks.&lt;/strong&gt; The best defence against a cloned "family member" is still a call back to a known number or a pre-agreed code word.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Audio deepfake detection looks at vocoder artifacts, missing natural noise, prosody and, above all, patterns learned from large datasets. Hand-crafted features are a great way to build intuition, but real-world detection needs trained models, diverse data and humility about error rates.&lt;/p&gt;

&lt;p&gt;At ProofAI we currently focus on detecting AI-generated &lt;strong&gt;text, documents and images&lt;/strong&gt;. You can try them free at &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=audio_deepfakes" rel="noopener noreferrer"&gt;proofai.in&lt;/a&gt;, and the &lt;a href="https://proofai.in/api-docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=audio_deepfakes" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; cover integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should we do a follow-up training a small spectrogram classifier on ASVspoof data?&lt;/strong&gt; Tell us in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team. Only clone voices you have permission to use.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>ai</category>
      <category>audio</category>
    </item>
    <item>
      <title>Build a Chrome Extension That Checks Selected Text for AI (Manifest V3 Tutorial)</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/build-a-chrome-extension-that-checks-selected-text-for-ai-manifest-v3-tutorial-15</link>
      <guid>https://dev.to/proofai/build-a-chrome-extension-that-checks-selected-text-for-ai-manifest-v3-tutorial-15</guid>
      <description>&lt;p&gt;Browser extensions are one of the most practical places to put AI detection. You highlight text on any page, right-click, and get a quick read on whether it looks AI-generated, without copying and pasting into another tab.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll build a small &lt;strong&gt;Manifest V3&lt;/strong&gt; Chrome extension that does exactly that. Along the way we'll cover the security mistake most "AI extension" tutorials make: shipping a secret API key inside the extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A right-click menu item: &lt;strong&gt;"Check selected text for AI"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It reads the full selected text from the page&lt;/li&gt;
&lt;li&gt;It sends the text to &lt;strong&gt;your own backend&lt;/strong&gt;, which calls the detection API&lt;/li&gt;
&lt;li&gt;It shows the result as a Chrome notification
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ai-text-checker/
├── manifest.json
├── background.js
└── icon128.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not call the detection API directly from the extension?
&lt;/h2&gt;

&lt;p&gt;Anything inside an extension package can be read by anyone who installs it. Unzip the &lt;code&gt;.crx&lt;/code&gt; and your &lt;code&gt;sk_live_...&lt;/code&gt; key is right there. So the extension talks to a small backend endpoint you control, and &lt;strong&gt;the backend holds the key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you followed our earlier post, &lt;em&gt;How to Add AI Content Detection to Your App with a REST API&lt;/em&gt;, you already have that endpoint: &lt;code&gt;POST /api/check-text&lt;/code&gt;. Any server route that accepts &lt;code&gt;{ text }&lt;/code&gt; and returns a score will work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: manifest.json
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Text Checker (demo)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Right-click selected text to check if it looks AI-generated."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"contextMenus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scripting"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://yourapp.com/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"128"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icon128.png"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each permission is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;contextMenus&lt;/code&gt;: adds the right-click item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;activeTab&lt;/code&gt; + &lt;code&gt;scripting&lt;/code&gt;: read the selection from the current tab, only after the user clicks our menu item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notifications&lt;/code&gt;: show the result&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;host_permissions&lt;/code&gt;: lets the service worker call your backend without CORS problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask for as little as possible. Reviewers on the Chrome Web Store check this, and so do users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: background.js (the service worker)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BACKEND_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourapp.com/api/check-text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contextMenus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;check-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check selected text for AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contextMenus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menuItemId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;check-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Read the full selection from the page.&lt;/span&gt;
  &lt;span class="c1"&gt;// info.selectionText works too, but reading it from the page is more reliable for long selections.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectionText&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scripting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tabId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelection&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Some pages (like chrome:// pages) block scripting. Fall back to selectionText.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Select more text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Only &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; words selected. Select at least 50 words for a reliable check.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BACKEND_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Try again later&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The daily check limit was reached.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't check this text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please try again in a moment.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High likelihood of AI-generated text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mixed signals: parts may be AI-generated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low likelihood of AI-generated text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;`AI probability: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aiProbability&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%. Treat this as one signal, not proof.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Network error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't reach the checking service.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;basic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iconUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon128.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few MV3 details worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service workers aren't persistent.&lt;/strong&gt; Don't keep state in global variables between clicks. Use &lt;code&gt;chrome.storage&lt;/code&gt; if you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create context menus in &lt;code&gt;onInstalled&lt;/code&gt;.&lt;/strong&gt; Creating them on every service-worker start throws "duplicate id" errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;chrome.scripting.executeScript&lt;/code&gt;&lt;/strong&gt; returns an array of results, one per frame. We only inject into the main frame here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Load it and test
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;chrome://extensions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Developer mode&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Load unpacked&lt;/strong&gt; and pick the &lt;code&gt;ai-text-checker&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Select a few paragraphs on any web page, right-click, and choose &lt;strong&gt;Check selected text for AI&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To debug, click &lt;strong&gt;"service worker"&lt;/strong&gt; on the extension's card to open its DevTools console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Make it production-ready
&lt;/h2&gt;

&lt;p&gt;Before publishing to the Chrome Web Store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate users&lt;/strong&gt; on your backend (for example with a token from your web app), so strangers can't use your quota.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit per user&lt;/strong&gt; on the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a clear privacy policy&lt;/strong&gt;: what text you send, where it goes, and whether it's stored. The Web Store requires disclosures for extensions that handle user data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add an options page&lt;/strong&gt; so users can set their own thresholds or turn notifications off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show results in context.&lt;/strong&gt; A small popup next to the selection, built with a content script, is a nicer experience than a system notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Don't want to build it?
&lt;/h2&gt;

&lt;p&gt;If you'd rather not build your own, try the &lt;a href="https://chromewebstore.google.com/detail/proofai/gakchielkgnpojmfdpfcpdacjdoahheb" rel="noopener noreferrer"&gt;ProofAI Chrome extension&lt;/a&gt;, which brings the same detection engine as &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=chrome_extension" rel="noopener noreferrer"&gt;proofai.in&lt;/a&gt; into your browser. Building your own is still a great way to learn MV3, and the pattern works with any detection API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you add to an extension like this: highlighting, history, Google Docs support?&lt;/strong&gt; Let us know in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Benchmark an AI Detector on Your Own Data (Precision, Recall and False Positives in Python)</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Wed, 28 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/how-to-benchmark-an-ai-detector-on-your-own-data-precision-recall-and-false-positives-in-python-3l0l</link>
      <guid>https://dev.to/proofai/how-to-benchmark-an-ai-detector-on-your-own-data-precision-recall-and-false-positives-in-python-3l0l</guid>
      <description>&lt;p&gt;Every AI detector claims to be "99% accurate". Those numbers usually come from the vendor's own test set, written in the vendor's favourite style, and they rarely tell you the number that matters most: &lt;strong&gt;how often it flags real human writing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The good news: you can measure a detector on &lt;strong&gt;your own data&lt;/strong&gt; in an afternoon. In this post we'll build a small benchmark in Python that reports precision, recall and false-positive rate, and helps you pick a threshold that fits your use case.&lt;/p&gt;

&lt;p&gt;(We build an AI detector, ProofAI, and we'd rather you test us properly than trust a marketing number. The method works for any detector.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build a labelled test set
&lt;/h2&gt;

&lt;p&gt;This is the part that matters most. You want two folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;testset/
├── human/   # writing you KNOW is human
└── ai/      # writing you KNOW is AI-generated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tips for a fair set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human samples should look like your real users.&lt;/strong&gt; If you're a university, use past essays from before 2022 (with permission). If you're in hiring, use old cover letters. Include non-native English writers, because that's where detectors are known to struggle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI samples should match the same tasks.&lt;/strong&gt; Give the same prompts to several models (for example ChatGPT, Gemini and Claude), and include some lightly edited AI text, because that's what you'll see in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aim for at least 100 of each.&lt;/strong&gt; Fewer than that and your numbers will swing a lot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep lengths similar.&lt;/strong&gt; Very short texts are harder to classify, so don't compare 50-word human texts to 800-word AI ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Score everything
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# score_testset.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://ai.proofai.in/api/v1/detect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROOFAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Origin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROOFAI_ORIGIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scores.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the scores once, then experiment with thresholds offline without spending more API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Compute the metrics
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# evaluate.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;confusion_matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roc_auc_score&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scores.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;confusion_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recall&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false_positive_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;humans_flagged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tn&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROC AUC:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;roc_auc_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;)]).&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading the numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;False-positive rate (FPR):&lt;/strong&gt; the share of human texts flagged as AI. For education and hiring, &lt;strong&gt;this is the number to watch&lt;/strong&gt;. An FPR of 5% means 1 in 20 honest people gets flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall:&lt;/strong&gt; the share of AI texts the detector catches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precision:&lt;/strong&gt; of everything flagged, how much was actually AI. This depends on how common AI text is in your real data, so don't over-trust it from a 50/50 test set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ROC AUC:&lt;/strong&gt; how well scores separate the two groups across all thresholds (1.0 is perfect, 0.5 is random).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Choose a threshold for &lt;em&gt;your&lt;/em&gt; risk
&lt;/h2&gt;

&lt;p&gt;There's no universal "right" threshold. Decide which mistake is worse for you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Worse mistake&lt;/th&gt;
&lt;th&gt;Pick a threshold that...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Student essays, hiring&lt;/td&gt;
&lt;td&gt;Flagging a real person&lt;/td&gt;
&lt;td&gt;keeps FPR very low (for example ≤1–2%), even if recall drops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spam / content farm filtering&lt;/td&gt;
&lt;td&gt;Letting junk through&lt;/td&gt;
&lt;td&gt;keeps recall high, with human review on flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editorial quality checks&lt;/td&gt;
&lt;td&gt;Neither is serious&lt;/td&gt;
&lt;td&gt;sits in the middle; treat as a writing hint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 5: Slice the results
&lt;/h2&gt;

&lt;p&gt;Overall numbers hide problems. Break them down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;length_bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;length_bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;human&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;human&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FPR at 85:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;human&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(n=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;human&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do the same by &lt;strong&gt;writer group&lt;/strong&gt; (native vs non-native English), &lt;strong&gt;topic&lt;/strong&gt; and &lt;strong&gt;AI model&lt;/strong&gt;. If one group's false-positive rate is much higher, you've found where human review is essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testing on texts the vendor might have trained on&lt;/strong&gt;, like famous essays or public datasets. Use your own data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only testing raw AI output.&lt;/strong&gt; Real users edit, paraphrase and mix. Include those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting one accuracy number.&lt;/strong&gt; Always report FPR separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-testing never.&lt;/strong&gt; Models change. Re-run your benchmark every few months.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;With two folders of labelled text and about 60 lines of Python, you can replace "99% accurate" marketing with numbers that reflect your users, your tasks and your risk tolerance. Whatever detector you choose, publish your threshold and review process internally, so decisions stay consistent and fair.&lt;/p&gt;

&lt;p&gt;Try ProofAI's &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=benchmark_detector" rel="noopener noreferrer"&gt;free detector&lt;/a&gt; or the &lt;a href="https://proofai.in/api-docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=benchmark_detector" rel="noopener noreferrer"&gt;API&lt;/a&gt; in your own benchmark. We'd genuinely like to hear the results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you benchmarked an AI detector? What false-positive rate did you see?&lt;/strong&gt; Share in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How AI Image Detection Works: Artifacts, Frequency Analysis and C2PA</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Mon, 26 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/how-ai-image-detection-works-artifacts-frequency-analysis-and-c2pa-5eeh</link>
      <guid>https://dev.to/proofai/how-ai-image-detection-works-artifacts-frequency-analysis-and-c2pa-5eeh</guid>
      <description>&lt;p&gt;"Is this image real?" used to be a question for photo editors. Now it's a question for every platform that accepts uploads, from marketplaces and dating apps to newsrooms and KYC flows.&lt;/p&gt;

&lt;p&gt;In this post we'll look at how AI image detection works in practice: the &lt;strong&gt;pixel and frequency artifacts&lt;/strong&gt; that classifiers learn, the &lt;strong&gt;provenance metadata&lt;/strong&gt; standards like C2PA, and &lt;strong&gt;invisible watermarks&lt;/strong&gt; like Google's SynthID. We'll also run two small experiments you can try on your own machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to answer "was this made by AI?"
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it looks at&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Weakness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Artifact detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The pixels themselves&lt;/td&gt;
&lt;td&gt;Works on any image&lt;/td&gt;
&lt;td&gt;Probabilistic; can be fooled by resizing, compression and new models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Provenance metadata (C2PA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Signed "Content Credentials" attached to the file&lt;/td&gt;
&lt;td&gt;Strong, cryptographically signed evidence when present&lt;/td&gt;
&lt;td&gt;Easily lost by screenshots and re-encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Watermarks (e.g. SynthID)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An invisible signal embedded at generation time&lt;/td&gt;
&lt;td&gt;Survives some editing&lt;/td&gt;
&lt;td&gt;Only works for models that add it, and needs the vendor's detector&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Real-world systems combine all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Artifact detection: what classifiers learn
&lt;/h2&gt;

&lt;p&gt;Image generators like diffusion models build pictures in ways that leave statistical traces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frequency-domain patterns.&lt;/strong&gt; Upsampling layers in generators can leave periodic, grid-like signals that are hard to see but show up clearly in a Fourier transform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noise inconsistencies.&lt;/strong&gt; Camera photos carry sensor noise that's consistent across the frame. Generated images often have noise that's too smooth or inconsistent between regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic slips.&lt;/strong&gt; Hands, text inside images, reflections, jewellery, earrings that don't match, and shadows pointing in different directions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern detectors are usually deep classifiers (CNNs or vision transformers) trained on large sets of real and generated images. They learn these cues automatically rather than relying on hand-written rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment: look at an image's frequency spectrum
&lt;/h3&gt;

&lt;p&gt;This won't detect AI on its own, but it shows the kind of signal detectors use. You need &lt;code&gt;numpy&lt;/code&gt;, &lt;code&gt;pillow&lt;/code&gt; and &lt;code&gt;matplotlib&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install numpy pillow matplotlib
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_spectrum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fftshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fft2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log1p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;radial_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;totals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bincount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bincount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real_photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;log_spectrum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gray&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;radial_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Frequency (radius)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Log magnitude&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare a photo from your phone with an AI-generated image. You'll sometimes see bright dots or grid patterns in the generated image's spectrum, or a different fall-off curve at high frequencies. You'll also see how much JPEG compression and resizing muddy the picture, which is why production detectors need a lot more than one hand-made feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Provenance: C2PA Content Credentials
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;C2PA&lt;/strong&gt; standard (Coalition for Content Provenance and Authenticity) lets cameras, editing apps and AI generators attach a &lt;strong&gt;cryptographically signed manifest&lt;/strong&gt; to a file. It records who or what created the image and what edits were made.&lt;/p&gt;

&lt;p&gt;Adoption has picked up a lot in 2026. At Google I/O in May 2026, Google said C2PA verification and SynthID detection are coming to Search and Chrome, and OpenAI announced it would add SynthID watermarks to its images alongside existing C2PA credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment: read Content Credentials from a file
&lt;/h3&gt;

&lt;p&gt;The open-source &lt;code&gt;c2patool&lt;/code&gt; from the Content Authenticity Initiative can print a file's manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (requires Rust's cargo), or download a release binary from GitHub&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;c2patool

&lt;span class="c"&gt;# Print the C2PA manifest, if the file has one&lt;/span&gt;
c2patool some_image.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file carries credentials, you'll get JSON describing the claim generator, signature and actions such as "created by an AI model" or "edited". If it doesn't, the tool reports that no manifest was found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key rule for developers:&lt;/strong&gt; &lt;em&gt;a missing manifest proves nothing.&lt;/em&gt; Screenshots, re-uploads and many social platforms strip metadata. Treat "no credentials" as &lt;strong&gt;inconclusive&lt;/strong&gt;, not as "real" or "fake".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Watermarks: SynthID and friends
&lt;/h2&gt;

&lt;p&gt;Watermarking works differently: the generator embeds an invisible signal into the pixels (or audio, or text) at creation time. Google DeepMind's &lt;strong&gt;SynthID&lt;/strong&gt; is the best-known example. Google DeepMind reported in May 2026 that more than 100 billion images, videos and audio files have been watermarked with it.&lt;/p&gt;

&lt;p&gt;Limitations to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A watermark only exists if the generating model added one. Images from open-source models running locally usually won't have one.&lt;/li&gt;
&lt;li&gt;Checking a watermark depends on the vendor's detector.&lt;/li&gt;
&lt;li&gt;A watermark says "this came from model X", not who made it or why.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it together in a product
&lt;/h2&gt;

&lt;p&gt;A sensible pipeline for user-uploaded images looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check provenance first.&lt;/strong&gt; If valid C2PA credentials say "AI-generated", you have strong evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check known watermarks&lt;/strong&gt; where tools are available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run an artifact classifier&lt;/strong&gt; for everything else, and treat its output as a probability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route uncertain or high-risk cases to a human&lt;/strong&gt;, especially for KYC, news and legal contexts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask for originals&lt;/strong&gt; when stakes are high. A real photographer usually has the RAW file or the original from their phone.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where ProofAI fits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=image_detection" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt; checks images for signs of AI generation and manipulation using computer-vision analysis, alongside text and document detection. AI video and deepfake detection is coming soon; for now, you can check key frames from a video as images. Like every detector, it returns a confidence score, not a verdict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the most convincing AI image you've seen this year, and what gave it away?&lt;/strong&gt; Share in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>computervision</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Can Turnitin Detect AI in a PDF? What Students Should Know</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Sun, 25 Oct 2026 02:21:05 +0000</pubDate>
      <link>https://dev.to/proofai/can-turnitin-detect-ai-in-a-pdf-what-students-should-know-5ed3</link>
      <guid>https://dev.to/proofai/can-turnitin-detect-ai-in-a-pdf-what-students-should-know-5ed3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer: yes.&lt;/strong&gt; Turnitin treats a PDF the same way as a Word file. If the PDF contains enough readable, long-form text in a supported language, it goes through the same AI writing check as a .docx. Saving your assignment as a PDF does not hide anything from it, and it does not make a human-written essay look more or less "AI".&lt;/p&gt;

&lt;p&gt;What matters is not the file type but &lt;strong&gt;what is inside the file&lt;/strong&gt;: how many words, what kind of writing, which language, and whether the text can actually be read. This guide explains each of those, what you will (and will not) see as a student, and how to check your own file before you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Turnitin's AI check handles PDFs
&lt;/h2&gt;

&lt;p&gt;Turnitin publishes the conditions a submission has to meet before it gets an AI writing report. According to its guide &lt;a href="https://guides.turnitin.com/hc/en-us/articles/28234943089933-File-requirements-for-an-AI-writing-report" rel="noopener noreferrer"&gt;File requirements for an AI writing report&lt;/a&gt;, a file needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At least 300 words and no more than 30,000 words&lt;/strong&gt; of prose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-form writing&lt;/strong&gt;, the kind you find in essays, reports and theses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An accepted file type:&lt;/strong&gt; .docx, .pdf, .txt or .rtf.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A file under 100 MB.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A supported language:&lt;/strong&gt; English, Spanish or Japanese.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no separate route for PDFs. A PDF that meets these rules is checked like any other file. A PDF that does not meet them simply does not get an AI score.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx9v3cwzy3iclttmm5g9q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx9v3cwzy3iclttmm5g9q.webp" alt="When does a file get an AI writing report: length, file type, language, and what usually gets no score" width="799" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What can stop a PDF from getting an AI score
&lt;/h2&gt;

&lt;p&gt;Most "Turnitin did not detect anything in my PDF" stories come down to one of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The PDF is a scan.&lt;/strong&gt; If you photographed or scanned printed pages, the PDF is a picture of text, not text. A checker that reads words has nothing to read unless the file has a text layer. A quick test: open the PDF and try to select a sentence with your cursor. If you can't, the text is an image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The text is too short.&lt;/strong&gt; Under 300 words of prose, there is no AI writing report at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The writing is not long-form prose.&lt;/strong&gt; Bullet-point notes, tables, code, poems and scripts are not the format the check is built for. Many university guides warn that these get unreliable results or none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The language is not supported.&lt;/strong&gt; A Hindi or Marathi assignment does not get an AI writing report from Turnitin today. Mixed-language files can behave unpredictably.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are tricks to "beat" Turnitin. Your instructor still sees the file, reads it, and can ask you about it. They are simply the reasons a report may be missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can students see their Turnitin AI score?
&lt;/h2&gt;

&lt;p&gt;Usually not. In most institutions the AI writing indicator and report are &lt;strong&gt;visible to instructors and administrators only&lt;/strong&gt;. University help pages such as &lt;a href="https://msd.unimelb.edu.au/belt/home/news/turnitin-ai-writing-detection-tool" rel="noopener noreferrer"&gt;the University of Melbourne's guide&lt;/a&gt; and &lt;a href="https://solomon.longwood.edu/dec/news/articles/ai-writing-detection-indicator-new-to-turnitin.php" rel="noopener noreferrer"&gt;Longwood University's announcement&lt;/a&gt; say students do not see the AI result, even when they can see their similarity (plagiarism) score. Turnitin's own help article &lt;a href="https://helpcenter.turnitin.com/hc/en-us/articles/46468418712461-Why-can-t-I-see-the-AI-writing-detection-report" rel="noopener noreferrer"&gt;Why can't I see the AI writing detection report?&lt;/a&gt; explains the account and file conditions behind a missing report.&lt;/p&gt;

&lt;p&gt;That is why many students want to check their work &lt;strong&gt;before&lt;/strong&gt; they submit: once it is in Turnitin, you usually cannot see what the AI indicator says.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your own PDF before you submit
&lt;/h2&gt;

&lt;p&gt;You cannot run Turnitin's AI check yourself, but you can see how an AI detector reads your text and fix problems early:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make sure the text is selectable.&lt;/strong&gt; If you exported from Word or Google Docs, it is. If you scanned pages, export the original document again instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count your words.&lt;/strong&gt; Under 300 words, there will be no AI writing report in Turnitin anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the file through a detector.&lt;/strong&gt; With the &lt;a href="https://proofai.in/document-detection" rel="noopener noreferrer"&gt;ProofAI document detector&lt;/a&gt; you can upload a PDF, DOCX, DOC or TXT file directly, without copy-pasting, and see an AI probability in seconds. It is free for a few checks a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the result as a signal, not a verdict.&lt;/strong&gt; If a section of your own writing looks "AI-like", look at why: very generic phrasing, repeated sentence patterns, or text you rewrote with a paraphrasing tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep your drafts.&lt;/strong&gt; Version history in Google Docs or Word, notes and outlines are the best evidence that the work is yours if a question ever comes up.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest limits of every AI detector
&lt;/h2&gt;

&lt;p&gt;No AI detector, Turnitin or ProofAI included, can prove who wrote a text. They estimate how likely it is that text was generated, based on patterns. Human writing can be flagged, especially very formal, formulaic or non-native English writing, and edited AI text can be missed. We explain why in &lt;a href="https://proofai.in/blog/ai-detector-false-positives-why-human-writing-gets-flagged-and-what-to-do" rel="noopener noreferrer"&gt;AI detector false positives: why human writing gets flagged&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So a score should start a conversation, not end one. If you are a student and a report surprises you, ask your instructor what they saw and share your drafts. If you are an instructor, look at the writing itself and talk to the student before drawing conclusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turnitin vs a self-check: when to use which
&lt;/h2&gt;

&lt;p&gt;Turnitin is sold to institutions, not individuals, so students in India and elsewhere normally meet it only through their college's submission portal. A self-check tool is for the step before that: making sure your file is readable, your text is your own, and nothing looks unexpected. We compare the two in more detail in &lt;a href="https://proofai.in/turnitin-alternative-india" rel="noopener noreferrer"&gt;our Turnitin alternative guide for India&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does converting Word to PDF hide AI writing from Turnitin?&lt;/strong&gt;&lt;br&gt;
No. A text-based PDF is checked the same way as the Word file it came from. The format does not change what the text says.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can Turnitin detect AI in a scanned PDF?&lt;/strong&gt;&lt;br&gt;
A scan with no text layer has no text for an AI writing check to read, so it may get no AI score. Your instructor can still read it and may ask for an editable version, so submitting a scan is not a safe way around anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many words does Turnitin need to check for AI?&lt;/strong&gt;&lt;br&gt;
At least 300 words of long-form prose, and no more than 30,000, according to Turnitin's published file requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Turnitin's AI check work for Hindi?&lt;/strong&gt;&lt;br&gt;
Not at the moment. Turnitin lists English, Spanish and Japanese as the supported languages for AI writing detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I see my Turnitin AI score as a student?&lt;/strong&gt;&lt;br&gt;
In most institutions, no. The AI indicator is usually visible only to instructors and administrators.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Turnitin can detect AI writing in a PDF just as it can in a Word file, as long as the PDF has enough readable prose in a supported language. File format is not a loophole. If you want peace of mind before a deadline, export a clean, text-based PDF, keep your drafts, and run a quick self-check with a document AI detector first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://proofai.in/blog/can-turnitin-detect-ai-in-a-pdf" rel="noopener noreferrer"&gt;ProofAI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>education</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Build a Discord Bot That Checks Messages for AI-Generated Text (discord.js v14)</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Sat, 24 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/build-a-discord-bot-that-checks-messages-for-ai-generated-text-discordjs-v14-1dmo</link>
      <guid>https://dev.to/proofai/build-a-discord-bot-that-checks-messages-for-ai-generated-text-discordjs-v14-1dmo</guid>
      <description>&lt;p&gt;Community managers are seeing more AI-generated posts: bot-like "introductions", copy-pasted answers in help channels, and contest entries that read suspiciously polished.&lt;/p&gt;

&lt;p&gt;In this tutorial we'll build a small &lt;strong&gt;Discord bot&lt;/strong&gt; with &lt;strong&gt;discord.js v14&lt;/strong&gt; that lets moderators right-click any message and choose &lt;strong&gt;"Check for AI"&lt;/strong&gt;. The bot replies privately (only the moderator sees it) with an AI-likelihood score.&lt;/p&gt;

&lt;p&gt;We deliberately &lt;strong&gt;don't&lt;/strong&gt; auto-scan every message or auto-delete anything. More on why at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20.6+ (for the built-in &lt;code&gt;--env-file&lt;/code&gt; flag)&lt;/li&gt;
&lt;li&gt;A Discord application and bot token from the &lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Developer Portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An AI detection API key (we use ProofAI's, but any detector with a probability score works)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;ai-check-bot &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ai-check-bot
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;module   &lt;span class="c"&gt;# we use ES modules and top-level await&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;discord.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env (never commit this)&lt;/span&gt;
&lt;span class="nv"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-bot-token
&lt;span class="nv"&gt;DISCORD_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-application-id
&lt;span class="nv"&gt;PROOFAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk_live_...
&lt;span class="nv"&gt;PROOFAI_ORIGIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://yourapp.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Register a message context-menu command
&lt;/h2&gt;

&lt;p&gt;Context-menu commands appear when you right-click a message → &lt;strong&gt;Apps&lt;/strong&gt;. No slash command typing, and no need for the privileged Message Content intent, because Discord passes the target message to your bot when a moderator uses the command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// register.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContextMenuCommandBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ApplicationCommandType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PermissionFlagsBits&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContextMenuCommandBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check for AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApplicationCommandType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDefaultMemberPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PermissionFlagsBits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ManageMessages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// moderators only by default&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applicationCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DISCORD_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registered 'Check for AI'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env register.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The detection helper
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// detect.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ai.proofai.in/api/v1/detect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;too_short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOFAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOFAI_ORIGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_probability&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short Discord messages ("lol same", "thanks!") can't be scored meaningfully, so we refuse anything under 50 words instead of returning a misleading number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The bot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bot.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GatewayIntentBits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MessageFlags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;detectAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./detect.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;GatewayIntentBits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Guilds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClientReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Logged in as &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InteractionCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMessageContextMenuCommand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commandName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check for AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Detection can take a few seconds, so acknowledge first (privately)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deferReply&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ephemeral&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;too_short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`This message has only &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; words. Detection needs at least 50 words to be meaningful.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Daily detection limit reached. Try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't check this message right now.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`**&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; likelihood of AI-generated text** (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%).\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="s2"&gt;`Treat this as one signal, not proof. Talk to the member before taking action.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env bot.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invite the bot with the &lt;code&gt;applications.commands&lt;/code&gt; and &lt;code&gt;bot&lt;/code&gt; scopes, then right-click a long message → &lt;strong&gt;Apps → Check for AI&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the reply is ephemeral
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MessageFlags.Ephemeral&lt;/code&gt; means only the moderator who ran the command sees the result. Posting "this person used AI!" publicly, based on a probability, is a fast way to embarrass a real member and lose trust in your mod team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things we deliberately didn't build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scanning every message.&lt;/strong&gt; It needs the privileged Message Content intent, burns your API quota on small talk, and creates a feeling of surveillance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-delete or auto-ban.&lt;/strong&gt; Detectors make mistakes. False positives are more common for non-native English writers and very formal writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public "AI" badges.&lt;/strong&gt; Same reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good uses: checking contest submissions, long "introductions" from brand-new accounts that look spammy, or answers copy-pasted into help channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas to extend it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log checks to a private &lt;code&gt;#mod-log&lt;/code&gt; channel for transparency within the mod team&lt;/li&gt;
&lt;li&gt;Add a per-moderator cooldown so nobody burns the quota&lt;/li&gt;
&lt;li&gt;Support attachments (PDF/DOCX) using the extraction code from our batch-scanning post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full API reference: &lt;a href="https://proofai.in/api-docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=discord_bot" rel="noopener noreferrer"&gt;ProofAI API docs&lt;/a&gt; · Try the &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=discord_bot" rel="noopener noreferrer"&gt;free web detector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you moderate a community? How are you handling AI-generated posts?&lt;/strong&gt; Tell us in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>javascript</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Designing a Fair AI Detection Feature: 7 UX Patterns That Prevent False Accusations</title>
      <dc:creator>ProofAI | AI Content Detector</dc:creator>
      <pubDate>Thu, 22 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/proofai/designing-a-fair-ai-detection-feature-7-ux-patterns-that-prevent-false-accusations-258f</link>
      <guid>https://dev.to/proofai/designing-a-fair-ai-detection-feature-7-ux-patterns-that-prevent-false-accusations-258f</guid>
      <description>&lt;p&gt;Adding an AI detector to your product takes an afternoon. Designing it so it doesn't &lt;strong&gt;falsely accuse a real person&lt;/strong&gt; takes much more thought.&lt;/p&gt;

&lt;p&gt;A student who gets flagged for an essay they wrote, or a candidate rejected because a tool said "AI", is a real harm. It's also a support ticket, a trust problem and, increasingly, a compliance risk. This post collects UX and product patterns we recommend for any feature that uses an AI detection score, whichever provider you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters: false positives are real
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In 2023, Stanford researchers (Liang et al., &lt;em&gt;"GPT detectors are biased against non-native English writers"&lt;/em&gt;) tested seven popular detectors on 91 TOEFL essays written by non-native English speakers. On average, the detectors misclassified &lt;strong&gt;61.3%&lt;/strong&gt; of those human-written essays as AI-generated, and almost all of the essays were flagged by at least one detector.&lt;/li&gt;
&lt;li&gt;OpenAI withdrew its own AI text classifier in July 2023, citing its low rate of accuracy.&lt;/li&gt;
&lt;li&gt;Formal, simple or heavily edited human writing can look statistically "predictable", which is exactly what detectors look for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means detection is useless. It means the &lt;strong&gt;interface around the score&lt;/strong&gt; decides whether the feature is helpful or harmful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Show confidence levels, not verdicts
&lt;/h2&gt;

&lt;p&gt;❌ &lt;code&gt;AI-GENERATED&lt;/code&gt; in a red badge&lt;br&gt;&lt;br&gt;
✅ &lt;code&gt;High likelihood of AI-generated text (review recommended)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A raw number like "87%" also misleads, because people read it as "87% of this was written by AI" or "87% chance this person cheated". Neither is true. Map the score into a few bands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;presentScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiProbability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiProbability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High likelihood of AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiProbability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mixed signals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;neutral&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optional_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low likelihood of AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune the thresholds for your use case. A plagiarism-sensitive exam needs different bands from a blog moderation queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: Refuse to score short text
&lt;/h2&gt;

&lt;p&gt;Short texts don't carry enough statistical signal. ProofAI's API docs recommend &lt;strong&gt;at least 50 words&lt;/strong&gt;, and many teams set a higher bar for high-stakes decisions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_WORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wordCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MIN_WORDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not enough text to analyze reliably. Add at least 50 words.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Showing "not enough text" is honest. Showing a confident score on two sentences is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Highlight passages, don't stamp documents
&lt;/h2&gt;

&lt;p&gt;Most real documents are mixed. Someone might write the body themselves and use AI to polish the intro. If your provider returns per-segment scores (ProofAI's API returns a &lt;code&gt;segments&lt;/code&gt; array), highlight passages instead of labelling the whole file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderSegments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;span class="seg seg--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color_label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" title="AI likelihood: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_prob&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;lt;/span&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reviewers can then ask a precise question ("Can you walk me through how you wrote this section?") instead of making a blanket accusation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Humans decide, the tool informs
&lt;/h2&gt;

&lt;p&gt;Never wire a detection score directly into an automatic, high-impact action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Auto-reject applications above 80%&lt;/li&gt;
&lt;li&gt;❌ Auto-fail assignments above a threshold&lt;/li&gt;
&lt;li&gt;✅ Add a "needs review" flag and send the item to a person&lt;/li&gt;
&lt;li&gt;✅ Combine the score with other evidence: drafts, version history, a short conversation, timed in-class writing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Log who reviewed each flagged item and what they decided. That audit trail protects both your users and your company.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: Explain it in the UI
&lt;/h2&gt;

&lt;p&gt;A small "How is this calculated?" link does a lot of work. Say plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the score measures (statistical patterns common in AI-generated text)&lt;/li&gt;
&lt;li&gt;What it &lt;em&gt;doesn't&lt;/em&gt; prove (authorship or intent)&lt;/li&gt;
&lt;li&gt;That results can be wrong, especially for short texts and non-native writing&lt;/li&gt;
&lt;li&gt;How a person can appeal or add context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 6: Give people a way to respond
&lt;/h2&gt;

&lt;p&gt;If you show a flag to the person who wrote the content, such as a student or candidate, give them a clear path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;See which passages were flagged&lt;/li&gt;
&lt;li&gt;Add context or upload drafts&lt;/li&gt;
&lt;li&gt;Request human review&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An appeal button costs little to build and prevents a lot of harm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 7: Be careful with privacy
&lt;/h2&gt;

&lt;p&gt;Detection means sending user content to a third party. Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the provider &lt;strong&gt;store&lt;/strong&gt; submitted content or use it for training? (ProofAI states it doesn't store scanned content.)&lt;/li&gt;
&lt;li&gt;Is the connection encrypted, and are API keys restricted to your domain?&lt;/li&gt;
&lt;li&gt;Do your terms tell users their content may be checked?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under rules like the EU AI Act's transparency obligations (applying from 2 August 2026) and India's amended IT Rules on synthetically generated content (in force since 20 February 2026), &lt;em&gt;how&lt;/em&gt; you handle AI-related content is getting more regulatory attention. Being transparent with users is the safest default.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Scores shown as confidence bands, not verdicts&lt;/li&gt;
&lt;li&gt;[ ] Minimum length enforced&lt;/li&gt;
&lt;li&gt;[ ] Passage-level highlighting where possible&lt;/li&gt;
&lt;li&gt;[ ] No automatic high-impact decisions&lt;/li&gt;
&lt;li&gt;[ ] "How this works" explanation in the UI&lt;/li&gt;
&lt;li&gt;[ ] Appeal or "add context" path&lt;/li&gt;
&lt;li&gt;[ ] Privacy and data-retention checked&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The detection model is only half the feature. The other half is the interface that decides how people act on it. If you get these patterns right, AI detection becomes a helpful review signal instead of a source of false accusations.&lt;/p&gt;

&lt;p&gt;If you want to try these patterns with real scores, the &lt;a href="https://proofai.in/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fair_ux" rel="noopener noreferrer"&gt;ProofAI free detector&lt;/a&gt; works without login, and our &lt;a href="https://proofai.in/api-docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fair_ux" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; show the segment-level response format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you ever been falsely flagged by an AI detector, or had to handle a complaint about one?&lt;/strong&gt; We'd like to hear how it went in the comments. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>ethics</category>
    </item>
  </channel>
</rss>
