<?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: Rahul Sharma</title>
    <description>The latest articles on DEV Community by Rahul Sharma (@rahulseo).</description>
    <link>https://dev.to/rahulseo</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%2F4019650%2F97848bea-39b2-4a6f-94fc-ff2d9814f784.png</url>
      <title>DEV Community: Rahul Sharma</title>
      <link>https://dev.to/rahulseo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahulseo"/>
    <language>en</language>
    <item>
      <title>Surfer SEO Alternatives Worth Testing in a Real Agency Workflow</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Fri, 17 Jul 2026 11:45:06 +0000</pubDate>
      <link>https://dev.to/rahulseo/surfer-seo-alternatives-worth-testing-in-a-real-agency-workflow-2hed</link>
      <guid>https://dev.to/rahulseo/surfer-seo-alternatives-worth-testing-in-a-real-agency-workflow-2hed</guid>
      <description>&lt;p&gt;Eighteen months back, I started looking for Surfer SEO alternatives not because Surfer was bad, but because I was paying for features I never touched while missing things I needed. Running a consultancy with clients in the UK, US, and Australia, every subscription has to earn its place. Surfer made sense at a certain scale, but plenty of my projects didn't sit at that scale.&lt;/p&gt;

&lt;p&gt;So I ran tests over a few months: real client briefs, real competition, real deadlines. Not a demo environment. Here's what came out of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why look for Surfer SEO alternatives at all?
&lt;/h2&gt;

&lt;p&gt;Surfer is genuinely good. The editor works, the keyword integration is decent, the AI features have gotten better. But three things kept frustrating me.&lt;/p&gt;

&lt;p&gt;The pricing is the first one. The entry-level plan has limits you don't discover until you're already committed, and for an agency handling several clients at once, you hit those limits fast.&lt;/p&gt;

&lt;p&gt;Second: the NLP recommendations don't always line up with what's ranking in competitive niches. Surfer gives you a score to optimize toward, but chasing that score doesn't guarantee results. I've watched articles score 80+ and still underperform pages that were optimized without any tool at all. That disconnect is frustrating when you're reporting to clients.&lt;/p&gt;

&lt;p&gt;Third thing, and I don't see this brought up much: when you're using Surfer across multiple clients, the content starts feeling formulaic only. Same structure, same flow, same everything. Clients in different industries end up with content that reads like it came from the same mold.&lt;/p&gt;

&lt;p&gt;That's basically why I started testing alternatives seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Surfer SEO alternatives I ran in live work
&lt;/h2&gt;

&lt;p&gt;These aren't impressions from a free trial with practice content. I used each of these on actual client projects over several months.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clearscope
&lt;/h3&gt;

&lt;p&gt;Clearscope is the one I recommend most for clients with larger content budgets. The NLP analysis is better than most tools I've tested, and the interface is clean enough that writers with no SEO background can use it without needing constant support.&lt;/p&gt;

&lt;p&gt;The D-through-A++ grading gives writers a clear target to hit, and the term suggestions are relevant, not just keyword variations. From my experience, pages optimized in Clearscope tend to hold rankings more consistently in that 30-90 day window than Surfer-optimized content does.&lt;/p&gt;

&lt;p&gt;The issue is cost. $170 to $350 per month depending on the plan, and the per-document model adds up fast if you're producing at volume. For established agencies with retainer clients, the math works. For project-based work, it's a harder sell.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Frase
&lt;/h3&gt;

&lt;p&gt;Frase is where I'd start if budget is the main constraint. Around $45/month for the base plan, and it combines research, outlining, and content optimization into one place, which is useful.&lt;/p&gt;

&lt;p&gt;The research side is the strongest feature. Frase pulls in competitor content, SERP data, and question-based insights automatically. That saves a meaningful amount of time at the brief-building stage, and for agencies producing content at volume, that time saving alone can justify the cost.&lt;/p&gt;

&lt;p&gt;The content optimizer is decent but not as precise as Clearscope. Topic suggestions can be noisy; you'll need to filter manually. For non-hyper-competitive niches, it's good enough. For tough competition, you'll want something more precise.&lt;/p&gt;

&lt;p&gt;One note on the AI writing assistant: the output needs significant editing. Use Frase for research and outlining, not for finished content.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. NeuronWriter
&lt;/h3&gt;

&lt;p&gt;This one surprised me. NeuronWriter is a Polish tool that doesn't get much mention in English-language SEO circles, but the SERP analysis is solid and the pricing is competitive.&lt;/p&gt;

&lt;p&gt;You're looking at $19-49/month for competitor content analysis, NLP suggestions, content templates, and internal linking recommendations in one platform. The internal linking feature itself is underrated. It flags relevant content on your site and suggests anchor text, which is a real time-saver for larger operations.&lt;/p&gt;

&lt;p&gt;The interface takes some adjustment. It's not as polished as Surfer or Clearscope. But once you know where things are, the workflow is fast. I've used it for clients in health, finance, and education niches with good results.&lt;/p&gt;

&lt;p&gt;For solo consultants and small agencies, NeuronWriter probably gives the best value among the Surfer SEO alternatives available right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. PageOptimizer Pro
&lt;/h3&gt;

&lt;p&gt;PageOptimizer Pro is different from the others. It's not a content editor. It's a technical on-page tool that tells you specifically what to change in existing content to improve rankings.&lt;/p&gt;

&lt;p&gt;The analysis is granular: it reads what's ranking for your target keyword and tells you how many times to use a given term, where to place it, and which related entities to include. More precise than most content editors, which is both the strength and the limitation.&lt;/p&gt;

&lt;p&gt;Where it works best is recovering underperforming pages. A page sitting between positions 8 and 15 can often be pushed into the top five with PageOptimizer Pro's recommendations faster than a full rewrite would achieve. From my experience, it makes most sense as a complement to your workflow rather than a replacement for a full content editor.&lt;/p&gt;

&lt;p&gt;Pricing is around $34/month. The interface isn't the most intuitive, but the recommendations are sound once you understand the logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. SEMrush Writing Assistant
&lt;/h3&gt;

&lt;p&gt;If you're already on SEMrush, try the Writing Assistant before adding another tool. It integrates directly with Google Docs and WordPress, which makes it easy to fold into existing client workflows.&lt;/p&gt;

&lt;p&gt;The suggestions are adequate for mid-competition content. Readability, tone, and SEO score in real time—useful when you're working with freelance writers who need structured guidance.&lt;/p&gt;

&lt;p&gt;It's not a replacement for Clearscope or Frase for serious optimization work. It's a bonus feature on your SEMrush subscription. Lightweight optimization, yes. Competitive content, no.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Dashword
&lt;/h3&gt;

&lt;p&gt;Dashword is the simplest tool in this group. Fast, around $39/month, clean reports.&lt;/p&gt;

&lt;p&gt;The keyword recommendations are easy to act on, and the lack of features makes it faster to use in practice. No complicated scoring systems, no bloated dashboards.&lt;/p&gt;

&lt;p&gt;For agencies that need a basic content editor with a low learning curve, it's a reasonable option. Not for highly competitive keywords; the NLP analysis isn't precise enough for that. But for medium-difficulty content, it does the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose between these Surfer SEO alternatives
&lt;/h2&gt;

&lt;p&gt;In practice, I use different tools for different situations.&lt;/p&gt;

&lt;p&gt;High-budget retainer clients in competitive niches: Clearscope.&lt;/p&gt;

&lt;p&gt;Budget-constrained clients or high-volume content: Frase or NeuronWriter.&lt;/p&gt;

&lt;p&gt;On-page audits and recovering underperforming pages: PageOptimizer Pro.&lt;/p&gt;

&lt;p&gt;Clients already on SEMrush: Writing Assistant for day-to-day work, then escalate to a dedicated tool if the numbers aren't moving.&lt;/p&gt;

&lt;p&gt;Basically, the tool should fit the project. There's no single Surfer SEO alternative that works for everything. I covered the full breakdown with ranking examples from my own client work in &lt;a href="https://rahulsharmaseo.substack.com/p/best-surfer-seo-alternatives-for-seo-writers-and-agencies" rel="noopener noreferrer"&gt;Best Surfer SEO Alternatives for SEO Writers and Agencies&lt;/a&gt; on Substack, worth reading if you want the data behind these decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for when evaluating alternatives
&lt;/h2&gt;

&lt;p&gt;After testing all of these, a few things matter more than any feature comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data accuracy.&lt;/strong&gt; Does the tool reflect what's ranking? Some tools base recommendations on models that don't align with real SERP results. Test it against pages you know before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writer-friendly interface.&lt;/strong&gt; If writers or clients are using the tool, it has to be simple enough without constant guidance. Complexity creates friction and errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing transparency.&lt;/strong&gt; In many cases, a tool looks affordable until you hit the document limits or need a feature behind a higher-tier plan. Know the real cost before you sign up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rankings over scores.&lt;/strong&gt; A tool that moves rankings matters more than one that gives your content a high score. Track performance over 60-90 days. That's the only meaningful evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;These Surfer SEO alternatives aren't fallback options. In many cases they're better choices depending on the project, budget, and content type.&lt;/p&gt;

&lt;p&gt;Clearscope for quality. Frase for value. NeuronWriter for cost-conscious operations. PageOptimizer Pro for a specific use case nothing else quite replaces.&lt;/p&gt;

&lt;p&gt;Test them on real content before deciding. The free trials are there for exactly this reason.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Google's helpful content update actually treats AI articles: 6 months of data</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:12:36 +0000</pubDate>
      <link>https://dev.to/rahulseo/how-googles-helpful-content-update-actually-treats-ai-articles-6-months-of-data-4oi4</link>
      <guid>https://dev.to/rahulseo/how-googles-helpful-content-update-actually-treats-ai-articles-6-months-of-data-4oi4</guid>
      <description>&lt;p&gt;December. A client in Melbourne calls me with a very specific complaint.&lt;/p&gt;

&lt;p&gt;Six months prior, he'd switched his content team fully to AI-generated articles, no real editing beyond a light proofread. Rankings stayed stable through November. Then a core update hit, and three of his top five pages dropped between 12 and 20 positions in two weeks.&lt;/p&gt;

&lt;p&gt;He wanted to know if the Google helpful content update AI articles situation was as bad as people were saying. I told him I'd been tracking this across other clients for a few months already, so I could give him actual numbers, not speculation.&lt;/p&gt;

&lt;p&gt;This is what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I was tracking and why
&lt;/h2&gt;

&lt;p&gt;Six client sites. Two in Australia, two in the UK, one in Canada, one in the US. Niches: legal services, SaaS, home improvement, a few others. Page counts ranged from around 80 to about 400 per site. Traffic tracked through Google Search Console and an independent rank tracker, cross-referenced both.&lt;/p&gt;

&lt;p&gt;Each site ran on a different content model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sites A and B:&lt;/strong&gt; Pure AI output. Minimal editing, light formatting tweaks, published mostly as-is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sites C and D:&lt;/strong&gt; AI drafts with serious human editing. Real client examples, case data, original analysis layered in before publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sites E and F:&lt;/strong&gt; Hybrid. Some pages fully human-written, some AI-assisted and properly edited, some raw AI with very little done to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My goal wasn't to prove or disprove that AI content gets penalized. I wanted to see if any pattern held consistently across different approaches, over enough time to mean something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first 90 days: what the data showed
&lt;/h2&gt;

&lt;p&gt;The first three months gave the clearest picture, but not the one I expected.&lt;/p&gt;

&lt;p&gt;Sites A and B, the minimal-edit AI sites, showed the most volatility. That said, "volatility" is the right word here, not "decline." Site A gained traffic in months one and two. The drop only started in month three, and even then, it was limited to specific page clusters.&lt;/p&gt;

&lt;p&gt;That made interpretation harder. A clean "AI content = penalty" story would show a straight-line drop from day one. What I saw instead was selective. Some AI pages held their rankings completely fine. Others dropped. And in many cases, the pages that dropped weren't the ones I'd have flagged as weak if I'd reviewed them manually.&lt;/p&gt;

&lt;p&gt;What emerged by month three: pages covering topics already well-served by established, authoritative sources dropped more often. Pages with operational detail, with specifics tied to real-world scenarios, were more stable, even the AI-written ones.&lt;/p&gt;

&lt;p&gt;Site B's decline was steadier and started earlier, around month two. That site was pushing 25 to 30 articles per month into a fairly narrow niche. By month three, topic overlap between articles was obvious. Google's consolidation behavior kicked in. Stronger pages started cannibalizing weaker ones within the same site itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Months 3 to 6: where things got more interesting
&lt;/h2&gt;

&lt;p&gt;The clearest positive signal came from Sites C and D.&lt;/p&gt;

&lt;p&gt;Both improved in rankings between months three and five, while Sites A and B were declining. Site C recovered pages that had dipped slightly in the earlier months. The format and structure weren't all that different from the other sites, but every article included real client case details, UK-market-specific examples, and original analysis. No generic filler.&lt;/p&gt;

&lt;p&gt;Site D was the most interesting of all six. It runs in a competitive SaaS niche with a lot of AI content already indexed. I expected it to struggle. Instead, it outperformed every other site in organic growth percentage from months four through six.&lt;/p&gt;

&lt;p&gt;Going through that content closely, the difference was specificity only. Every article had a clear angle grounded in observations from their actual product users, not generic "here's what industry experts say" framing.&lt;/p&gt;

&lt;p&gt;Site E split into two distinct phases. Months one through three: human-written pages stable or improving, AI-assisted pages mixed, minimal-edit AI pages mostly holding. Then month four, the minimal-edit AI pages started declining consistently, while the other two categories mostly held their ground.&lt;/p&gt;

&lt;p&gt;By month four, three tiers had basically emerged on Site E:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Human-written pages, stable to improving&lt;/li&gt;
&lt;li&gt;AI-assisted with heavy editing, mixed but mostly stable&lt;/li&gt;
&lt;li&gt;Minimal-edit AI pages, declining&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What kept repeating across Google helpful content update AI articles
&lt;/h2&gt;

&lt;p&gt;Six months in, a few things showed up consistently regardless of niche or geography.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specificity separated the good from the bad more than anything else.&lt;/strong&gt; Generic topic coverage performed worse than articles referencing specific scenarios, real data, or market-specific context, even when both types had similar structure and word count. In many cases, two articles on the same topic performed very differently based on this factor alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frequency wasn't the problem people thought it was.&lt;/strong&gt; High publishing volume alone didn't cause issues. High publishing volume on overlapping topics within a narrow niche, that caused problems. The issue wasn't how much content was going out. It was if the content was competing with itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E-E-A-T signals correlated with stability.&lt;/strong&gt; Sites with strong author pages, proper citations, structured original data held up better. Not a surprising finding on its own, but the correlation was consistent enough across the data to note explicitly. It wasn't a one-site anomaly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recovery was possible, and one site proved it.&lt;/strong&gt; Site A ran a content audit in month four and removed about 30 pages with high topic overlap and low engagement metrics. By month six, the remaining pages had partially bounced back. Not full recovery. Basically what you'd expect from Google reassessing a site after thin content is taken down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What got hit versus what survived
&lt;/h2&gt;

&lt;p&gt;From my experience, the Google helpful content update AI articles that dropped hardest shared these characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No original angle or data. Generic information restructured in AI format with nothing added.&lt;/li&gt;
&lt;li&gt;Thin coverage. Articles under 600 words chasing competitive terms without real depth.&lt;/li&gt;
&lt;li&gt;Internal self-competition. Multiple pages targeting near-identical intent within the same site.&lt;/li&gt;
&lt;li&gt;No real-world specificity. Advice that could apply to any market, any situation, no context given.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What survived looked different: articles with referenced data, genuine first-person perspective, substantial depth even when AI-assisted, and a clear differentiation point from what already existed in the SERPs.&lt;/p&gt;

&lt;p&gt;The production method wasn't the deciding factor, from what I can tell. Useful, specific, credible content survived. Content without those qualities dropped, regardless of how it was written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Google penalize AI content, or just bad content?
&lt;/h2&gt;

&lt;p&gt;This is what I get asked most. Here's my read after six months watching real sites.&lt;/p&gt;

&lt;p&gt;Google's helpful content system isn't an AI detection tool. It's a quality classification system that's gotten better at identifying content with low information gain, poor specificity, and weak engagement signals. Those are quality signals, not origin signals.&lt;/p&gt;

&lt;p&gt;AI content produced at scale without editorial control tends to fail on all three. That's the source of the correlation between raw AI content and helpful content system penalties. But the cause is quality, not origin.&lt;/p&gt;

&lt;p&gt;The distinction matters when you're deciding how to manage your content program. Spending effort trying to hide that content is AI-generated is solving the wrong problem. Spending that same effort making content specific, useful, and genuinely differentiated, that's what moves rankings.&lt;/p&gt;

&lt;p&gt;I've written more on this question with broader data from the SEO community in my piece on &lt;a href="https://rahulsharmaseo.substack.com/p/does-google-penalize-ai-written-content" rel="noopener noreferrer"&gt;whether Google penalizes AI-written content&lt;/a&gt;. It goes beyond my client set into what other practitioners have been tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm doing differently now
&lt;/h2&gt;

&lt;p&gt;The audit process has changed for clients running high volumes of AI content. Every three months, we do a content consolidation check: identify topic clusters, flag pages with overlapping intent and below-baseline performance, then improve or remove them. Simple, but it wasn't being done with enough regularity before.&lt;/p&gt;

&lt;p&gt;The briefing process for new AI content has changed too. Every draft now starts with a defined angle: a real case from the client's business, a market-specific observation, or an industry data point. Generic overview articles don't get commissioned without a differentiation plan attached. That rule alone has changed the quality of what gets written.&lt;/p&gt;

&lt;p&gt;At the site level, topical authority signals are getting more attention. Being the most comprehensive resource on a narrower set of topics outperforms being a moderate resource on a wide range, especially in competitive niches. From my experience, this gap has been widening since the HCU started rolling out.&lt;/p&gt;

&lt;p&gt;None of these are dramatic changes. It's applying quality standards that good SEOs have recommended for years, but with more discipline in checking if the AI-assisted content meets those standards, rather than assuming it does.&lt;/p&gt;

&lt;p&gt;One thing the data reinforced: backlinks still interact with content quality signals in ways that matter. If a page doesn't have the quality signals to support it, strong backlinks don't move things the way they used to. I went into more detail on &lt;a href="https://rahulsharmaseo.substack.com/p/the-backlinks-that-actually-move" rel="noopener noreferrer"&gt;which backlinks are still moving rankings in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For anyone using AI content for guest posts, there's an additional layer because you're being evaluated by an editor in addition to an algorithm. The rejection patterns I've seen are &lt;a href="https://rahulsharmaseo.substack.com/p/ai-guest-posts-in-2026-what-editors" rel="noopener noreferrer"&gt;covered in this piece on what editors are rejecting in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And for the broader AI content workflow, the process I've settled on is detailed in &lt;a href="https://rahulsharmaseo.substack.com/p/a-workflow-to-make-your-ai-content" rel="noopener noreferrer"&gt;this overview of my current AI SEO content workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Six months of data won't give you definitive answers on everything. But it does show patterns. Content quality beats content origin. Specificity beats coverage volume. That holds for AI content and human content alike.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why I Moved Away from My Old AI SEO Content Stack and What I Replaced It With</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:19:49 +0000</pubDate>
      <link>https://dev.to/rahulseo/why-i-moved-away-from-my-old-ai-seo-content-stack-and-what-i-replaced-it-with-39bb</link>
      <guid>https://dev.to/rahulseo/why-i-moved-away-from-my-old-ai-seo-content-stack-and-what-i-replaced-it-with-39bb</guid>
      <description>&lt;p&gt;About fourteen months ago, I counted the browser tabs I had open to produce a single SEO article for a client. There were eight. ChatGPT for the draft. A humanizer. A second humanizer I was testing because the first one kept making things worse. An AI detector. Surfer SEO. Ahrefs. The client's brief. And a Grammarly tab I'd opened and then forgotten about.&lt;/p&gt;

&lt;p&gt;That's not a workflow. That's just chaos with steps in between.&lt;/p&gt;

&lt;p&gt;My AI SEO content stack at the time looked impressive on paper. In practice, it was producing content that kept getting flagged by editors, eating more time than I was billing, and giving me no real way to tell which part was failing. Rebuilding it took about six weeks of testing and a few uncomfortable client conversations. What came out the other side is simpler, faster, and it works.&lt;/p&gt;

&lt;p&gt;This is what changed and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What my old AI SEO content stack looked like
&lt;/h2&gt;

&lt;p&gt;The original stack had four layers. Ahrefs for keyword research and backlink analysis. ChatGPT for drafts, using custom prompts I'd been refining for two years. A standalone humanizer to clean up the AI patterns. Surfer SEO for on-page optimization and content scoring.&lt;/p&gt;

&lt;p&gt;On paper, that covered everything. In practice, none of these tools talked to each other. The workflow was entirely manual handoffs: write in ChatGPT, copy to humanizer, copy to detector, edit, paste back, run Surfer, edit again. By the time a piece was done, I'd touched it six or seven times across four different windows.&lt;/p&gt;

&lt;p&gt;The humanizer was the specific weak point. It handled some passes fine. Others it made worse, introducing phrasing that sounded awkward or shifting the meaning of sentences in ways I'd only catch after reading the whole thing again. From my experience, that inconsistency is the part that kills content quality at scale. You can't build a reliable system on a tool that produces unpredictable output.&lt;/p&gt;

&lt;p&gt;There was also no feedback loop. I couldn't measure whether any step of the process was working until an editor told me it wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it started breaking down
&lt;/h2&gt;

&lt;p&gt;The clearest signal came from a professional services client in the UK. We were running eight to ten articles per month, a mix of blog content and guest post outreach. Three pitches in a row came back rejected. Two included explicit notes that the content seemed AI-generated.&lt;/p&gt;

&lt;p&gt;I ran those rejected pieces through a proper AI detector. They were scoring 80 to 90 percent AI. The humanizer had run on all of them. It hadn't done what I assumed it was doing.&lt;/p&gt;

&lt;p&gt;That alone was enough to start testing replacements. But the deeper problem was what I mentioned: no feedback loop. Once I started looking carefully, I realized I had no way to tell whether the humanizer was working short of submitting to an editor and waiting for a rejection. That's not a testing methodology. That's just finding out after the damage is done.&lt;/p&gt;

&lt;p&gt;I'd been assuming the humanizer was handling it because I hadn't checked systematically. I did a few spot checks when I first started using it, saw decent results, and moved on. Testing each step in isolation is not the same as testing whether the whole chain produces output that passes real editorial review.&lt;/p&gt;

&lt;p&gt;So I ran a batch test. I took 20 pieces from the previous quarter, ran them all through a detection tool, and mapped the scores by content type and which tools had processed them. Average score after humanization: 73 percent AI. The only pieces below 50 percent were ones I'd manually rewritten myself, which defeated the purpose of having AI in the workflow at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I approached rebuilding the AI SEO content stack
&lt;/h2&gt;

&lt;p&gt;The first thing I did was separate the problem into two questions: what functional layers did I need, and which tools were genuinely doing their job?&lt;/p&gt;

&lt;p&gt;The functional layers were clear enough. Keyword research, AI-assisted drafting, humanization, detection, and on-page optimization. That hadn't changed. What needed to change was the tools filling those layers and the order of operations between them.&lt;/p&gt;

&lt;p&gt;I spent about six weeks testing alternatives, mostly focused on the humanization layer because that was the clear failure point. The full testing notes are in a separate post for anyone who wants the breakdown: &lt;a href="https://rahulsharmaseo.substack.com/p/every-ai-tool-i-tested-for-seo-content" rel="noopener noreferrer"&gt;every AI tool I tested for SEO content in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The short version: the quality gap between humanizers is significant. Most tools do word-level replacement. They swap vocabulary and adjust sentence structure slightly. Detection tools look for writing patterns and phrasing structures, not just word frequency. Surface-level word swapping doesn't move the score much.&lt;/p&gt;

&lt;p&gt;The tools that pass editorial review do structural rewriting: they change how ideas are expressed, not just which words carry them. From my experience, that's the distinction worth looking for. If a tool is just swapping synonyms, that's not humanization in any useful sense. Structural rewriting changes the rhythm of paragraphs, the way arguments are built, the variation in sentence length. Those are the things editors and detection tools notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new setup, piece by piece
&lt;/h2&gt;

&lt;p&gt;The rebuilt AI SEO content stack has the same four functional layers. The tools and sequence are different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyword research:&lt;/strong&gt; Ahrefs. No change here. It's the strongest tool I've found for keyword difficulty, SERP analysis, and backlink work, and I use it on every client project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI drafting:&lt;/strong&gt; Still ChatGPT, but with tighter brief templates. The biggest improvement wasn't the tool itself, it was the prompts. I now specify persona, content structure, which sections need specific examples, and what claims need sourcing. Generic prompts produce generic content. The prompt is basically half the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humanization and detection:&lt;/strong&gt; This is where the biggest change happened. In the old stack, these were separate steps with separate tools. In the new stack, I use a single tool that handles both inside the same editor. Write, humanize, check the detection score, decide if another pass is needed, all in one place. That change alone cut the time I spent on this step by roughly half, and it built in the feedback loop that was missing before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-page optimization:&lt;/strong&gt; Surfer SEO. Still the most reliable option for real-time keyword guidance and content scoring against live SERPs.&lt;/p&gt;

&lt;p&gt;The improvement is the integration. There's nothing exotic about any of these tools individually. What changed was that the workflow now has a visible feedback loop at the most critical step. The score tells me whether the content is ready. I'm not submitting and waiting to find out.&lt;/p&gt;

&lt;p&gt;The complete workflow, including how the tools connect to each other in practice, is in an earlier post: &lt;a href="https://rahulsharmaseo.substack.com/p/ai-seo-content-workflow" rel="noopener noreferrer"&gt;the complete AI SEO content workflow for agencies in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after the switch
&lt;/h2&gt;

&lt;p&gt;Detection scores improved immediately. Before the rebuild, content was averaging 70 to 80 percent AI after humanization. After, that dropped to 20 to 30 percent on the same detection tools. For high-stakes content like guest posts headed to editorial sites, additional rewrite passes bring it lower.&lt;/p&gt;

&lt;p&gt;Guest post acceptance rates improved over the following quarter. Not dramatically in the first month, but consistently. The rejection rate from editorial sites dropped from roughly one in three to around one in nine. For a solo consultant where guest post placements are a core deliverable for backlink clients, that shift is meaningful.&lt;/p&gt;

&lt;p&gt;The second change was less measurable but worth mentioning: I stopped second-guessing the stack. The old setup had too many disconnected steps with no feedback. Every rejection left me guessing which part had failed. Now the stack tells me whether the content meets the bar before anything goes out the door. If the score is too high, I know immediately and do another pass. That certainty has its own value.&lt;/p&gt;

&lt;p&gt;For the tool-by-tool breakdown in more detail, I've covered this in two other posts: &lt;a href="https://rahulsharmaseo.substack.com/p/best-ai-seo-content-tools" rel="noopener noreferrer"&gt;the best AI SEO content tools I've tested and ranked&lt;/a&gt; and &lt;a href="https://rahulsharmaseo.substack.com/p/the-seo-tools-i-actually-use-every" rel="noopener noreferrer"&gt;the SEO tools I actually use every month and what I've cut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone building their AI SEO content stack today
&lt;/h2&gt;

&lt;p&gt;Build the feedback loop before you optimize anything else.&lt;/p&gt;

&lt;p&gt;Most people building a content stack focus on output: which AI writer produces the best prose, which SEO platform has the best keyword data. Those things matter. But if you can't measure whether the final output is performing, you're running the system blind.&lt;/p&gt;

&lt;p&gt;For SEO content, the minimum viable feedback loop is: humanize, detect, check the score. If it's still too high, you need to know that before the piece goes to an editor or gets published, not after.&lt;/p&gt;

&lt;p&gt;Sequence matters as much as the tools. From my experience, most AI content quality issues don't come from using the wrong tool. They come from doing steps in the wrong order, or skipping a step because you assumed another tool handled it. Write before you optimize. Humanize before you run detection. And check the score before you decide the content is done.&lt;/p&gt;

&lt;p&gt;It's simple only when you lay it out that way. But in many cases, consultants are doing at least one of these steps out of order, or they've skipped detection entirely because the humanizer looked fine when they first tested it.&lt;/p&gt;

&lt;p&gt;The tools in my current stack aren't exotic. Ahrefs is the standard. ChatGPT is everywhere. Surfer is a common choice in this space. The real change was structural: a workflow where each step connects to the next, where I can see the output quality at the point that matters most before anything leaves my hands.&lt;/p&gt;

&lt;p&gt;The stack matters less than the sequence, and the sequence matters less than having a feedback loop built in somewhere. Without that, you're testing your quality control by submitting to editors and clients and finding out the hard way.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>seo</category>
      <category>writing</category>
    </item>
    <item>
      <title>AI content detection script Python: how I test against 3 detectors at once</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Tue, 07 Jul 2026 13:35:57 +0000</pubDate>
      <link>https://dev.to/rahulseo/ai-content-detection-script-python-how-i-test-against-3-detectors-at-once-4h4i</link>
      <guid>https://dev.to/rahulseo/ai-content-detection-script-python-how-i-test-against-3-detectors-at-once-4h4i</guid>
      <description>&lt;p&gt;Last October a client forwarded me a screenshot. She'd written a guest post with an AI tool, submitted it to a UK marketing blog, and got the rejection email. The editor wrote: "This reads like it was generated by ChatGPT. We don't accept AI content."&lt;/p&gt;

&lt;p&gt;What annoyed both of us: she'd tested it through one detector. Came back clean. That was enough for her.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;I've seen the same piece of content flagged as 85% AI by GPTZero and cleared as human by another tool in the same hour. That inconsistency is not unusual. It's how these tools work. Different models, different signals, no common standard. Editors use whatever they use, and you don't get to pick. So I built a small AI content detection script in Python that runs content through three detectors and returns a comparison. It's part of my workflow now for every client batch before anything goes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one AI detector isn't the full picture
&lt;/h2&gt;

&lt;p&gt;The three tools I use in this script approach detection differently. GPTZero measures perplexity and burstiness — basically, how surprising each word is and how much sentence length varies. Sapling AI is a transformer model fine-tuned on labeled human and AI text. Originality.ai layers its own classifier on top of plagiarism checking. These aren't variations on the same method. They genuinely catch different things.&lt;/p&gt;

&lt;p&gt;That matters. If content passes GPTZero but the editor is running Originality.ai on submissions, you've wasted a guest post slot. From my experience with clients doing guest posting at volume, that's a real thing that happens. Not rarely either.&lt;/p&gt;

&lt;p&gt;I looked at this more closely in my &lt;a href="https://rahulsharmaseo.substack.com/p/gptzero-alternatives" rel="noopener noreferrer"&gt;breakdown of GPTZero alternatives&lt;/a&gt; — specifically for freelancers targeting UK, US, and Australian markets where editorial standards tend to be stricter. Detector disagreement is the norm on borderline content, not the exception.&lt;/p&gt;

&lt;p&gt;Running three together gives you something more useful: a pattern. If all three score content above 70% AI, the writing has problems that are recognisable across different detection methods. If two say human and one says AI, you're looking at something borderline, worth a second pass. A consensus tells you more than any single score, basically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this AI content detection script Python does
&lt;/h2&gt;

&lt;p&gt;The script accepts text strings or reads from a file, sends each piece to GPTZero, Sapling AI, and Originality.ai, calculates the average AI probability across all three, and outputs a side-by-side score comparison to the terminal. No web interface, no dashboard. Just a clean comparison you can run in a terminal from anywhere.&lt;/p&gt;

&lt;p&gt;Adding a fourth detector or pulling from a CSV is a few extra lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Keys required for each service. All three have free tiers or trials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPTZero API&lt;/strong&gt; — apply at gptzero.me/api&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sapling AI&lt;/strong&gt; — free key at sapling.ai&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Originality.ai&lt;/strong&gt; — paid but low per-scan; sign up at originality.ai&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install &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;requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python 3.10+ for the type hints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full AI content detection script Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIDetectorBatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Batch AI content detection across GPTZero, Sapling AI, and Originality.ai.
    &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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gptzero_key&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="n"&gt;originality_key&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="n"&gt;sapling_key&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gptzero_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gptzero_key&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;originality_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;originality_key&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sapling_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sapling_key&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_gptzero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;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://api.gptzero.me/v2/predict/text&lt;/span&gt;&lt;span class="sh"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gptzero_key&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-Type&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;application/json&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;Accept&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;application/json&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;payload&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;document&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multilingual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;response&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;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="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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;response&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&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="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;detector&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;GPTZero&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;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completely_generated_prob&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;classification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predicted_class&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_sapling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;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://api.sapling.ai/api/v1/aidetect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;payload&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;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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sapling_key&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;response&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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;response&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="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;detector&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;Sapling AI&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;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classification&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="k"&gt;if&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="mf"&gt;0.5&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;human&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_originality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;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://api.originality.ai/api/v1/scan/ai&lt;/span&gt;&lt;span class="sh"&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-OAI-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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;originality_key&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-Type&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;application/json&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;payload&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aiModelVersion&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;1&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;storeScan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;response&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;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="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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;response&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;ai_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="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;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;detector&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;Originality.ai&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;ai_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classification&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ai_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;human&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run all three detectors on a single piece of text.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_gptzero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_sapling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_originality&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;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checks&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="n"&gt;results&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="nf"&gt;fn&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;results&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;detector&lt;/span&gt;&lt;span class="sh"&gt;"&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;__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;error&lt;/span&gt;&lt;span class="sh"&gt;"&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;e&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="n"&gt;delay&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;results&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batch_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&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;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run all detectors on a list of text strings.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;all_results&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&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;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&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;texts&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;span class="n"&gt;entry&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;text_preview&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&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="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="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;...&lt;/span&gt;&lt;span class="sh"&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;else&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;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_all&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;delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&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;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_ai_probability&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="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="n"&gt;r&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&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;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;all_results&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="n"&gt;entry&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;all_results&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Print a formatted comparison table to stdout.&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;batch_output&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="se"&gt;\n&lt;/span&gt;&lt;span class="si"&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;65&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="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;Text: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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_preview&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="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="si"&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;65&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&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;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;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;detector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  ERROR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
            &lt;span class="n"&gt;filled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pct&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="n"&gt;bar&lt;/span&gt; &lt;span class="o"&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;filled&lt;/span&gt; &lt;span class="o"&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="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;filled&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;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&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;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;detector&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_ai_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_ai_probability&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="mi"&gt;100&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Average&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&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;output_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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Export results to JSON for logging or downstream analysis.&lt;/span&gt;&lt;span class="sh"&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="n"&gt;output_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;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;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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;indent&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;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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Results saved to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_path&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="c1"&gt;# Entry point
&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="n"&gt;detector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AIDetectorBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;gptzero_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GPTZERO_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;your_gptzero_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;originality_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ORIGINALITY_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;your_originality_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;sapling_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAPLING_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;your_sapling_key&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;sample_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;The integration of artificial intelligence into content marketing workflows &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;has fundamentally transformed how organizations approach content creation at scale. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;By leveraging large language models, teams can produce high-quality written materials &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in a fraction of the time previously required, enabling unprecedented levels of output &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;while maintaining consistency in brand voice and messaging across all channels.&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Last year I made a mistake that cost me three months of work. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A client in Manchester needed product descriptions for over 400 items. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I used AI to draft the lot, did a quick read-through, and sent them over. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The site owner rejected all of them after their in-house editor flagged them as AI. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I had not run a single detection check before delivery. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;That was my fault, and I still think about it when setting up any content workflow.&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;render_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;save_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detection_results.json&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;h2&gt;
  
  
  How the script is structured
&lt;/h2&gt;

&lt;p&gt;One class, &lt;code&gt;AIDetectorBatch&lt;/code&gt;. A method each for GPTZero, Sapling, and Originality. Then &lt;code&gt;check_all&lt;/code&gt; and &lt;code&gt;batch_check&lt;/code&gt; sit on top of those.&lt;/p&gt;

&lt;p&gt;The design decision I'd highlight: all three detector methods return the same dict shape — &lt;code&gt;detector&lt;/code&gt;, &lt;code&gt;ai_probability&lt;/code&gt;, &lt;code&gt;classification&lt;/code&gt;. That's it. I kept it uniform on purpose. Means &lt;code&gt;render_results&lt;/code&gt; and the averaging logic don't need to know which detector produced which result. One loop handles all three.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;check_all&lt;/code&gt; runs the detectors sequentially with a delay. The default is 1.5 seconds between calls. Don't remove this unless you're on a paid plan with higher rate limits. I've hit limits on the free tiers when batching articles quickly, and the script handles &lt;code&gt;HTTPError&lt;/code&gt; gracefully — it logs the error and keeps going rather than crashing the whole batch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;batch_check&lt;/code&gt; is the main entry point for most use. It takes a list of strings, runs &lt;code&gt;check_all&lt;/code&gt; on each, stores the preview plus all results, and calculates the average probability across detectors that returned successfully. So if Sapling's API goes down mid-batch, the average is still computed from the two that responded.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;render_results&lt;/code&gt; just makes terminal output readable. ASCII bars instead of raw floats. When you're scanning 20 articles in one go, that visual difference matters more than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the sample output looks like
&lt;/h2&gt;

&lt;p&gt;The two texts in the sample are intentional extremes. First one is generic AI filler — "transformational", "at scale", "brand voice across all channels." All three detectors pick it up, consistently above 70%. Second one is a personal story with a specific city, a specific client mistake, a specific volume of work. Scores come in below 30% across all three.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing 1/2...
Processing 2/2...

=================================================================
Text: The integration of artificial intelligence into content marketing workflo...
=================================================================
  GPTZero          [████████████████░░░░]   82.3%  (AI)
  Sapling AI       [██████████████░░░░░░]   71.8%  (AI)
  Originality.ai   [███████████████░░░░░]   78.5%  (AI)

  Average           77.5% AI probability

=================================================================
Text: Last year I made a mistake that cost me three months of work. A client in...
=================================================================
  GPTZero          [████░░░░░░░░░░░░░░░░]   21.4%  (HUMAN)
  Sapling AI       [███░░░░░░░░░░░░░░░░░]   17.2%  (HUMAN)
  Originality.ai   [█████░░░░░░░░░░░░░░░]   27.9%  (HUMAN)

  Average           22.2% AI probability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agreement across all three is the signal to watch. One outlier is noise. Three outliers is a problem in the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading from a file instead of hardcoded strings
&lt;/h2&gt;

&lt;p&gt;For actual content batches, I use a plain text file with &lt;code&gt;---&lt;/code&gt; as a separator between articles:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_texts_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&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="n"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&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="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="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&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;content&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&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="n"&gt;t&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;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="n"&gt;content&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="n"&gt;delimiter&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;t&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;span class="n"&gt;texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_texts_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;articles.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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batch_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;render_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I save results to JSON on every run too. Lets me compare scores between draft versions. &lt;a href="https://rahulsharmaseo.substack.com/p/walter-writes-api" rel="noopener noreferrer"&gt;When I wrote about API integrations for content pipelines&lt;/a&gt;, this score-tracking across revisions is the part people actually found useful — more than the integration setup itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the scores correctly
&lt;/h2&gt;

&lt;p&gt;The number that matters is the average, not any single detector's output. I had a situation where GPTZero flagged an article at 80%, Sapling cleared it, and Originality put it at 85%. The client wanted to publish based on the Sapling pass. The piece got rejected by the site editor. &lt;a href="https://rahulsharmaseo.substack.com/p/does-walter-writes-pass-originalityai" rel="noopener noreferrer"&gt;I documented similar inconsistencies&lt;/a&gt; in detail — same content, same day, wildly different individual scores.&lt;/p&gt;

&lt;p&gt;What to look for: are all three pointing in the same direction? That's your real verdict.&lt;/p&gt;

&lt;p&gt;On thresholds — different editors have different cutoffs. From my experience, under 30% average across all three is a safe zone for editorial submissions. Some sites are stricter, some are looser. Under 20% and I've never had a detection-based rejection, from my experience.&lt;/p&gt;

&lt;p&gt;Short text gives bad results, basically across every detector I've tried. Under 150 words, the classifications start behaving randomly. Test full articles or at least full sections.&lt;/p&gt;

&lt;p&gt;One more thing: both GPTZero and Originality.ai update their models without much announcement. A score from three months ago doesn't hold. Run this check close to the submission date, not once during initial drafting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to extend this
&lt;/h2&gt;

&lt;p&gt;Winston AI has an API and is used specifically by a lot of academic editors. Worth adding as a fourth check if content goes to research or educational publications.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;argparse&lt;/code&gt; turns this into a proper CLI tool — call it with a file path from the terminal rather than editing the script. &lt;code&gt;gspread&lt;/code&gt; writes results to a Google Sheet in a few lines if you're managing content across multiple client accounts. And if Originality.ai is timing out during busy hours, wrapping &lt;code&gt;check_originality&lt;/code&gt; in a retry decorator with exponential backoff fixes most of those failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection is the starting point, not the end
&lt;/h2&gt;

&lt;p&gt;The script tells you where the problem is. Fixing it is separate work.&lt;/p&gt;

&lt;p&gt;Content scoring above 60% across all three detectors tends to have the same fingerprints: sentence lengths that barely vary, transitions like "furthermore" and "it is worth noting", paragraphs that state a point and then restate it from a slightly different angle. Those are the specific patterns to go after when revising.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rahulsharmaseo.substack.com/p/humanize-ai-alternatives" rel="noopener noreferrer"&gt;My roundup of humanizer tools&lt;/a&gt; covers what fits into the pipeline after detection flags something — if that's the next step.&lt;/p&gt;

&lt;p&gt;Modify the script as needed. Most people add a detector or change the output format within the first week. If parsing breaks after a few weeks, check Originality.ai first. That response shape changes most often.&lt;/p&gt;

&lt;p&gt;Leave a comment if you extend it somewhere interesting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>writing</category>
    </item>
  </channel>
</rss>
