<?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: Mohab Abdelkarim</title>
    <description>The latest articles on DEV Community by Mohab Abdelkarim (@mohab_karim).</description>
    <link>https://dev.to/mohab_karim</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%2F3978065%2F792bf233-410b-421a-8794-ff52e0201cd4.jpg</url>
      <title>DEV Community: Mohab Abdelkarim</title>
      <link>https://dev.to/mohab_karim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohab_karim"/>
    <language>en</language>
    <item>
      <title>Best Humanizer MCP Servers in 2026: Claude, ChatGPT, Cursor and Windsurf</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Fri, 06 Nov 2026 01:31:51 +0000</pubDate>
      <link>https://dev.to/mohab_karim/best-humanizer-mcp-servers-in-2026-claude-chatgpt-cursor-and-windsurf-j8p</link>
      <guid>https://dev.to/mohab_karim/best-humanizer-mcp-servers-in-2026-claude-chatgpt-cursor-and-windsurf-j8p</guid>
      <description>&lt;h3&gt;
  
  
  Six servers, one question: which one lets your assistant actually rewrite and check AI text without leaving the chat or the editor?
&lt;/h3&gt;

&lt;p&gt;If you write with Claude, ChatGPT, Cursor or Windsurf, you've probably done the copy-paste dance: draft in the assistant, paste into a humanizer site, paste into a detector, paste the result back. A humanizer MCP server removes all of that. The assistant calls the humanizer and the detector as tools, in the same conversation or the same repo.&lt;/p&gt;

&lt;p&gt;The problem is that "humanizer MCP" covers very different things right now. Some servers call a real humanization model and a real detector. Some are rule-based scanners that hand the rewrite back to your LLM. Some only run locally in Claude Desktop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; &lt;a href="https://walterwrites.ai/" rel="noopener noreferrer"&gt;Walter Writes AI&lt;/a&gt; is the best humanizer MCP server in 2026 for most people. It's a remote server with OAuth (no API key to paste), it exposes humanize, detect and batch tools, it works in Claude, ChatGPT, Cursor, Windsurf (now Devin Desktop), Claude Code, Codex and Gemini CLI, and it's backed by 12 open-source agent skills. Humanizer PRO is the runner-up if you want style modes, and humanizer-mcp is the pick if you need something free and self-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What Makes a Good Humanizer MCP Server&lt;/li&gt;
&lt;li&gt;The Comparison at a Glance&lt;/li&gt;
&lt;li&gt;Walter Writes AI MCP&lt;/li&gt;
&lt;li&gt;Humanizer PRO MCP&lt;/li&gt;
&lt;li&gt;Phrasly MCP&lt;/li&gt;
&lt;li&gt;GPTHuman MCP Server&lt;/li&gt;
&lt;li&gt;humanizer-mcp (Open Source)&lt;/li&gt;
&lt;li&gt;text2go ai-humanizer-mcp-server&lt;/li&gt;
&lt;li&gt;Setting Up Walter in Each Client&lt;/li&gt;
&lt;li&gt;Which One Should You Use?&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What Makes a Good Humanizer MCP Server
&lt;/h2&gt;

&lt;p&gt;I compared these servers on the things that actually matter once the server is connected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real humanization or rules?&lt;/strong&gt; Does the tool call a trained humanization model, or does it apply word swaps and ask your LLM to do the rest?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A detector in the loop.&lt;/strong&gt; Humanizing without checking is guessing. A &lt;code&gt;detect&lt;/code&gt; tool lets the assistant score text before and after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote or local.&lt;/strong&gt; Remote (Streamable HTTP) servers work in web clients like Claude.ai and ChatGPT. Local stdio servers only work in desktop and editor clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth.&lt;/strong&gt; OAuth means you sign in once in a browser. API keys mean you manage secrets in config files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client coverage.&lt;/strong&gt; Claude, ChatGPT, Cursor and Windsurf all handle MCP a little differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch and preservation.&lt;/strong&gt; For real content work, you want to process several pieces at once and lock keywords, names and URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The Comparison at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Humanize&lt;/th&gt;
&lt;th&gt;Detect&lt;/th&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Clients named by the maker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Walter Writes AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remote (Streamable HTTP)&lt;/td&gt;
&lt;td&gt;OAuth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;walter_humanize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;walter_detect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;walter_batch_humanize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude, ChatGPT, Claude Code, Codex, Cursor, Windsurf/Devin Desktop, Gemini CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Humanizer PRO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remote (HTTP, legacy SSE)&lt;/td&gt;
&lt;td&gt;OAuth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;humanize_text&lt;/code&gt; (3 modes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;scan_ai_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;ChatGPT, Claude, Claude Code, Cursor, Windsurf, VS Code, Cline, Zed and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phrasly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remote&lt;/td&gt;
&lt;td&gt;OAuth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;humanize_text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;detect_ai_text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Claude.ai, Claude Desktop, Claude Code, ChatGPT (developer mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPTHuman&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local stdio or remote&lt;/td&gt;
&lt;td&gt;API key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;humanize_text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No separate tool&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Cursor, Claude Desktop, Windsurf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;humanizer-mcp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local or hosted&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Rule-based edits&lt;/td&gt;
&lt;td&gt;Rule-based risk score&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Claude Code, Claude Desktop, Claude.ai, any MCP client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;text2go ai-humanizer-mcp-server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local stdio&lt;/td&gt;
&lt;td&gt;Not documented&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Claude Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Walter Writes AI MCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best overall.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Walter's MCP server is a remote server at &lt;code&gt;https://mcp-server.walterwrites.ai/mcp&lt;/code&gt;. You add it once, sign in to your Walter account with OAuth, and the assistant gets three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;walter_humanize&lt;/code&gt;&lt;/strong&gt; rewrites AI text with adjustable strength while preserving keywords, named entities, URLs and document structure, and returns a preservation report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;walter_detect&lt;/code&gt;&lt;/strong&gt; returns a 0 to 100 AI score, a verdict, and the high-risk spans behind it, so the assistant knows exactly which sentences to work on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;walter_batch_humanize&lt;/code&gt;&lt;/strong&gt; processes several texts in one call, with shared defaults and per-item overrides. If one item fails, the rest still come back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three things set it apart from the rest of this list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It runs everywhere.&lt;/strong&gt; Because it's a remote OAuth server, the same URL works in Claude.ai, ChatGPT, Claude Code, Codex, Cursor, Windsurf (now Devin Desktop) and Gemini CLI. Every client uses the same Walter account, so your usage and settings follow you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It has skills on top.&lt;/strong&gt; Walter publishes 12 free, MIT-licensed agent skills on &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;: an SEO writer, newsletter writer, docs writer, content refresh, brand voice adapter, programmatic SEO and more. Each one tells the agent when to humanize, which mode to use (Light, Balanced or Aggressive) and when to run detection. Install all of them with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;It's part of a full platform.&lt;/strong&gt; The same account gives you Walter's web app, its free AI detector, a grammar checker, a Chrome extension and a REST API, and the humanizer covers 80+ languages. When an MCP workflow becomes something you run unattended, you can move it to the API without changing vendors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; usage draws from your Walter plan, so heavy batch work needs a paid tier. ChatGPT also needs a paid plan on the web to add any custom MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Humanizer PRO MCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for style modes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Humanizer PRO, from texthumanizer.pro, runs as a remote server at &lt;code&gt;https://texthumanizer.pro/mcp&lt;/code&gt; with OAuth sign-in. Its &lt;code&gt;humanize_text&lt;/code&gt; tool has three modes (Stealth, Academic and SEO), and &lt;code&gt;scan_ai_detection&lt;/code&gt; returns an AI probability, a human-likeness percentage and a verdict. There are also tools to check your word balance and plans.&lt;/p&gt;

&lt;p&gt;Client coverage is broad on paper: ChatGPT, Claude, Cursor, Windsurf, VS Code with Copilot, Cline, Continue and Zed. Pricing starts with 500 free words, then $9.99 a month for 30,000 words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; no batch tool, no skills or agent instructions, and the detector-bypass claims on its listing are the vendor's own.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Phrasly MCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best if you already use Phrasly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Phrasly's server lives at &lt;code&gt;https://phrasly.ai/api/mcp&lt;/code&gt; and uses OAuth. It exposes &lt;code&gt;humanize_text&lt;/code&gt; (the same model as the website), &lt;code&gt;detect_ai_text&lt;/code&gt; (an AI percentage and the number of flagged sentences) and &lt;code&gt;get_account_status&lt;/code&gt;. Usage comes out of the same word balance as the web app.&lt;/p&gt;

&lt;p&gt;Phrasly documents Claude.ai, Claude Desktop, Claude Code and ChatGPT in developer mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; Cursor and Windsurf aren't covered in its documentation, and there's no batch tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. GPTHuman MCP Server
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for API-key setups.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GPTHuman's open-source server (Apache-2.0) runs locally with &lt;code&gt;npx -y @gpthuman/mcp-server&lt;/code&gt; or remotely at &lt;code&gt;https://api.gpthuman.ai/mcp&lt;/code&gt;. It needs a &lt;code&gt;GPTHUMAN_API_KEY&lt;/code&gt; from your GPTHuman account. Its single &lt;code&gt;humanize_text&lt;/code&gt; tool takes tone (Standard, HighSchool, College, PhD) and mode (Professional, Balanced, Enhanced) options, and accepts up to 2,000 words.&lt;/p&gt;

&lt;p&gt;It lists Cursor, Claude Desktop and Windsurf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; there's no separate detect tool, so you can't score text before humanizing, and API keys in config files are one more secret to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. humanizer-mcp (Open Source)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best free, self-hosted option.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/humanizer-mcp/" rel="noopener noreferrer"&gt;humanizer-mcp&lt;/a&gt; on PyPI (MIT, version 0.2.0) takes a different approach. It doesn't call a humanization model. It scores text on rule-based signals like AI-associated vocabulary, sentence-length variation and em dash use, returns a 0 to 100 risk score, applies mechanical edits, and gives your LLM rewrite instructions for the rest.&lt;/p&gt;

&lt;p&gt;Install it with &lt;code&gt;pip install humanizer-mcp&lt;/code&gt;, or in Claude Code with &lt;code&gt;claude mcp add humanizer -- uvx humanizer-mcp&lt;/code&gt;. There's also a free hosted endpoint, though the maker recommends self-hosting for anything sensitive, since your text passes through it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; the "humanizing" is really your own LLM following a checklist, and the risk score is a heuristic, not a trained detector.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. text2go ai-humanizer-mcp-server
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lightweight, Claude Desktop only.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ai-humanizer-mcp-server&lt;/code&gt; npm package from text2go runs locally through &lt;code&gt;npx -y ai-humanizer-mcp-server&lt;/code&gt; in Claude Desktop's config. It describes AI detection, readability and grammar improvements, length control and term preservation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for:&lt;/strong&gt; it only documents Claude Desktop, doesn't say which service it calls, and its README was last updated in October 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Setting Up Walter in Each Client
&lt;/h2&gt;

&lt;p&gt;Every client below uses the same server URL and the same OAuth sign-in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude (web and desktop)
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;Settings → Connectors&lt;/strong&gt;, click &lt;strong&gt;+&lt;/strong&gt;, choose &lt;strong&gt;Add custom connector&lt;/strong&gt;, name it &lt;code&gt;Walter Writes AI&lt;/code&gt;, and paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp-server.walterwrites.ai/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Add&lt;/strong&gt;, sign in to Walter and allow access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http walter-writes https://mcp-server.walterwrites.ai/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code to sign in. Add &lt;code&gt;--scope user&lt;/code&gt; to make it available in every project.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatGPT
&lt;/h3&gt;

&lt;p&gt;On a paid plan in ChatGPT on the web, add Walter as a custom MCP server: depending on your account, that's through the Plugins page (&lt;strong&gt;+ → Add custom MCP server&lt;/strong&gt;) or through &lt;strong&gt;Settings → Apps → Advanced settings → Developer mode → Create app&lt;/strong&gt;. Use the same URL, choose OAuth, and sign in. In chats, name the tool you want ("use Walter to run walter_detect"), since ChatGPT's tool selection is less automatic than Claude's.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cursor
&lt;/h3&gt;

&lt;p&gt;Add this to &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in your project, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; for every project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor will ask you to sign in to Walter the first time it connects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windsurf (now Devin Desktop)
&lt;/h3&gt;

&lt;p&gt;Windsurf was renamed Devin Desktop in June 2026, and its MCP config moved to &lt;code&gt;~/.config/devin/mcp_config.json&lt;/code&gt; (or &lt;code&gt;.devin/mcp_config.json&lt;/code&gt; in a project). The snippet is the same as Cursor's. On an older Windsurf build that hasn't updated, use &lt;code&gt;serverUrl&lt;/code&gt; instead of &lt;code&gt;url&lt;/code&gt; in &lt;code&gt;~/.codeium/windsurf/mcp_config.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the connection
&lt;/h3&gt;

&lt;p&gt;Paste a deliberately robotic sentence and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the walter-writes MCP server. Run walter_detect on this text, humanize it with walter_humanize, then run walter_detect again and show both scores.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you get two scores back, it's working. If you only get a rewrite with no scores, the assistant paraphrased on its own and the server isn't being called.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Which One Should You Use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You use more than one assistant, or you write in a repo and the browser:&lt;/strong&gt; Walter Writes AI. One remote server, one sign-in, every major client, plus skills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want named style modes like Academic or SEO:&lt;/strong&gt; Humanizer PRO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You already pay for Phrasly and only use Claude or ChatGPT:&lt;/strong&gt; Phrasly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You prefer API keys and local stdio servers:&lt;/strong&gt; GPTHuman.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want free, open source and self-hosted, and you're fine with heuristics:&lt;/strong&gt; humanizer-mcp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You only need something quick in Claude Desktop:&lt;/strong&gt; text2go, with the caveats above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;A humanizer MCP server is only as useful as the tools behind it. The ones worth connecting give your assistant a real humanizer and a real detector, work in the clients you actually use, and don't make you manage secrets in config files.&lt;/p&gt;

&lt;p&gt;Walter Writes AI checks all of those boxes and adds batch processing, keyword preservation and 12 agent skills on top, which is why it's my top pick for Claude, ChatGPT, Cursor and Windsurf in 2026. If you're starting in an editor, install the skills and connect the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  12. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is a humanizer MCP server?&lt;/strong&gt;&lt;br&gt;
It's a Model Context Protocol server that gives an AI assistant tools to rewrite AI-generated text so it reads more naturally, and usually to check it with an AI detector. Your assistant calls those tools directly instead of you copying text into a website.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best humanizer MCP server in 2026?&lt;/strong&gt;&lt;br&gt;
Walter Writes AI. It's a remote OAuth server with humanize, detect and batch tools, it works in Claude, ChatGPT, Cursor, Windsurf, Claude Code, Codex and Gemini CLI, and it comes with 12 open-source agent skills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a humanizer MCP work in ChatGPT?&lt;/strong&gt;&lt;br&gt;
Yes, if the server is remote. Walter, Humanizer PRO and Phrasly all support ChatGPT through custom MCP servers, which require a paid ChatGPT plan on the web. Local stdio-only servers don't work in ChatGPT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use a humanizer MCP in Cursor and Windsurf?&lt;/strong&gt;&lt;br&gt;
Yes. Add the server's URL to &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in Cursor, or to Devin Desktop's &lt;code&gt;mcp_config.json&lt;/code&gt; (Windsurf was renamed Devin Desktop in 2026). Walter, Humanizer PRO and GPTHuman all list both editors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an API key for Walter's MCP server?&lt;/strong&gt;&lt;br&gt;
No. Walter's MCP server uses OAuth, so you sign in to your Walter account once in a browser. API keys are only needed for Walter's REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free humanizer MCP server?&lt;/strong&gt;&lt;br&gt;
humanizer-mcp is free and open source, but it's rule-based rather than a trained humanizer. Walter, Humanizer PRO and Phrasly all offer free tiers to try their servers before paying.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>productivity</category>
      <category>writing</category>
    </item>
    <item>
      <title>Best AI Humanizer in 2026: Ranked and Tested (Open Benchmark)</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Sat, 31 Oct 2026 17:30:11 +0000</pubDate>
      <link>https://dev.to/mohab_karim/best-ai-humanizer-in-2026-ranked-and-tested-open-benchmark-c7i</link>
      <guid>https://dev.to/mohab_karim/best-ai-humanizer-in-2026-ranked-and-tested-open-benchmark-c7i</guid>
      <description>&lt;h3&gt;
  
  
  Same inputs, same detectors, every score public. Here's which AI humanizer actually held up.
&lt;/h3&gt;

&lt;p&gt;Most "best AI humanizer" lists rank tools on screenshots nobody can check. So we ran a small, open benchmark instead. Two AI-written texts went through four popular AI humanizers with default settings, and every output was scored by the same AI detectors. The inputs, the unedited outputs, the raw scores and the script that turns them into a ranking are all in a public GitHub repo, so you can check every number or rerun the test yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; Walter Writes AI ranked first. Its outputs passed all 6 detector checks with an average AI score of 14.2%. Humanize AI came second with 4 of 6 but dropped a sentence from both samples. Undetectable AI passed 1 of 6 and QuillBot passed none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark repo:&lt;/strong&gt; &lt;a href="https://github.com/Mohabak/ai-humanizer-benchmark-2026" rel="noopener noreferrer"&gt;Mohabak/ai-humanizer-benchmark-2026 on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Ranking&lt;/li&gt;
&lt;li&gt;How the Benchmark Works&lt;/li&gt;
&lt;li&gt;Every Score, Every Sample&lt;/li&gt;
&lt;li&gt;Tool by Tool&lt;/li&gt;
&lt;li&gt;What the Benchmark Doesn't Tell You&lt;/li&gt;
&lt;li&gt;Run the Benchmark Yourself&lt;/li&gt;
&lt;li&gt;Which AI Humanizer Should You Use?&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The Ranking
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff21nzl6xas480htrp0xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff21nzl6xas480htrp0xb.png" alt="AI humanizer benchmark 2026 ranking: Walter Writes AI 6 of 6 checks passed, Humanize AI 4 of 6, Undetectable AI 1 of 6, QuillBot 0 of 6" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A "check" is one humanized text scored by one detector. With 2 samples and 3 core detectors, each tool had 6 checks. A check passes when the detector's AI score is below 50%.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;AI humanizer&lt;/th&gt;
&lt;th&gt;Checks passed&lt;/th&gt;
&lt;th&gt;Avg AI score&lt;/th&gt;
&lt;th&gt;Key points kept&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Walter Writes AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 / 6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Humanize AI (humanizeai.pro)&lt;/td&gt;
&lt;td&gt;4 / 6&lt;/td&gt;
&lt;td&gt;41.9%&lt;/td&gt;
&lt;td&gt;11 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Undetectable AI&lt;/td&gt;
&lt;td&gt;1 / 6&lt;/td&gt;
&lt;td&gt;78.2%&lt;/td&gt;
&lt;td&gt;13 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;QuillBot AI Humanizer&lt;/td&gt;
&lt;td&gt;0 / 6&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;td&gt;13 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;WriteHuman (1 sample)&lt;/td&gt;
&lt;td&gt;2 / 3&lt;/td&gt;
&lt;td&gt;51.0%&lt;/td&gt;
&lt;td&gt;6 / 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;Original AI text&lt;/td&gt;
&lt;td&gt;0 / 6&lt;/td&gt;
&lt;td&gt;97.1%&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Key points kept" measures meaning. Each sample has 6 or 7 key points, and we counted how many survived the rewrite. A humanizer that passes detectors by deleting half your argument isn't doing its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How the Benchmark Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Samples.&lt;/strong&gt; Two AI-generated texts of 92 and 93 words, each in a different register:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;s1&lt;/code&gt;: academic (remote work and employee well-being)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s2&lt;/code&gt;: blog (building a morning routine)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're short on purpose. Some humanizers cap input at 100 words, and every tool had to receive exactly the same text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline.&lt;/strong&gt; Before humanizing anything, we scored the originals. Every detector flagged both as AI, between 89.7% and 100%. So the bar is clear: any drop is the humanizer's work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humanizers.&lt;/strong&gt; Each sample went through each tool's web app once, with default settings, and the first output was saved without edits.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://walterwrites.ai" rel="noopener noreferrer"&gt;Walter Writes AI&lt;/a&gt;:&lt;/strong&gt; Readability University, Purpose General, Bypass Level Enhanced (defaults).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.humanizeai.pro" rel="noopener noreferrer"&gt;Humanize AI&lt;/a&gt;:&lt;/strong&gt; Writing level Default, Tone Free, Rewrite level Balanced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://undetectable.ai" rel="noopener noreferrer"&gt;Undetectable AI&lt;/a&gt;:&lt;/strong&gt; Readability University, Purpose General Writing, Human level Balanced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://quillbot.com/ai-humanizer" rel="noopener noreferrer"&gt;QuillBot AI Humanizer&lt;/a&gt;:&lt;/strong&gt; Standard mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://writehuman.ai" rel="noopener noreferrer"&gt;WriteHuman&lt;/a&gt;:&lt;/strong&gt; It asked for an account after the first sample, so it isn't ranked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Detectors.&lt;/strong&gt; Every output was scored by three detectors that could process all 11 texts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://proofademic.ai" rel="noopener noreferrer"&gt;Proofademic&lt;/a&gt;&lt;/strong&gt;, an academic AI detector with sentence-level scoring&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.zerogpt.com" rel="noopener noreferrer"&gt;ZeroGPT&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://sapling.ai/ai-content-detector" rel="noopener noreferrer"&gt;Sapling&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copyleaks scored 5 texts and GPTZero scored 1 before hitting their usage limits. Those results are in the repo as extra checks but aren't used for the ranking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meaning check.&lt;/strong&gt; For each output, a reviewer checked which key points from the original survived and noted any errors, such as dropped sentences, grammar mistakes or leftover AI preambles.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Every Score, Every Sample
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm27m9e1wv4iz6qz4z0yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm27m9e1wv4iz6qz4z0yo.png" alt="Benchmark heatmap of AI detector scores for every humanizer and sample across Proofademic, ZeroGPT, Sapling and Copyleaks" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Average AI score by detector (lower reads more human):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AI humanizer&lt;/th&gt;
&lt;th&gt;Proofademic&lt;/th&gt;
&lt;th&gt;ZeroGPT&lt;/th&gt;
&lt;th&gt;Sapling&lt;/th&gt;
&lt;th&gt;Copyleaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Walter Writes AI&lt;/td&gt;
&lt;td&gt;19.5%&lt;/td&gt;
&lt;td&gt;18.1%&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;td&gt;0% (2 texts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humanize AI&lt;/td&gt;
&lt;td&gt;48.5%&lt;/td&gt;
&lt;td&gt;7.8%&lt;/td&gt;
&lt;td&gt;69.2%&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undetectable AI&lt;/td&gt;
&lt;td&gt;99.0%&lt;/td&gt;
&lt;td&gt;45.3%&lt;/td&gt;
&lt;td&gt;90.2%&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QuillBot&lt;/td&gt;
&lt;td&gt;99.0%&lt;/td&gt;
&lt;td&gt;98.8%&lt;/td&gt;
&lt;td&gt;97.8%&lt;/td&gt;
&lt;td&gt;100% (1 text)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original AI text&lt;/td&gt;
&lt;td&gt;98.5%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;93.0%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two patterns stand out. First, detectors disagree a lot. ZeroGPT was the easiest to pass, while Proofademic and Sapling were much stricter. A humanizer that only beats one detector isn't reliable. Second, consistency matters more than any single good score. Walter was the only tool that stayed under 50% on every detector for both samples.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Tool by Tool
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Walter Writes AI: Best AI Humanizer in This Benchmark
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks passed:&lt;/strong&gt; all 6 core checks, plus both Copyleaks checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average AI score:&lt;/strong&gt; 14.2%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best result:&lt;/strong&gt; the academic sample scored 1% on Proofademic, 0% on ZeroGPT, 0.7% on Sapling and 0% on Copyleaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highest score:&lt;/strong&gt; the blog sample on Proofademic (38%), still well under the 50% line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Walter rewrites at the sentence-structure level rather than swapping words. You can see it in the outputs: sentences are reordered and split, and it adds specific detail. For example, it turned "informal mentoring that typically occurs in shared offices" into a line about "watercooler" conversations. The trade-off is length. Outputs were 30% to 40% longer than the inputs.&lt;/p&gt;

&lt;p&gt;For developers, Walter also has an API with a detect-and-humanize endpoint, an MCP server for Claude, ChatGPT and Codex, and installable &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;agent skills&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Humanize AI: Strong on One Detector, Loses Content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks passed:&lt;/strong&gt; 4 of 6&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average AI score:&lt;/strong&gt; 41.9%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Humanize AI was excellent on ZeroGPT (7.8% average) and passed the academic sample on all three detectors. But Proofademic (95%) and Sapling (99.1%) flagged the blog sample. It also cut the final sentence from both samples, keeping 11 of 13 key points.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Undetectable AI: Keeps Meaning, Stays Detectable
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks passed:&lt;/strong&gt; 1 of 6&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average AI score:&lt;/strong&gt; 78.2%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Undetectable AI kept every key point and made the text noticeably more casual, and longer: the blog output was 62% longer than the input. Proofademic still flagged both outputs at 99%, and Sapling flagged both above 87%. Its one pass was the blog sample on ZeroGPT (26.6%).&lt;/p&gt;
&lt;h3&gt;
  
  
  4. QuillBot AI Humanizer: Light Edits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks passed:&lt;/strong&gt; 0 of 6&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average AI score:&lt;/strong&gt; 98.6%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QuillBot's humanizer made light, readable edits that kept the meaning, but the structure stayed close to the original. Every detector flagged both outputs at 97% or higher, and Copyleaks flagged the academic sample at 100%.&lt;/p&gt;
&lt;h3&gt;
  
  
  Not ranked: WriteHuman
&lt;/h3&gt;

&lt;p&gt;WriteHuman processed one sample before asking for an account. That single output scored 40% on Proofademic, 19.6% on ZeroGPT and 93.5% on Sapling, and it introduced one meaning error. One sample isn't enough to rank it.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. What the Benchmark Doesn't Tell You
&lt;/h2&gt;

&lt;p&gt;Every benchmark has limits, and this one is small on purpose so it's easy to check. Keep these in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two short samples.&lt;/strong&gt; The results show a clear direction, not a precise pass rate. Longer documents can score differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One run each.&lt;/strong&gt; Humanizers and detectors aren't fully deterministic. A second run can give different outputs and scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detectors change.&lt;/strong&gt; Every score reflects the detector's model on October 31, 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaning check is human judgment,&lt;/strong&gt; recorded with notes in &lt;code&gt;data/meaning.csv&lt;/code&gt; so you can disagree with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Detection scores also aren't the only thing that matters. The best humanizer is the one whose output you'd actually publish: accurate, readable and still saying what you meant.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Run the Benchmark Yourself
&lt;/h2&gt;

&lt;p&gt;Everything is in the repo:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Mohabak" rel="noopener noreferrer"&gt;
        Mohabak
      &lt;/a&gt; / &lt;a href="https://github.com/Mohabak/ai-humanizer-benchmark-2026" rel="noopener noreferrer"&gt;
        ai-humanizer-benchmark-2026
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Open AI humanizer benchmark 2026: ranked and tested with Proofademic, ZeroGPT and Sapling. Raw scores and outputs included.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AI Humanizer Benchmark 2026&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;An open, reproducible benchmark of popular AI humanizers. Each tool rewrote the same two AI-generated texts, and every output was scored by independent AI detectors. All inputs, outputs and raw scores are in this repo so anyone can check the numbers or rerun the test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested:&lt;/strong&gt; October 31, 2026
&lt;strong&gt;Humanizers:&lt;/strong&gt; Walter Writes AI, Humanize AI (humanizeai.pro), Undetectable AI, QuillBot AI Humanizer, WriteHuman (partial)
&lt;strong&gt;Detectors:&lt;/strong&gt; Proofademic, ZeroGPT, Sapling (all texts); Copyleaks and GPTZero (partial, usage limits)&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Results&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Mohabak/ai-humanizer-benchmark-2026/charts/ranking.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMohabak%2Fai-humanizer-benchmark-2026%2FHEAD%2Fcharts%2Franking.png" alt="AI humanizer benchmark ranking"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ranked by how many detector checks each tool passed across the three core detectors (2 samples x 3 detectors = 6 checks). A check passes when the detector's AI score is below 50%.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Humanizer&lt;/th&gt;
&lt;th&gt;Checks passed&lt;/th&gt;
&lt;th&gt;Avg AI score&lt;/th&gt;
&lt;th&gt;Key points kept&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Walter Writes AI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 / 6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Humanize AI (humanizeai.pro)&lt;/td&gt;
&lt;td&gt;4 / 6&lt;/td&gt;
&lt;td&gt;41.9%&lt;/td&gt;
&lt;td&gt;11 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Undetectable AI&lt;/td&gt;
&lt;td&gt;1 /&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Mohabak/ai-humanizer-benchmark-2026" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;samples/   original AI-generated texts (s1 academic, s2 blog)
outputs/   unedited humanizer outputs, named &amp;lt;tool&amp;gt;-&amp;lt;sample&amp;gt;.txt
data/      scores.csv (raw), meaning.csv, summary.csv
scripts/   summarize.py
charts/    ranking.png, heatmap.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recompute the ranking from the raw scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Mohabak/ai-humanizer-benchmark-2026
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-humanizer-benchmark-2026
python3 scripts/summarize.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;walter        avg AI  14.2%  passes 6/6  meaning 13/13
humanizeai    avg AI  41.9%  passes 4/6  meaning 11/13
undetectable  avg AI  78.2%  passes 1/6  meaning 13/13
quillbot      avg AI  98.6%  passes 0/6  meaning 13/13
writehuman    avg AI  51.0%  passes 2/3  meaning 6/6
original      avg AI  97.1%  passes 0/6  meaning 0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line also prints the per-detector averages, and the script writes the ranking to &lt;code&gt;data/summary.csv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To extend it, add your own samples to &lt;code&gt;samples/&lt;/code&gt;, run them through any humanizer, add the outputs to &lt;code&gt;outputs/&lt;/code&gt; and the detector scores to &lt;code&gt;data/scores.csv&lt;/code&gt;, then rerun the script. Pull requests with new tools or detectors are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Which AI Humanizer Should You Use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want the most consistent results across detectors:&lt;/strong&gt; Walter Writes AI. It was the only tool that passed every check on both samples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You only care about one specific detector:&lt;/strong&gt; test your own text against it. Scores varied hugely by detector in this benchmark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need every sentence preserved exactly:&lt;/strong&gt; QuillBot and Undetectable AI kept all key points, but most detectors still flagged their output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're building it into an app or agent:&lt;/strong&gt; Walter has an API, an MCP server and agent skills. See its docs before you choose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever you use, read the output before you publish it, and follow the AI rules of wherever it's going.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusion
&lt;/h2&gt;

&lt;p&gt;In this open benchmark, Walter Writes AI was the best AI humanizer by a clear margin: all 6 core detector checks passed, a 14.2% average AI score, and every key point kept. Humanize AI was second but lost content. Undetectable AI and QuillBot kept the meaning but stayed detectable.&lt;/p&gt;

&lt;p&gt;The numbers are small and public on purpose. Clone the repo, check the scores, and add your own tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best AI humanizer in 2026?
&lt;/h3&gt;

&lt;p&gt;In this benchmark, Walter Writes AI. Its outputs passed all 6 detector checks across Proofademic, ZeroGPT and Sapling, with an average AI score of 14.2%, and kept every key point from the originals.&lt;/p&gt;

&lt;h3&gt;
  
  
  How was this AI humanizer benchmark tested?
&lt;/h3&gt;

&lt;p&gt;Two AI-generated texts (academic and blog, 92 and 93 words) went through each humanizer once with default settings. Every output was scored by Proofademic, ZeroGPT and Sapling, and a check passed when the AI score was under 50%. All data is on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I see the raw benchmark data?
&lt;/h3&gt;

&lt;p&gt;In the public &lt;a href="https://github.com/Mohabak/ai-humanizer-benchmark-2026" rel="noopener noreferrer"&gt;ai-humanizer-benchmark-2026 repo on GitHub&lt;/a&gt;. It contains the samples, unedited outputs, raw detector scores, meaning checks and the script that computes the ranking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did any humanizer pass every detector?
&lt;/h3&gt;

&lt;p&gt;Only Walter Writes AI. It passed all 6 core checks and both Copyleaks checks. The other tools were flagged by at least one detector on at least one sample, and QuillBot was flagged on every check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI humanizer keeps the meaning best?
&lt;/h3&gt;

&lt;p&gt;Walter, Undetectable AI and QuillBot all kept every key point, but only Walter also passed the detectors. Undetectable AI was flagged on 5 of 6 checks and QuillBot on all 6. Humanize AI passed more checks than those two but dropped a sentence from both samples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does humanized text change the meaning?
&lt;/h3&gt;

&lt;p&gt;Sometimes. Humanize AI dropped the final sentence of both samples, and WriteHuman introduced a meaning error in its one output. Always read the output before using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add my own tests to the benchmark?
&lt;/h3&gt;

&lt;p&gt;Yes. Add samples, outputs and scores to the repo folders, run &lt;code&gt;python3 scripts/summarize.py&lt;/code&gt;, and open a pull request.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>opensource</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>Best AI Humanizer in 2026: A Developer's Guide to APIs, MCP and Integrations</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:32:49 +0000</pubDate>
      <link>https://dev.to/mohab_karim/best-ai-humanizer-in-2026-a-developers-guide-to-apis-mcp-and-integrations-57np</link>
      <guid>https://dev.to/mohab_karim/best-ai-humanizer-in-2026-a-developers-guide-to-apis-mcp-and-integrations-57np</guid>
      <description>&lt;h3&gt;
  
  
  Most "best AI humanizer" lists are written for people pasting text into a box. This one is for people wiring a humanizer into code, agents and content pipelines.
&lt;/h3&gt;

&lt;p&gt;The best AI humanizer for developers in 2026 is &lt;strong&gt;Walter Writes AI&lt;/strong&gt;. It's the only option in this comparison that gives you a REST API with sync, polling and webhook modes, a combined detect-and-humanize endpoint, an official MCP server for Claude, ChatGPT and Codex, and installable agent skills, all on one account and one credit pool. &lt;strong&gt;WriteHuman&lt;/strong&gt; is the strongest alternative if you want several rewrites per call, and &lt;strong&gt;Undetectable AI&lt;/strong&gt; is worth a look if you need fine-grained readability controls or streaming.&lt;/p&gt;

&lt;p&gt;That's the short answer. The rest of this guide explains how each tool works from a developer's point of view, using each vendor's own documentation as of October 2026: endpoints, authentication, limits, pricing, and the trade-offs that only show up once you're in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What Makes an AI Humanizer "Best" for Developers&lt;/li&gt;
&lt;li&gt;The Comparison at a Glance&lt;/li&gt;
&lt;li&gt;Walter Writes AI: Best Overall for Developers&lt;/li&gt;
&lt;li&gt;WriteHuman: Best for Multiple Rewrites per Call&lt;/li&gt;
&lt;li&gt;Undetectable AI: Best for Readability Controls&lt;/li&gt;
&lt;li&gt;Phrasly: Best for High-Volume Streaming&lt;/li&gt;
&lt;li&gt;QuillBot: Fine for Writers, Not Built for Developers&lt;/li&gt;
&lt;li&gt;Code: Humanizing Text With Walter's API&lt;/li&gt;
&lt;li&gt;Code: Detect First, Humanize Only When Needed&lt;/li&gt;
&lt;li&gt;Using a Humanizer Inside AI Agents With MCP&lt;/li&gt;
&lt;li&gt;How to Choose&lt;/li&gt;
&lt;li&gt;Production Tips That Apply to Every Humanizer&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What Makes an AI Humanizer "Best" for Developers
&lt;/h2&gt;

&lt;p&gt;An AI humanizer rewrites machine-generated text so it reads like a person wrote it, changing sentence structure, rhythm and word choice while keeping the meaning. For someone using a web app, "best" mostly means output quality. For a developer, quality still matters, but so do things that never show up in consumer reviews:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration surface.&lt;/strong&gt; Is there a documented REST API? An MCP server for AI assistants? Webhooks? A no-code connector?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processing modes.&lt;/strong&gt; Synchronous calls are easy. Async with polling or webhooks is what keeps long jobs from timing out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits.&lt;/strong&gt; Minimum and maximum words per request, and rate limits per minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection in the same account.&lt;/strong&gt; Being able to score text before and after rewriting, with the same key, saves a second vendor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing model.&lt;/strong&gt; Per word, per credit, or a monthly minimum, and whether unused allowance rolls over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaning and keyword safety.&lt;/strong&gt; Whether the rewrite preserves names, URLs and terms your content depends on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tool below is evaluated on those criteria, using only what the vendors publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Comparison at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Walter Writes AI&lt;/th&gt;
&lt;th&gt;WriteHuman&lt;/th&gt;
&lt;th&gt;Undetectable AI&lt;/th&gt;
&lt;th&gt;Phrasly&lt;/th&gt;
&lt;th&gt;QuillBot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public REST API&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-API-Key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bearer token&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;apikey&lt;/code&gt; header&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x-api-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sync responses&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (submit, then poll)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async: polling&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async: webhooks&lt;/td&gt;
&lt;td&gt;Yes, with secret header&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;On request&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;WebSocket&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detect-and-humanize in one call&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (separate endpoints)&lt;/td&gt;
&lt;td&gt;No (separate APIs)&lt;/td&gt;
&lt;td&gt;No (separate endpoints)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official MCP server&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent skills&lt;/td&gt;
&lt;td&gt;Yes, 12&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Words per request&lt;/td&gt;
&lt;td&gt;50 to 2,000&lt;/td&gt;
&lt;td&gt;2,000 to 6,000 by plan&lt;/td&gt;
&lt;td&gt;Min 50 characters, no max published&lt;/td&gt;
&lt;td&gt;20 to 5,000&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry API price&lt;/td&gt;
&lt;td&gt;$49/mo, 300K credits&lt;/td&gt;
&lt;td&gt;$29/mo, 125K words&lt;/td&gt;
&lt;td&gt;$29/mo, 72.5K words&lt;/td&gt;
&lt;td&gt;$100/mo, $100 credits&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free API trial&lt;/td&gt;
&lt;td&gt;2,000 credits, 5 days&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;250 words&lt;/td&gt;
&lt;td&gt;Not listed&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Not listed" means the vendor's public API pages don't mention it, not that it definitely doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Walter Writes AI: Best Overall for Developers
&lt;/h2&gt;

&lt;p&gt;Walter Writes AI positions itself as an authenticity platform: a humanizer at the core, with an AI detector, plagiarism checker, grammar checker and voice and image detection around it. For developers, it's the most complete package in this list, because it gives you three ways in instead of one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The REST API.&lt;/strong&gt; The humanizer lives at &lt;code&gt;POST /api/humanizer/&lt;/code&gt; on &lt;code&gt;https://developer-portal.walterwrites.ai/api&lt;/code&gt;, authenticated with an &lt;code&gt;X-API-Key&lt;/code&gt; header that has the humanizer scope. Every request supports three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync:&lt;/strong&gt; send &lt;code&gt;content&lt;/code&gt;, get the humanized &lt;code&gt;result&lt;/code&gt; back in the response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling:&lt;/strong&gt; send &lt;code&gt;poll: true&lt;/code&gt;, get a &lt;code&gt;task_id&lt;/code&gt;, then fetch &lt;code&gt;GET /api/jobs/{task_id}/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook:&lt;/strong&gt; send a &lt;code&gt;callback_url&lt;/code&gt; (and optionally a &lt;code&gt;webhook_secret&lt;/code&gt;), and Walter POSTs the result to your server with the secret in an &lt;code&gt;X-Webhook-Secret&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Detect-and-humanize.&lt;/strong&gt; &lt;code&gt;POST /api/detect-and-humanize/&lt;/code&gt; scores the text first and only rewrites it if the AI score is above your &lt;code&gt;detection_threshold&lt;/code&gt;. If the text already reads as human, you get the sentence-level detection back and the text is left alone. Few humanizers offer this as a single call, and it's the cleanest way to avoid rewriting content that doesn't need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server.&lt;/strong&gt; Walter runs an official MCP server at &lt;code&gt;https://mcp-server.walterwrites.ai/mcp&lt;/code&gt;, signed in with OAuth, that works with Claude, ChatGPT and Codex. It exposes &lt;code&gt;walter_humanize&lt;/code&gt;, &lt;code&gt;walter_detect&lt;/code&gt; and &lt;code&gt;walter_batch_humanize&lt;/code&gt;. According to Walter's docs, the humanize tool preserves keywords, named entities, URLs and document structure and returns a report showing what was kept, which matters if you're humanizing SEO content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent skills.&lt;/strong&gt; Walter Skills are 12 ready-made workflows (SEO writing, content refresh, programmatic SEO, newsletters and more) that tell an agent when to humanize, when to detect and how to format the result. They install with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source is on GitHub at &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;walterwritesai/walter-skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits and pricing.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests must be 50 to 2,000 words.&lt;/li&gt;
&lt;li&gt;Synchronous calls are limited to 3 per minute per user; async calls have no time-based limit and are bounded by credits and pending jobs.&lt;/li&gt;
&lt;li&gt;API credits are 1 per word for the humanizer, from one shared pool across all of Walter's APIs.&lt;/li&gt;
&lt;li&gt;Plans start at $49 a month for 300,000 credits and go up to 25 million. New accounts get 2,000 credits for 5 days, no card required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs.&lt;/strong&gt; The 2,000-word cap means long documents need to be split, and the sync rate limit is low enough that anything beyond light use should go through async. Both are easy to design around, and the docs are clear about them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; content platforms, SEO and agency pipelines, and anyone building with AI agents who wants humanization and detection on one account.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. WriteHuman: Best for Multiple Rewrites per Call
&lt;/h2&gt;

&lt;p&gt;WriteHuman's API is a single, clean endpoint, &lt;code&gt;POST /v1/humanize&lt;/code&gt; on &lt;code&gt;api.writehuman.ai&lt;/code&gt;, authenticated with a revocable Bearer key (up to five per account).&lt;/p&gt;

&lt;p&gt;What sets it apart is the Premium tier. Each call returns three rewrites, each with a human-likeness score from 0 to 1, so your code can pick the best one automatically. You can also set one of seven tones (professional, academic, blog, casual, creative, scientific, technical) per request, and WriteHuman lists 40+ supported languages with auto-detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits and pricing.&lt;/strong&gt; Standard is $29 a month for 125,000 words, 2,000 words per call and 40 requests per minute. Premium plans start at $69 a month for 400,000 words, with 4,000 to 6,000 words per call and 120 to 160 requests per minute depending on size. Top-up packs of 100,000 words cost $25 and don't expire. There's also a separate &lt;code&gt;/v1/detect&lt;/code&gt; endpoint and a read-only &lt;code&gt;/v1/usage&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs.&lt;/strong&gt; No published async or webhook mode, no combined detect-and-humanize call, and no MCP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; apps that want to offer users a choice of rewrites, or pipelines that auto-select the highest-scoring version.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Undetectable AI: Best for Readability Controls
&lt;/h2&gt;

&lt;p&gt;Undetectable AI's humanization API works differently from the others. You submit a document to &lt;code&gt;POST https://humanize.undetectable.ai/submit&lt;/code&gt;, get back an ID, and then poll &lt;code&gt;POST /document&lt;/code&gt; every 5 to 10 seconds until it's done. There's also a WebSocket mode that streams the result sentence by sentence.&lt;/p&gt;

&lt;p&gt;The request parameters are the most detailed in this list. &lt;code&gt;readability&lt;/code&gt; (High School to Doctorate, Journalist, Marketing) and &lt;code&gt;purpose&lt;/code&gt; (Essay, Article, Marketing Material, Legal Material and others) are required, and you can set &lt;code&gt;strength&lt;/code&gt; and choose a &lt;code&gt;model&lt;/code&gt;. The docs describe &lt;code&gt;v2&lt;/code&gt; as supporting every language and &lt;code&gt;v11&lt;/code&gt; and &lt;code&gt;v11sr&lt;/code&gt; as English-focused models with stronger rewriting. Authentication uses an &lt;code&gt;apikey&lt;/code&gt; header, with an option to exchange it for a short-lived token for client-side use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits and pricing.&lt;/strong&gt; Starter is $29 a month for 72,500 words and Growth is $49 a month for 245,000 words, with pay-as-you-go overage. The default rate limit is 3 requests per second per IP. Webhooks are available on request, and new accounts get 250 word credits to test with. The same account also unlocks detection APIs for text, images, audio and video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs.&lt;/strong&gt; No synchronous mode, so every integration needs a polling loop or WebSocket handling. No MCP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; products that need control over reading level and content type, or live streaming of the rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Phrasly: Best for High-Volume Streaming
&lt;/h2&gt;

&lt;p&gt;Phrasly's business API is built for speed. You call &lt;code&gt;POST /api/v1/humanize&lt;/code&gt; with an &lt;code&gt;x-api-key&lt;/code&gt; header, and Phrasly says typical requests return in under two seconds, with streaming responses and white-label output. A detection endpoint runs from the same balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits and pricing.&lt;/strong&gt; There's one Business plan: $100 a month, which includes $100 in API credits. Humanization costs $0.14 per 1,000 words and detection $0.02 per 1,000. An "Ultra" model is available on request at $0.30 per 1,000 words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs.&lt;/strong&gt; The $100 monthly minimum makes it a poor fit for small projects, and there's no MCP server or combined detect-and-humanize call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; established products with steady, high volume that want fast streaming output.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. QuillBot: Fine for Writers, Not Built for Developers
&lt;/h2&gt;

&lt;p&gt;QuillBot appears on almost every "best AI humanizer" list, so it's worth explaining why it's not a real option here. Its AI Humanizer is part of a writing suite available through the web app, browser extensions, a Word add-in, desktop apps and a mobile keyboard. Its Premium plan costs $8.33 a month billed annually and includes an unlimited Advanced humanizer.&lt;/p&gt;

&lt;p&gt;But QuillBot doesn't list a public humanizer API on its humanizer or Premium pages, and there's no MCP server. If you're building something, you can't call it from code. It's a good tool for individual writers, not a component you can integrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Code: Humanizing Text With Walter's API
&lt;/h2&gt;

&lt;p&gt;Get a key from the Walter Writes developer portal and select the &lt;strong&gt;Humanizer&lt;/strong&gt; scope (and &lt;strong&gt;AI Detector&lt;/strong&gt; if you'll use section 9). Keep the key on your server.&lt;/p&gt;

&lt;p&gt;A synchronous call with curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://developer-portal.walterwrites.ai/api/humanizer/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$WALTER_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"content": "Your AI-generated draft of at least 50 words goes here..."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes the humanized text in &lt;code&gt;result&lt;/code&gt;, the input &lt;code&gt;word_count&lt;/code&gt; and your &lt;code&gt;credits_remaining&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For anything beyond occasional use, go async. Here's a Node.js example (Node 18+, no dependencies) that submits a job with a webhook and verifies the callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// humanize.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://developer-portal.walterwrites.ai/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WALTER_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WALTER_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitHumanizeJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/humanizer/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callback_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-app.example.com/webhooks/walter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;webhook_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// also allow polling as a fallback&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unexpected response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// store this with your document&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2mb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nx"&gt;app&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/walter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;safeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Webhook-Secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// save `result` against the document you stored with task_id&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;callback_url&lt;/code&gt; has to be publicly reachable. Walter blocks private and internal addresses, so use a tunnel in local development. Because the request also sets &lt;code&gt;poll: true&lt;/code&gt;, you can fall back to &lt;code&gt;GET /api/jobs/{task_id}/&lt;/code&gt; if a webhook is ever missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Code: Detect First, Humanize Only When Needed
&lt;/h2&gt;

&lt;p&gt;Rewriting text that already reads as human wastes credits and can make good writing worse. Walter's &lt;code&gt;detect-and-humanize&lt;/code&gt; endpoint handles the decision for you. This Python example sends a draft with a threshold and handles both outcomes:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE&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://developer-portal.walterwrites.ai/api&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;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;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WALTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# needs humanizer + ai_detector scopes
&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;clean_draft&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;threshold&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;0.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;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/detect-and-humanize/&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;detection_threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;humanized&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;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;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;humanized_content&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_score_before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_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;changed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Below threshold: original text comes back untouched, with sentence-level scores
&lt;/span&gt;    &lt;span class="n"&gt;flagged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detection_items&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;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prediction&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-generated&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;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;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;content&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_score_before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai_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;changed&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentences_to_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft.md&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_draft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;AI score &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out&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_score_before&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="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&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;, changed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;changed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick the threshold based on your content. A higher threshold rewrites less often and leaves more of the original voice intact. A lower one rewrites more aggressively.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Using a Humanizer Inside AI Agents With MCP
&lt;/h2&gt;

&lt;p&gt;The newest way to use a humanizer isn't an HTTP call at all. With an MCP server, an AI assistant can draft, humanize and score text in one conversation, deciding on its own when to call each tool.&lt;/p&gt;

&lt;p&gt;Walter is the only humanizer in this comparison with an official MCP server. In Claude Code, adding it is one command, followed by &lt;code&gt;/mcp&lt;/code&gt; to sign in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cursor, add it to &lt;code&gt;.cursor/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a prompt like this runs the whole loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the walter-writes MCP server. Run walter_detect on draft.md, humanize any
section scoring above 30 with walter_humanize while keeping the keywords
"AI humanizer API" and "MCP" unchanged, then run walter_detect again and
show me the before and after scores.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair the MCP server with Walter Skills (&lt;code&gt;npx skills add walterwritesai/walter-skills&lt;/code&gt;) and the agent also knows the workflow, such as when to use a lighter or stronger rewrite and how to report results.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. How to Choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want one platform for humanizing, detection, agents and webhooks:&lt;/strong&gt; Walter Writes AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want users to choose between several rewrites, or auto-pick the best one:&lt;/strong&gt; WriteHuman Premium.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need reading-level and content-type controls, or live streaming:&lt;/strong&gt; Undetectable AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're at steady high volume and want sub-two-second streaming responses:&lt;/strong&gt; Phrasly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're an individual writer, not a developer:&lt;/strong&gt; QuillBot is fine, but there's nothing to integrate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. Production Tips That Apply to Every Humanizer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep keys on the server.&lt;/strong&gt; Never ship an API key in a browser or mobile app. Use environment variables or a secrets manager, and one key per environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunk long documents&lt;/strong&gt; on paragraph or section boundaries to stay under each vendor's per-request limit, then reassemble in order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go async for anything long.&lt;/strong&gt; Sync calls are convenient but tie up a request and hit rate limits sooner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect what must not change.&lt;/strong&gt; Product names, URLs, code snippets and target keywords can drift in a rewrite. Use a tool that preserves them, or check them after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure, don't assume.&lt;/strong&gt; Score text before and after with a detector, and keep a human review step for anything published under your name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry carefully.&lt;/strong&gt; Retry &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; errors with backoff. Don't retry &lt;code&gt;400&lt;/code&gt;, &lt;code&gt;401&lt;/code&gt; or &lt;code&gt;403&lt;/code&gt;; those mean the request itself needs fixing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect the rules of where the text goes.&lt;/strong&gt; Schools, publishers and platforms have their own AI policies, and some jurisdictions have disclosure rules. A humanizer improves how text reads; it doesn't change what's allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  13. Conclusion
&lt;/h2&gt;

&lt;p&gt;For a writer, the best AI humanizer is the one whose output reads best. For a developer, it's the one that fits cleanly into what you're building, and that's where the differences between these tools get big.&lt;/p&gt;

&lt;p&gt;Walter Writes AI comes out on top because it's the most complete: a REST API with sync, polling and webhooks, a detect-and-humanize call that avoids unnecessary rewrites, an official MCP server for Claude, ChatGPT and Codex, and installable agent skills, all on one account. WriteHuman, Undetectable AI and Phrasly each win a narrower case, and QuillBot is a writer's tool rather than a developer's.&lt;/p&gt;

&lt;p&gt;Start with the free trials, run your own content through each one, and choose the integration you'd be happy maintaining a year from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best AI humanizer in 2026?
&lt;/h3&gt;

&lt;p&gt;For developers, Walter Writes AI. It combines a humanizer API with sync, polling and webhook modes, a detect-and-humanize endpoint, an official MCP server and installable agent skills. WriteHuman, Undetectable AI and Phrasly are solid alternatives for narrower needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI humanizers have an API?
&lt;/h3&gt;

&lt;p&gt;Walter Writes AI, WriteHuman, Undetectable AI and Phrasly all publish humanizer APIs. QuillBot doesn't list a public humanizer API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there an AI humanizer with an MCP server?
&lt;/h3&gt;

&lt;p&gt;Yes. Walter Writes AI runs an official MCP server at &lt;code&gt;https://mcp-server.walterwrites.ai/mcp&lt;/code&gt; with OAuth sign-in, exposing &lt;code&gt;walter_humanize&lt;/code&gt;, &lt;code&gt;walter_detect&lt;/code&gt; and &lt;code&gt;walter_batch_humanize&lt;/code&gt; to Claude, ChatGPT and Codex.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does an AI humanizer API cost?
&lt;/h3&gt;

&lt;p&gt;Entry plans in this comparison range from $29 a month (WriteHuman and Undetectable AI) to $49 (Walter Writes AI) and $100 (Phrasly, including $100 in credits). Pricing is per word or per credit, and limits per request and per minute vary by plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a free AI humanizer API?
&lt;/h3&gt;

&lt;p&gt;Several offer free trials. Walter Writes AI gives new accounts 2,000 API credits for 5 days, and Undetectable AI gives 250 word credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the maximum length for one request?
&lt;/h3&gt;

&lt;p&gt;It depends on the vendor. Walter Writes AI accepts 50 to 2,000 words per request, WriteHuman 2,000 to 6,000 depending on plan, and Phrasly 20 to 5,000. Longer documents should be split into chunks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a humanizer keep my SEO keywords?
&lt;/h3&gt;

&lt;p&gt;Some can. Walter's MCP humanize tool is documented to preserve keywords, named entities, URLs and document structure, and it returns a report of what was kept. With other tools, check important terms after rewriting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I install Walter Skills?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;npx skills add walterwritesai/walter-skills&lt;/code&gt; in your project. It installs 12 agent workflows from the &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;walterwritesai/walter-skills&lt;/a&gt; repository. Connect the Walter MCP server too, so the agent can actually run the humanize and detect steps.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Plagiarism Checker API: Adding Originality Checks to Your App</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Tue, 27 Oct 2026 19:29:23 +0000</pubDate>
      <link>https://dev.to/mohab_karim/plagiarism-checker-api-adding-originality-checks-to-your-app-51f6</link>
      <guid>https://dev.to/mohab_karim/plagiarism-checker-api-adding-originality-checks-to-your-app-51f6</guid>
      <description>&lt;h3&gt;
  
  
  One POST request gets you a score, a verdict, the sources that matched, and the exact passages to highlight. Here's how to build it into a real product.
&lt;/h3&gt;

&lt;p&gt;If your app accepts writing from users, sooner or later someone asks whether that writing is original. It might be an LMS that takes essays, a CMS where freelancers submit articles, a marketplace for guest posts, or an internal tool where an editor reviews drafts before they go live.&lt;/p&gt;

&lt;p&gt;Building plagiarism detection yourself means crawling and indexing the web, fingerprinting text, and matching passages at scale. That's a company, not a feature. A plagiarism checker API lets you skip all of it: you send text, and you get back a score, the matching sources, and the passages that matched.&lt;/p&gt;

&lt;p&gt;This guide uses the &lt;strong&gt;Proofademic API&lt;/strong&gt;, which added a plagiarism checker alongside its AI detector in July 2026. Everything below comes from Proofademic's official API documentation, with working examples in curl, Node.js and Python, plus the production details that usually get skipped: idempotent retries, rendering highlights, error handling and cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What a Plagiarism Checker API Gives You&lt;/li&gt;
&lt;li&gt;Getting an API Key&lt;/li&gt;
&lt;li&gt;Your First Scan&lt;/li&gt;
&lt;li&gt;Understanding the Response&lt;/li&gt;
&lt;li&gt;Node.js: A Reusable Client&lt;/li&gt;
&lt;li&gt;Python: Safe Retries With Idempotency Keys&lt;/li&gt;
&lt;li&gt;Rendering Highlights in Your UI&lt;/li&gt;
&lt;li&gt;Handling Errors&lt;/li&gt;
&lt;li&gt;Pairing Plagiarism and AI Detection&lt;/li&gt;
&lt;li&gt;Pricing, Limits and Cost Planning&lt;/li&gt;
&lt;li&gt;Already Using Walter's API?&lt;/li&gt;
&lt;li&gt;Production Checklist&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What a Plagiarism Checker API Gives You
&lt;/h2&gt;

&lt;p&gt;Proofademic's plagiarism endpoint scans text against web sources and returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;plagiarism score&lt;/strong&gt; from 0 (original) to 1 (fully plagiarized)&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;verdict&lt;/strong&gt; label you can show directly or map to your own UI&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;matched sources&lt;/strong&gt;, strongest first, with URL, title, domain and how much of your text each one covers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;highlights&lt;/strong&gt;, as character ranges into the text you submitted, each with a severity and a pointer to its source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part matters more than it sounds. A score on its own tells a user "something is wrong." Offsets let you show them exactly which sentences matched and where they came from, which is what a reviewer actually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Getting an API Key
&lt;/h2&gt;

&lt;p&gt;Sign in to the Proofademic Developer Portal and open the &lt;strong&gt;API Keys&lt;/strong&gt; page.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create API Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a descriptive name, such as &lt;code&gt;production-cms&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Model Access&lt;/strong&gt;, select &lt;strong&gt;Plagiarism Checker&lt;/strong&gt;. Add &lt;strong&gt;AI Detector&lt;/strong&gt; too if you plan to run both checks (section 9).&lt;/li&gt;
&lt;li&gt;Choose an expiration: Never, 30, 60 or 90 days.&lt;/li&gt;
&lt;li&gt;Copy the key immediately. It's shown only once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keys start with &lt;code&gt;prfd_&lt;/code&gt;, which makes them easy to spot in config files and secret scanners. A key only works for the scopes you selected. Calling an endpoint outside its scopes returns &lt;code&gt;403&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;New accounts get a free trial with &lt;strong&gt;2,000 credits for 5 days&lt;/strong&gt;, no credit card required.&lt;/p&gt;

&lt;p&gt;Store the key server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PROOFADEMIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prfd_..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never ship it to a browser or mobile client. Your frontend should call your backend, and your backend should call Proofademic.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Your First Scan
&lt;/h2&gt;

&lt;p&gt;The endpoint is a single POST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST https://developer-portal.proofademic.ai/api/plagiarism/scan/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request body has one field, &lt;code&gt;content&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;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://developer-portal.proofademic.ai/api/plagiarism/scan/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$PROOFADEMIC_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "The Eiffel Tower is a wrought-iron lattice tower on the Champ de Mars in Paris, France. It is named after the engineer Gustave Eiffel, whose company designed and built the tower from 1887 to 1889."
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Limits, per the docs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimum length&lt;/td&gt;
&lt;td&gt;50 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum length&lt;/td&gt;
&lt;td&gt;25,000 words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processing&lt;/td&gt;
&lt;td&gt;Synchronous only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Flat 2 credits per scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Word counts are script-aware: whitespace-separated words for most languages, one word per character for CJK scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Understanding the Response
&lt;/h2&gt;

&lt;p&gt;Here's the response from the docs for the request above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scan_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scan_YYbWPwT4JHUabc12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plagiarism_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"severe_plagiarism"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nearly all of the submitted text matches known sources."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://en.wikipedia.org/wiki/Eiffel_Tower"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Eiffel Tower - Wikipedia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en.wikipedia.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"similarity_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matched_word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matched_percentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"submitted_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"similarity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.97&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"highlights"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_chars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;196&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sentence_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sources_checked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"processing_time_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3100&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credits_remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1998&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields you'll use most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plagiarism_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overall result, 0.0 to 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;verdict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ready-made label for the score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A human-readable sentence you can show as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sources[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matched sources, strongest first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sources[].matched_percentage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share of the submitted text this source covers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;highlights[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Character ranges to mark in the text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;highlights[].source_index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which entry in &lt;code&gt;sources[]&lt;/code&gt; the passage matched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scan_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Store it. Include it in support requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;credits_remaining&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your balance after this call&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Verdicts map to score ranges like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;below 0.05&lt;/td&gt;
&lt;td&gt;&lt;code&gt;original&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.05 to 0.15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;minor_similarity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.15 to 0.40&lt;/td&gt;
&lt;td&gt;&lt;code&gt;moderate_plagiarism&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40 to 0.70&lt;/td&gt;
&lt;td&gt;&lt;code&gt;significant_plagiarism&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.70 and above&lt;/td&gt;
&lt;td&gt;&lt;code&gt;severe_plagiarism&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Highlight severity is based on how closely each passage matches its source: &lt;code&gt;high&lt;/code&gt; at 0.85 or above, &lt;code&gt;medium&lt;/code&gt; at 0.70 or above, and &lt;code&gt;low&lt;/code&gt; below that.&lt;/p&gt;

&lt;p&gt;A note on interpretation: a &lt;code&gt;minor_similarity&lt;/code&gt; result is normal for most real writing. Quotes, common phrases, names and standard definitions all overlap with something on the web. Treat the verdict as a starting point for review, not an automatic accusation, and show users the matched passages so they can judge for themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Node.js: A Reusable Client
&lt;/h2&gt;

&lt;p&gt;This uses the built-in &lt;code&gt;fetch&lt;/code&gt; in Node 18 and later, with no dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// plagiarism.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://developer-portal.proofademic.ai/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOFADEMIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlagiarismApiError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scanForPlagiarism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text must be at least 50 characters to scan.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/plagiarism/scan/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlagiarismApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a small Express route that your frontend can call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scanForPlagiarism&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PlagiarismApiError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./plagiarism.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1mb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nx"&gt;app&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/originality-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;scanForPlagiarism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plagiarism_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matched_percentage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matched_percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="na"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;scanId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;PlagiarismApiError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;502&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route returns only what the frontend needs. It doesn't forward &lt;code&gt;credits_remaining&lt;/code&gt; or &lt;code&gt;user.id&lt;/code&gt;, which are account details your users don't need to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Python: Safe Retries With Idempotency Keys
&lt;/h2&gt;

&lt;p&gt;Because each scan is billed, a naive retry after a timeout can charge you twice: the first request may have succeeded even though your client never saw the response.&lt;/p&gt;

&lt;p&gt;The API handles this with an &lt;code&gt;Idempotency-Key&lt;/code&gt; header (any string up to 255 characters). Per the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeating the &lt;strong&gt;same key with the same text within 5 minutes&lt;/strong&gt; returns the original response. Nothing is re-scanned and nothing is charged.&lt;/li&gt;
&lt;li&gt;Repeating the &lt;strong&gt;same key with different text&lt;/strong&gt; returns &lt;code&gt;409 idempotency_key_reuse&lt;/code&gt;, so a stale key can never return results for the wrong text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule that follows: generate the key once per logical scan, and reuse it for every retry of that scan.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE_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://developer-portal.proofademic.ai/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_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="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROOFADEMIC_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;RETRYABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&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;scan_for_plagiarism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&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;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;idempotency_key&lt;/span&gt; &lt;span class="o"&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# one key for every retry of this scan
&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;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;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;API_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;Idempotency-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;idempotency_key&lt;/span&gt;&lt;span class="p"&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;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/plagiarism/scan/&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;content&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;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;RequestException&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;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&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="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;RETRYABLE&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&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;retry_after&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&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="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;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;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Plagiarism scan failed after retries&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="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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scan_for_plagiarism&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft.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;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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verdict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plagiarism_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;for&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;src&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;domain&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="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;matched_percentage&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="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details make this safe. The key is created outside the retry loop, so every attempt shares it. And retries only happen for &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; errors. The docs are explicit that &lt;code&gt;400&lt;/code&gt;, &lt;code&gt;401&lt;/code&gt; and &lt;code&gt;403&lt;/code&gt; mean something in the request needs fixing, so retrying them just wastes time.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Rendering Highlights in Your UI
&lt;/h2&gt;

&lt;p&gt;Each highlight is a &lt;code&gt;{ start, end }&lt;/code&gt; range into the text &lt;strong&gt;exactly as you submitted it&lt;/strong&gt;. That's why it's worth storing the submitted text alongside the result: if a user edits the draft afterward, old offsets no longer line up.&lt;/p&gt;

&lt;p&gt;Here's a framework-free function that turns the response into safe HTML with &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&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="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;&amp;amp;&amp;lt;&amp;gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;#39;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderHighlights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// skip overlaps&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source_index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;mark class="match match--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" title="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Matched source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/mark&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.match--high&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#fecaca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.match--medium&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#fde68a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.match--low&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;#e5e7eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Escape both the user text and the source titles. Both come from outside your app, and rendering either one as raw HTML is an easy XSS hole.&lt;/p&gt;

&lt;p&gt;One edge case worth testing: the docs say offsets refer to the text as submitted, but they don't specify whether they count UTF-16 code units (how JavaScript indexes strings) or Unicode code points. For plain text it makes no difference. If your users paste emoji or other characters outside the Basic Multilingual Plane, run a test scan with one and confirm the highlights still line up.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Handling Errors
&lt;/h2&gt;

&lt;p&gt;All errors return JSON with an &lt;code&gt;error&lt;/code&gt; message, and most include a machine-readable &lt;code&gt;code&lt;/code&gt; and a &lt;code&gt;details&lt;/code&gt; object. Branch on &lt;code&gt;code&lt;/code&gt;, not on the message string.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;invalid_payload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;content&lt;/code&gt; or under 50 characters. Validate before sending&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;insufficient_content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No complete sentences to scan. Ask the user for more text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max_words_exceeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Over 25,000 words. &lt;code&gt;details&lt;/code&gt; has &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;actual&lt;/code&gt;. Split the document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;invalid_idempotency_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key longer than 255 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Missing, invalid or expired API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;&lt;code&gt;insufficient_credits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not enough credits. &lt;code&gt;details&lt;/code&gt; has &lt;code&gt;credits&lt;/code&gt; and &lt;code&gt;required&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;credits_exhausted&lt;/code&gt; / &lt;code&gt;trial_expired&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Plan or trial is used up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;&lt;code&gt;idempotency_key_reuse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same key sent with different text. Generate a new key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Rate limited. Wait for the &lt;code&gt;Retry-After&lt;/code&gt; header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;&lt;code&gt;service_unavailable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Temporary. Retry after &lt;code&gt;Retry-After&lt;/code&gt; if present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;&lt;code&gt;service_error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scan failed. Credits were refunded automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need to scan something longer than 25,000 words, split it on paragraph or section boundaries, scan each part, and keep track of each part's starting offset so you can shift its highlights back into the full document.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Pairing Plagiarism and AI Detection
&lt;/h2&gt;

&lt;p&gt;Plagiarism and AI-generated text are different problems. A plagiarism scan asks whether this text matches something that already exists. An AI detector asks whether this text was likely written by a model. Fresh AI output usually won't match any web source, so a plagiarism check alone won't flag it, and copied human writing won't look like AI.&lt;/p&gt;

&lt;p&gt;The same Proofademic key can call both if you gave it both scopes. The AI detector lives at &lt;code&gt;POST /api/detector/&lt;/code&gt; and returns an &lt;code&gt;ai_score&lt;/code&gt; from 0 to 1 plus a per-sentence breakdown. Running both checks in parallel gives a reviewer the full picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://developer-portal.proofademic.ai/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOFADEMIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;originalityReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;plagiarism&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/plagiarism/scan/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/detector/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plagiarism&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plagiarism&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plagiarism_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plagiarism&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;needsReview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;plagiarism&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plagiarism_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thresholds in &lt;code&gt;needsReview&lt;/code&gt; are an example, not a recommendation. Pick yours based on what a false positive costs in your product. For anything with real consequences, such as grades or contracts, route flagged work to a human instead of acting on scores automatically.&lt;/p&gt;

&lt;p&gt;Keep the cost difference in mind too. A plagiarism scan is a flat 2 credits, while the AI detector charges 1 credit per word. A 2,000-word essay costs 2 credits to scan for plagiarism and 2,000 credits for AI detection.&lt;/p&gt;

&lt;p&gt;For long texts, the detector also supports async processing with polling or a webhook (&lt;code&gt;poll: true&lt;/code&gt; or &lt;code&gt;callback_url&lt;/code&gt;). The plagiarism endpoint is synchronous only, so give its HTTP call a generous timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Pricing, Limits and Cost Planning
&lt;/h2&gt;

&lt;p&gt;Proofademic uses one shared pool of credits across all its APIs. Plagiarism scans are a flat 2 credits regardless of length, and failed scans are refunded automatically.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Credits per month&lt;/th&gt;
&lt;th&gt;Price per month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;300K&lt;/td&gt;
&lt;td&gt;300,000&lt;/td&gt;
&lt;td&gt;$49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;$129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2M&lt;/td&gt;
&lt;td&gt;2,000,000&lt;/td&gt;
&lt;td&gt;$229&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5M&lt;/td&gt;
&lt;td&gt;5,000,000&lt;/td&gt;
&lt;td&gt;$479&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12M&lt;/td&gt;
&lt;td&gt;12,000,000&lt;/td&gt;
&lt;td&gt;$899&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25M&lt;/td&gt;
&lt;td&gt;25,000,000&lt;/td&gt;
&lt;td&gt;$1,699&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Credits reset monthly and don't roll over. The free trial includes 2,000 credits for 5 days.&lt;/p&gt;

&lt;p&gt;Because the price is per scan rather than per word, the math is simple. If you only use the plagiarism checker, the 300K plan covers up to 150,000 scans a month. If you also run the AI detector, budget for that by word count, since it will dominate your spend.&lt;/p&gt;

&lt;p&gt;Rate limits from the error-handling docs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Default limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per IP (burst)&lt;/td&gt;
&lt;td&gt;60 requests per minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per IP (sustained)&lt;/td&gt;
&lt;td&gt;3,000 requests per hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per user (global)&lt;/td&gt;
&lt;td&gt;60 requests per minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI detector (sync)&lt;/td&gt;
&lt;td&gt;10 requests per minute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The docs note that limits may be higher on larger plans. If you're scanning a backlog, put the work in a queue and keep under 60 requests a minute rather than firing everything at once.&lt;/p&gt;

&lt;p&gt;The number of active API keys also depends on your plan: 1 on the trial, 5 on the 300K and 1M plans, 10 on 2M and 5M, and 25 on 12M and 25M.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Already Using Walter's API?
&lt;/h2&gt;

&lt;p&gt;If your app already uses Walter Writes AI for humanizing or AI detection, you don't need a second vendor for plagiarism. Walter's API has the same plagiarism endpoint, &lt;code&gt;POST /api/plagiarism/scan/&lt;/code&gt;, with the same request body, response fields, verdicts, idempotency behavior and flat 2-credit price. The differences are the base URL (&lt;code&gt;https://developer-portal.walterwrites.ai/api&lt;/code&gt;) and the key, which needs the plagiarism checker scope.&lt;/p&gt;

&lt;p&gt;So pick based on what else your app does. Proofademic fits products built around academic integrity and AI detection. Walter fits products that already humanize or rewrite content. The code in this post works with either one by changing the base URL and key.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Production Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep the API key on the server and in environment variables or a secrets manager.&lt;/li&gt;
&lt;li&gt;Create one key per environment, and give each key only the scopes it needs.&lt;/li&gt;
&lt;li&gt;Validate input before calling: at least 50 characters, at most 25,000 words.&lt;/li&gt;
&lt;li&gt;Send an &lt;code&gt;Idempotency-Key&lt;/code&gt; and reuse it across retries of the same scan.&lt;/li&gt;
&lt;li&gt;Retry only on &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt;, and honor &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store &lt;code&gt;scan_id&lt;/code&gt;, the verdict, the score and the exact submitted text together.&lt;/li&gt;
&lt;li&gt;Escape user text and source titles when rendering highlights.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;credits_remaining&lt;/code&gt; and alert before you run out.&lt;/li&gt;
&lt;li&gt;Show matched sources to users instead of only a score, and send borderline results to a person for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  13. Conclusion
&lt;/h2&gt;

&lt;p&gt;Adding originality checks used to mean picking between an expensive enterprise integration and building something weak in-house. With a plagiarism checker API, it's one authenticated POST request and some careful handling of the response.&lt;/p&gt;

&lt;p&gt;The parts that make it production-ready are small: idempotency keys so retries never double-charge, offset-based highlights so users can see what matched, and clear error handling around the limits. Add AI detection next to it, and your app can answer both questions reviewers ask now: was this copied, and was this written by a model?&lt;/p&gt;

&lt;h2&gt;
  
  
  14. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What does a plagiarism checker API do?&lt;/strong&gt;&lt;br&gt;
It compares submitted text against existing sources and returns a similarity score, the sources that matched and the passages that overlap. Proofademic's API returns a 0 to 1 &lt;code&gt;plagiarism_score&lt;/code&gt;, a verdict, matched sources with URLs and character-offset highlights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Proofademic have a plagiarism checker API?&lt;/strong&gt;&lt;br&gt;
Yes. Proofademic launched its plagiarism checker and public API portal in July 2026. The endpoint is &lt;code&gt;POST /api/plagiarism/scan/&lt;/code&gt; on &lt;code&gt;https://developer-portal.proofademic.ai/api&lt;/code&gt;, authenticated with an &lt;code&gt;X-API-Key&lt;/code&gt; header that has the Plagiarism Checker scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Walter Writes AI have a plagiarism API?&lt;/strong&gt;&lt;br&gt;
Yes. Walter's API offers the same &lt;code&gt;POST /api/plagiarism/scan/&lt;/code&gt; endpoint with the same request and response format, at &lt;code&gt;https://developer-portal.walterwrites.ai/api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a plagiarism scan cost?&lt;/strong&gt;&lt;br&gt;
A flat 2 credits per scan, regardless of length, up to 25,000 words. Failed scans are refunded automatically. Plans start at $49 a month for 300,000 credits, and the free trial includes 2,000 credits for 5 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the maximum text length?&lt;/strong&gt;&lt;br&gt;
25,000 words per scan, with a minimum of 50 characters. Longer documents need to be split into parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the plagiarism endpoint synchronous?&lt;/strong&gt;&lt;br&gt;
Yes, it's synchronous only. The response comes back in the same HTTP request, so set a generous timeout on your client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I avoid being charged twice when retrying?&lt;/strong&gt;&lt;br&gt;
Send an &lt;code&gt;Idempotency-Key&lt;/code&gt; header and reuse the same key for every retry of the same scan. Within 5 minutes, a repeat with the same key and text returns the original response without charging again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a plagiarism check catch AI-generated text?&lt;/strong&gt;&lt;br&gt;
Not reliably. Fresh AI output usually doesn't match existing web pages. Use an AI detector, such as Proofademic's &lt;code&gt;/api/detector/&lt;/code&gt; endpoint, alongside the plagiarism check.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>python</category>
    </item>
    <item>
      <title>Walter MCP vs Walter API: Which One Should You Use?</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:52:38 +0000</pubDate>
      <link>https://dev.to/mohab_karim/walter-mcp-vs-walter-api-which-one-should-you-use-2edm</link>
      <guid>https://dev.to/mohab_karim/walter-mcp-vs-walter-api-which-one-should-you-use-2edm</guid>
      <description>&lt;p&gt;Walter Writes AI gives developers two ways to use its humanizer and AI detector. The &lt;strong&gt;MCP server&lt;/strong&gt; plugs Walter into AI assistants like Claude, ChatGPT, and Claude Code, so the assistant can call Walter's tools in the middle of a conversation. The &lt;strong&gt;REST API&lt;/strong&gt; gives your own code direct HTTP access to Walter's services, with API keys, async jobs, and webhooks.&lt;/p&gt;

&lt;p&gt;They sound like two doors into the same room, but they're built for different jobs. They expose different capabilities, authenticate differently, and are limited differently. This post walks through both using Walter's official documentation, with code for the API and setup and prompts for MCP, so you can pick the right one for what you're building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What Each One Is&lt;/li&gt;
&lt;li&gt;Side by Side&lt;/li&gt;
&lt;li&gt;When to Use Walter MCP&lt;/li&gt;
&lt;li&gt;When to Use the Walter API&lt;/li&gt;
&lt;li&gt;The Same Workflow, Both Ways&lt;/li&gt;
&lt;li&gt;API Details That Matter in Production&lt;/li&gt;
&lt;li&gt;Pricing and Limits&lt;/li&gt;
&lt;li&gt;Using Both Together&lt;/li&gt;
&lt;li&gt;Decision Guide&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What Each One Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Walter MCP&lt;/strong&gt; is a remote Model Context Protocol server at &lt;code&gt;https://mcp-server.walterwrites.ai/mcp&lt;/code&gt;. Connect it to an MCP-compatible assistant and the assistant gets three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;walter_humanize&lt;/code&gt; rewrites AI-generated text to read naturally, with adjustable rewrite strength. It preserves keywords, named entities, URLs, and document structure, and returns a preservation report.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;walter_detect&lt;/code&gt; returns a 0 to 100 score, a verdict (likely human, unclear, or likely AI), and high-risk spans, with optional explanations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;walter_batch_humanize&lt;/code&gt; processes several texts in one run with shared defaults and per-item overrides. If one item fails, the rest still return, with per-item reports and a batch summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't call these tools yourself. You ask the assistant in plain language, and it decides when and how to call them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walter API&lt;/strong&gt; is a REST API at &lt;code&gt;https://developer-portal.walterwrites.ai/api&lt;/code&gt;. Your code sends JSON over HTTPS with an API key. It covers more services than the MCP server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/humanizer/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/detector/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/detect-and-humanize/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/plagiarism/scan/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/grammar/correct/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/voice-detector/detect/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/image-detector/predict/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/jobs/{task_id}/&lt;/code&gt; for async results&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Side by Side
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Who calls it&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: an AI assistant, on your behalf, from natural-language instructions&lt;/li&gt;
&lt;li&gt;API: your code, with explicit HTTP requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it runs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: Claude on the web, desktop, and mobile, Claude Code, ChatGPT, Gemini CLI, and Open WebUI&lt;/li&gt;
&lt;li&gt;API: anywhere you can make an HTTPS request: backends, scripts, CI jobs, serverless functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: OAuth. You sign in to your Walter account once and approve access. No key to store.&lt;/li&gt;
&lt;li&gt;API: an &lt;code&gt;X-API-Key&lt;/code&gt; header, with scopes chosen per key (Humanizer, AI Detector, Image Detector, or a combination)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: humanize, detect, and batch humanize&lt;/li&gt;
&lt;li&gt;API: humanize, detect, detect-and-humanize, plagiarism, grammar, voice detection, and image detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Humanizer controls&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: documented keyword, entity, URL, and structure preservation, adjustable rewrite strength, and a preservation report&lt;/li&gt;
&lt;li&gt;API: the humanizer reference documents &lt;code&gt;content&lt;/code&gt; plus async options (&lt;code&gt;poll&lt;/code&gt;, &lt;code&gt;callback_url&lt;/code&gt;, &lt;code&gt;webhook_secret&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Detection output&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: score from 0 to 100, verdict, high-risk spans&lt;/li&gt;
&lt;li&gt;API: &lt;code&gt;ai_score&lt;/code&gt; from 0.0 to 1.0, a binary &lt;code&gt;result&lt;/code&gt; of &lt;code&gt;ai&lt;/code&gt; or &lt;code&gt;human&lt;/code&gt;, and per-sentence &lt;code&gt;items&lt;/code&gt; with their own scores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Async and webhooks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: not applicable; the assistant waits for each tool call&lt;/li&gt;
&lt;li&gt;API: polling and webhooks on the humanizer, detector, and detect-and-humanize endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limits and billing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: runs under your Walter workspace's plan, which sets limits such as daily word volume and batch size&lt;/li&gt;
&lt;li&gt;API: a shared credit pool, generally 1 credit per word for text endpoints, plus rate limits and a pending-jobs cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP: interactive editing, content workflows inside an assistant, and agentic file work in Claude Code&lt;/li&gt;
&lt;li&gt;API: products, pipelines, automation, and anything that needs repeatable, programmatic control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. When to Use Walter MCP
&lt;/h2&gt;

&lt;p&gt;Choose MCP when a person, or an AI agent working for one, is in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You draft in an AI assistant.&lt;/strong&gt; If your content already lives in Claude or ChatGPT, MCP removes the copy-paste loop. You ask the assistant to check and humanize, and the results come back into the same conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want instruction-level control without writing code.&lt;/strong&gt; Because the assistant orchestrates the tools, you can describe a whole workflow in one prompt: detect first, humanize only the flagged spans, keep these exact keywords, then re-check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You work with files in Claude Code.&lt;/strong&gt; In Claude Code, the assistant can read drafts from your repo, run Walter on them, and write results back, which makes MCP surprisingly capable for batch content work without a custom script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want keyword preservation out of the box.&lt;/strong&gt; Walter's MCP documentation explicitly describes &lt;code&gt;walter_humanize&lt;/code&gt; as preserving keywords, entities, URLs, and structure, with a report showing what it kept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup in Claude Code&lt;/strong&gt; is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/mcp&lt;/code&gt;, select &lt;code&gt;walter-writes&lt;/code&gt;, choose &lt;strong&gt;Authenticate&lt;/strong&gt;, and sign in. For Claude on the web or desktop, ChatGPT, and the other supported clients, add a custom connector with the same URL and sign in when prompted. Walter's docs have a guide for each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Walter Skills for repeatable workflows.&lt;/strong&gt; Walter publishes 12 free, MIT-licensed skill files that tell the assistant how to run specific workflows, such as SEO writing, agency QC, local SEO, and programmatic SEO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo is at &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;github.com/walterwritesai/walter-skills&lt;/a&gt;. If you get stuck on setup, &lt;a href="https://waltermcp.com" rel="noopener noreferrer"&gt;waltermcp.com&lt;/a&gt; is a community-run guide, not an official Walter resource, so treat Walter's own docs as the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. When to Use the Walter API
&lt;/h2&gt;

&lt;p&gt;Choose the API when software is in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're building a product.&lt;/strong&gt; If Walter's humanizer or detector is a feature inside your app, such as a "check for AI" button or an automatic rewrite step, you need the API. MCP is for assistants, not end-user product features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need services beyond humanize and detect.&lt;/strong&gt; Plagiarism scanning, grammar correction, voice detection, and image detection are API-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need deterministic, testable behavior.&lt;/strong&gt; An API call does exactly what your code says, with no assistant interpreting your instructions. That's what you want in CI, scheduled jobs, and production pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need scale and async.&lt;/strong&gt; The API supports async processing with polling and webhooks, so you can submit many jobs without holding connections open, subject to your plan's pending-jobs limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need a server-side threshold.&lt;/strong&gt; The &lt;code&gt;detect-and-humanize&lt;/code&gt; endpoint scores text and only humanizes it if the AI score is above a &lt;code&gt;detection_threshold&lt;/code&gt; you set, in a single request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting a key:&lt;/strong&gt; create one on the API Keys page of the Developer Portal at &lt;code&gt;platform.walterwrites.ai/api-keys&lt;/code&gt;, choose its scopes, and copy it immediately, because it's shown only once. Walter's guidance is to keep keys out of client-side code, public repos, and logs, store them in environment variables or a secrets manager, use separate keys for development and production, and revoke keys you no longer need.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Same Workflow, Both Ways
&lt;/h2&gt;

&lt;p&gt;The workflow: check a draft, and only humanize it if it looks AI-generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  With MCP
&lt;/h3&gt;

&lt;p&gt;In Claude, Claude Code, or ChatGPT with Walter connected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the walter-writes MCP server.
1. Run walter_detect on the draft below with explanations on.
2. If it's flagged, humanize only the high-risk spans with walter_humanize,
   preserving "Acme Sync Pro", "$49/month", and every URL.
3. Run walter_detect again and give me the final text, both scores,
   and the preservation report.

[draft]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code, and the assistant handles sequencing. The trade-off is that the assistant interprets your instructions, so it's worth checking the tool calls it makes the first few times.&lt;/p&gt;

&lt;h3&gt;
  
  
  With the API
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;detect-and-humanize&lt;/code&gt; endpoint does the conditional step on Walter's side:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE&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://developer-portal.walterwrites.ai/api&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;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;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WALTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# key needs humanizer + ai_detector scopes
&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_and_fix&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;threshold&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;0.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;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/detect-and-humanize/&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;detection_threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;humanized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;final&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;humanized_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;final&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# unchanged; per-sentence scores in data["detection_items"]
&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;ai_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;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;ai_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;needs_humanization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;needs_humanization&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;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;final&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fully deterministic, easy to test, and easy to drop into a backend. The trade-off is that you write and maintain the code, and the humanizer API reference documents only the &lt;code&gt;content&lt;/code&gt; field and async options, so if you need specific phrases kept, plan to verify them in your own code after the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. API Details That Matter in Production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Word limits.&lt;/strong&gt; The humanizer accepts 50 to 2,000 words per request. Requests outside that range return &lt;code&gt;400&lt;/code&gt; before any credits are charged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync rate limits.&lt;/strong&gt; By default, sync humanizer calls are limited to 3 requests per minute and sync detector calls to 10 per minute, alongside per-user and per-IP limits. Limits may vary by plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use async for volume.&lt;/strong&gt; Async requests aren't subject to the per-minute limit. They're constrained by credits and a pending-jobs cap that depends on your plan, ranging from 2 pending jobs on the trial to 50 on the largest plan. Add &lt;code&gt;poll: true&lt;/code&gt;, a &lt;code&gt;callback_url&lt;/code&gt;, or both:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;humanize_async&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/humanizer/&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;poll&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# expect 202 Accepted
&lt;/span&gt;    &lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# Walter's recommended schedule: start at 2s, double, cap at 30s
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;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;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;job&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/jobs/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&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;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;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;job&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;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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;status&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&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="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;result&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="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;status&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# credits are refunded on failure
&lt;/span&gt;        &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results expire after one hour, after which the jobs endpoint returns &lt;code&gt;404&lt;/code&gt;, so fetch them promptly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks.&lt;/strong&gt; With a &lt;code&gt;callback_url&lt;/code&gt;, Walter POSTs the same JSON as the polling response when the job completes and retries failed deliveries up to 3 times with exponential backoff. The URL must be publicly routable. If you set a &lt;code&gt;webhook_secret&lt;/code&gt;, verify it on arrival:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&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="n"&gt;SECRET&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="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WALTER_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/walter-webhook&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;walter_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Webhook-Secret&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# payload["task_id"], payload["status"], payload["result"], ...
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Errors and retries.&lt;/strong&gt; Use the &lt;code&gt;code&lt;/code&gt; field rather than the error string for programmatic handling. Retry &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; errors with exponential backoff, and respect the &lt;code&gt;Retry-After&lt;/code&gt; header on rate limits. Don't retry &lt;code&gt;400&lt;/code&gt;, &lt;code&gt;401&lt;/code&gt;, or &lt;code&gt;403&lt;/code&gt;; fix the request, key, or account state first. Useful codes to handle explicitly include &lt;code&gt;min_words_not_met&lt;/code&gt;, &lt;code&gt;max_words_exceeded&lt;/code&gt;, &lt;code&gt;insufficient_credits&lt;/code&gt;, &lt;code&gt;credits_exhausted&lt;/code&gt;, and &lt;code&gt;too_many_pending_jobs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading detector scores.&lt;/strong&gt; The detector returns &lt;code&gt;ai_score&lt;/code&gt; from 0.0 to 1.0 and a binary &lt;code&gt;result&lt;/code&gt; that's &lt;code&gt;ai&lt;/code&gt; above 0.5. Walter's docs describe 0.0 to 0.3 as human with high confidence, 0.3 to 0.5 as likely human, 0.5 to 0.7 as likely AI, and 0.7 to 1.0 as AI-generated with high confidence. The per-sentence &lt;code&gt;items&lt;/code&gt; array is what you want if you're highlighting flagged sentences in a UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Pricing and Limits
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;API credits.&lt;/strong&gt; The API uses a single shared credit pool across Walter's services. Most text endpoints cost 1 credit per word of input, plagiarism scans cost a flat 2 credits, voice detection costs 10 credits per second of audio, and image detection is priced by resolution with a minimum of 6 credits. Failed requests are refunded automatically. Credits reset each billing cycle and don't roll over. Published monthly plans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;300K credits:&lt;/strong&gt; $49&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1M credits:&lt;/strong&gt; $129&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2M credits:&lt;/strong&gt; $229&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5M credits:&lt;/strong&gt; $479&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12M credits:&lt;/strong&gt; $899&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25M credits:&lt;/strong&gt; $1,699&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New accounts get a free trial of 2,000 credits, usable for 5 days, with no credit card required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP limits.&lt;/strong&gt; The MCP server runs under your Walter workspace's plan. Walter's documentation says availability and limits depend on the plan, including daily word volume and the number of items allowed per batch, and that workspace admins can control usage. Walter's docs don't state whether MCP usage and API credits share a pool, so check your own plan before you budget for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Using Both Together
&lt;/h2&gt;

&lt;p&gt;Many teams end up with both, and they fit together naturally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP for the editorial side.&lt;/strong&gt; Writers and editors draft, check, and humanize inside Claude or ChatGPT, with Walter Skills enforcing a consistent process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API for the system side.&lt;/strong&gt; Your CMS or publishing pipeline calls the detector on every piece before it goes live, logs the per-sentence results, and routes anything above a threshold back for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical pattern: use MCP to develop and tune a workflow interactively, such as which threshold makes sense or which phrases should never change, then encode the settled version in code against the API once it needs to run unattended.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Decision Guide
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Walter MCP if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You or your team work inside Claude, ChatGPT, Claude Code, or another MCP client&lt;/li&gt;
&lt;li&gt;You want to describe workflows in plain language instead of writing code&lt;/li&gt;
&lt;li&gt;You want documented keyword and entity preservation with a preservation report&lt;/li&gt;
&lt;li&gt;You don't want to manage API keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use the Walter API if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Walter is a feature in a product you're building&lt;/li&gt;
&lt;li&gt;You need plagiarism, grammar, voice, or image detection&lt;/li&gt;
&lt;li&gt;You need async processing, webhooks, or a server-side detection threshold&lt;/li&gt;
&lt;li&gt;You need deterministic behavior for CI, scheduled jobs, or production pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use both if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People edit interactively and systems check automatically&lt;/li&gt;
&lt;li&gt;You want to prototype a workflow in an assistant and then productionize it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between Walter MCP and the Walter API?&lt;/strong&gt;&lt;br&gt;
Walter MCP is a server that lets AI assistants like Claude, ChatGPT, and Claude Code call Walter's humanize, detect, and batch humanize tools from natural-language instructions. The Walter API is a REST API your own code calls directly with an API key, and it covers more services, including detect-and-humanize, plagiarism, grammar, voice detection, and image detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an API key to use Walter MCP?&lt;/strong&gt;&lt;br&gt;
No. Walter MCP uses OAuth. You sign in to your Walter account once and approve access, and tool calls run under your workspace's plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the Walter API support async requests?&lt;/strong&gt;&lt;br&gt;
Yes. The humanizer, detector, and detect-and-humanize endpoints accept &lt;code&gt;poll: true&lt;/code&gt;, a &lt;code&gt;callback_url&lt;/code&gt;, or both. They return &lt;code&gt;202 Accepted&lt;/code&gt; with a &lt;code&gt;task_id&lt;/code&gt;, and you fetch results from &lt;code&gt;GET /api/jobs/{task_id}/&lt;/code&gt; or receive them by webhook. Results expire after one hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the Walter API rate limits?&lt;/strong&gt;&lt;br&gt;
By default, sync humanizer requests are limited to 3 per minute and sync detector requests to 10 per minute, with additional per-user and per-IP limits. Async requests aren't subject to the per-minute limits; they're constrained by credits and a pending-jobs cap that depends on your plan. Rate-limit responses include a &lt;code&gt;Retry-After&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does the Walter API cost?&lt;/strong&gt;&lt;br&gt;
Most text endpoints cost 1 credit per word. Monthly plans range from 300,000 credits for $49 to 25 million credits for $1,699, and new accounts get a 2,000-credit free trial for 5 days with no card required. Failed requests are refunded automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I preserve keywords with the Walter API?&lt;/strong&gt;&lt;br&gt;
Walter's MCP documentation describes keyword, entity, and URL preservation for &lt;code&gt;walter_humanize&lt;/code&gt;. The humanizer API reference documents the &lt;code&gt;content&lt;/code&gt; field and async options. If you need specific phrases kept when using the API, verify them in your own code after each call, or use the MCP route for that workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Walter MCP in Claude Code?&lt;/strong&gt;&lt;br&gt;
Yes. Run &lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp -t http --scope user&lt;/code&gt;, then authenticate with &lt;code&gt;/mcp&lt;/code&gt;. Claude Code can then run Walter on files in your repo and write the results back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which should I pick if I'm just getting started?&lt;/strong&gt;&lt;br&gt;
If you mainly want to check and humanize your own writing, start with MCP in the assistant you already use. If you're building something that other people will use, or something that runs without a person in the loop, start with the API and the free trial credits.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 Advanced Walter MCP Prompts for Power Users</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Sat, 17 Oct 2026 23:08:11 +0000</pubDate>
      <link>https://dev.to/mohab_karim/5-advanced-walter-mcp-prompts-for-power-users-26d2</link>
      <guid>https://dev.to/mohab_karim/5-advanced-walter-mcp-prompts-for-power-users-26d2</guid>
      <description>&lt;p&gt;Most people using Walter Writes AI's MCP server never get past the basic version: paste a paragraph, ask Claude to humanize it, done. That works, but it leaves most of the integration unused.&lt;/p&gt;

&lt;p&gt;Once Walter is connected as an MCP server, Claude has three tools it can call, and it can call them in sequence, conditionally, and across many inputs. In Claude Code, it can also read the drafts from your repo and write the results back. That turns a one-off paraphrase into something closer to a content pipeline: detect first, humanize only what needs it, lock the terms that must survive, and report per file.&lt;/p&gt;

&lt;p&gt;This post covers the setup in two minutes, then five prompts that use it properly. Each prompt is in a code block so you can copy it straight into Claude or Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What You're Working With: Tools vs Skills&lt;/li&gt;
&lt;li&gt;Setup in Two Minutes&lt;/li&gt;
&lt;li&gt;Prompt 1: The Detect-and-Humanize Chain&lt;/li&gt;
&lt;li&gt;Prompt 2: The Keyword-Locked Humanize&lt;/li&gt;
&lt;li&gt;Prompt 3: Agency QC Over a Folder of Drafts&lt;/li&gt;
&lt;li&gt;Prompt 4: The Local SEO Multiplier&lt;/li&gt;
&lt;li&gt;Prompt 5: Programmatic SEO From a CSV&lt;/li&gt;
&lt;li&gt;Bonus: The Repurposing Cascade&lt;/li&gt;
&lt;li&gt;Stacking Prompts and Watching the Tool Calls&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What You're Working With: Tools vs Skills
&lt;/h2&gt;

&lt;p&gt;Two separate things do the work here, and mixing them up is the most common reason a prompt doesn't behave as expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MCP server provides the tools.&lt;/strong&gt; Walter's server exposes three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;walter_humanize&lt;/code&gt; rewrites AI-generated text to read naturally, with adjustable rewrite strength. It preserves keywords, named entities, URLs, and document structure, and returns a preservation report.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;walter_detect&lt;/code&gt; returns a 0 to 100 AI score, a verdict (likely human, unclear, or likely AI), and the high-risk spans behind the score. Explanations can be turned on for detailed writing advice.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;walter_batch_humanize&lt;/code&gt; processes several texts in one call with shared defaults and per-item overrides. If one item fails, the others still return, with per-item reports and a batch summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Skills provide the process.&lt;/strong&gt; Walter publishes 12 free, MIT-licensed skill files in &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;github.com/walterwritesai/walter-skills&lt;/a&gt;. Each one is a &lt;code&gt;SKILL.md&lt;/code&gt; that tells Claude how to run a specific workflow: when to humanize, which mode to use (light, balanced, or aggressive), which keywords to lock, and what the summary should look like. The four used in this post are &lt;code&gt;walter-agency-qc&lt;/code&gt;, &lt;code&gt;walter-local-seo&lt;/code&gt;, &lt;code&gt;walter-programmatic-seo&lt;/code&gt;, and &lt;code&gt;walter-content-repurposer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without the server, a skill gets followed but can't actually humanize or detect anything. Without the skills, the server works but you re-explain your process every session.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Setup in Two Minutes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude Code.&lt;/strong&gt; Add the server with HTTP transport, scoped to your user so it's available in every repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code, select &lt;code&gt;walter-writes&lt;/code&gt;, choose &lt;strong&gt;Authenticate&lt;/strong&gt;, and sign in to your Walter account in the browser. Walter uses OAuth, so there's no API key to paste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude on the web or desktop.&lt;/strong&gt; Add Walter as a custom connector using the same URL, then sign in when prompted. Walter's docs have step-by-step guides for Claude web, desktop, mobile, ChatGPT, Gemini CLI, and Open WebUI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills.&lt;/strong&gt; Install all 12 with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; walter-agency-qc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude on the web, open the skill's folder in the repo, copy its &lt;code&gt;SKILL.md&lt;/code&gt;, and paste it into your project instructions.&lt;/p&gt;

&lt;p&gt;If you get stuck on setup, &lt;a href="https://waltermcp.com" rel="noopener noreferrer"&gt;waltermcp.com&lt;/a&gt; is a community-run guide with walkthroughs. It isn't an official Walter Writes AI resource, so treat Walter's own docs and GitHub repo as the source of truth if the two ever disagree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; ask Claude to run &lt;code&gt;walter_detect&lt;/code&gt; on any paragraph. If you get a score and a verdict back, the tools are live. If you get a rewrite with no score, Claude is paraphrasing on its own and Walter isn't connected yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prompt 1: The Detect-and-Humanize Chain
&lt;/h2&gt;

&lt;p&gt;The biggest inefficiency in how people use a humanizer is running the whole document through it, including the parts that were never going to get flagged. This prompt makes detection the first step and humanization the second, applied only where it's needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the walter-writes MCP server.
1. Run walter_detect on the draft below with explanations on.
2. Humanize only the high-risk spans it returns, using balanced mode.
   Leave every other sentence exactly as written.
3. Run walter_detect again on the full result.
Return: the final text, the before and after scores, and a list of
which sentences changed.

[paste draft]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; &lt;code&gt;walter_detect&lt;/code&gt; returns the specific spans behind the score, not just a number, so Claude knows exactly which sentences to send to &lt;code&gt;walter_humanize&lt;/code&gt;. Your own phrasing survives in the sections that were already fine, you use fewer words from your plan, and you get a clean changelog instead of a full rewrite you have to diff yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Claude Code&lt;/strong&gt;, point it at a file instead of pasting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run the detect-and-humanize chain above on @drafts/launch-post.md.
Write the result back to the same file and print the changed sentences.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Prompt 2: The Keyword-Locked Humanize
&lt;/h2&gt;

&lt;p&gt;Humanizers are designed to change wording, which is a problem when your draft contains a product name, a statistic, or a technical term that has to survive word for word. Walter preserves keywords, entities, and URLs by default, but listing the critical ones explicitly removes any guesswork.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use walter_humanize on the product description below in balanced mode.
Preserve these exact terms with keyword preservation:
- "Acme Sync Pro"
- "47 percent faster"
- "ISO 27001 certified"
- every URL
Rewrite everything else freely for tone and sentence rhythm.
After humanizing, show me the preservation report and confirm each
locked term appears unchanged.

[paste description]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Be specific. "Don't touch the numbers" is weaker than listing the exact figures, because a vague instruction leaves room for interpretation. Asking Claude to confirm each locked term against the preservation report turns "I hope it kept my keywords" into a check you can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Prompt 3: Agency QC Over a Folder of Drafts
&lt;/h2&gt;

&lt;p&gt;If you QC client work regularly, the &lt;code&gt;walter-agency-qc&lt;/code&gt; skill gives Claude a fixed checklist: run detection, humanize in balanced mode if the score is above 30, re-check, verify keywords, flag thin content (under 300 words) or missing H2 structure, and output a summary with a publish recommendation.&lt;/p&gt;

&lt;p&gt;In Claude Code, that checklist can run across a whole folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the walter-agency-qc skill, QC every .md file in @content/client-drafts/.
Target keyword for each file is in its front matter under `keyword:`.
For each file:
- run the full QC checklist
- write humanized versions to content/client-drafts/qc/ with the same filename
Then print one summary table: file, original score, final score,
keyword status, word count, flags, recommendation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; The skill keeps the process identical for file one and file twenty, and the output format stays consistent, which matters when the summary goes to a client or a junior editor. Writing results to a separate &lt;code&gt;qc/&lt;/code&gt; folder keeps your originals untouched, so you can diff before you accept anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Prompt 4: The Local SEO Multiplier
&lt;/h2&gt;

&lt;p&gt;Multi-location service pages are one of the fastest ways to produce near-duplicate content, because the usual approach is a template with the city name swapped. The &lt;code&gt;walter-local-seo&lt;/code&gt; skill is built to avoid that: it requires local context in each page, preserves the "[service] in [city]" phrase in the right places, humanizes in balanced mode, and flags pages that are too similar to each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the walter-local-seo skill, adapt the base service page in
@pages/emergency-plumbing.md for these cities:
Austin, Denver, Raleigh, Tucson, Columbus.
Each page needs at least one real neighborhood or landmark and must read
like it was written locally, not templated.
Run walter_detect on each page individually.
Save each page to pages/locations/&amp;lt;city&amp;gt;.md and print the summary table:
city, word count, detection score, keyword status, similarity flags.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; "Individually" is the key word. It's easy to optimize for an average across five pages while one still reads as obviously templated. A per-page score and the skill's similarity flags catch that before you publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Prompt 5: Programmatic SEO From a CSV
&lt;/h2&gt;

&lt;p&gt;This is where Claude Code really pays off. The &lt;code&gt;walter-programmatic-seo&lt;/code&gt; skill generates one page per CSV row using a fixed template (an H1 from the row, an intro, three H2 sections, and three FAQs), humanizes each page in balanced mode, and locks the H1 phrase with keyword preservation. It also rotates sentence openings and H2 order to avoid duplicate patterns, and it flags bad rows instead of stopping the batch.&lt;/p&gt;

&lt;p&gt;Given a CSV like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;h1,city,service
Roof Repair in Austin,Austin,roof repair
Roof Repair in Denver,Denver,roof repair
Gutter Cleaning in Austin,Austin,gutter cleaning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the walter-programmatic-seo skill, generate a page for every row
in @data/pages.csv.
Lock each row's h1 exactly with keyword preservation.
Save each page as pages/generated/&amp;lt;slugified-h1&amp;gt;.md.
When finished, print the summary table: row, word count, detection score,
H1 preserved (yes/no), flags. Don't stop for a bad row; list it at the end.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; The skill handles structure and anti-duplication, Walter handles the humanizing and the per-page score, and Claude Code handles the files. Detection risk resets per page, so a per-row score is the only number that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Bonus: The Repurposing Cascade
&lt;/h2&gt;

&lt;p&gt;One long post can become a LinkedIn post, an X thread, a newsletter, and an Instagram caption. The &lt;code&gt;walter-content-repurposer&lt;/code&gt; skill handles each format's length and tone, humanizes each one in light mode so it reads casually and natively, and runs detection on every output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the walter-content-repurposer skill, turn @blog/pillar-post.md into
a LinkedIn post, an X thread, an email newsletter, and an Instagram caption.
Save them to blog/repurposed/ and print the summary table:
format, length, detection score.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each derivative gets its own score because, again, detection risk is per piece, not per source article.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Stacking Prompts and Watching the Tool Calls
&lt;/h2&gt;

&lt;p&gt;Once these feel natural, combine them. A detect-and-humanize chain with locked keywords is a perfectly good single prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run the detect-and-humanize chain on @drafts/pricing-page.md, but keep
"Acme Sync Pro", "$49/month", and "14-day free trial" exactly as written.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few habits that make these prompts reliable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name the server and the tool.&lt;/strong&gt; "Use the walter-writes MCP server to run walter_detect" removes any chance of Claude paraphrasing on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the tool calls the first few times.&lt;/strong&gt; In Claude Code you can see each call to &lt;code&gt;walter_detect&lt;/code&gt; and &lt;code&gt;walter_humanize&lt;/code&gt; as it happens. If a step you expected doesn't appear, tighten the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write outputs to a separate folder.&lt;/strong&gt; It keeps originals safe and makes &lt;code&gt;git diff&lt;/code&gt; your review step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect before you humanize.&lt;/strong&gt; It's cheaper on your plan's limits and keeps more of your original voice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage runs under your Walter workspace's plan, which sets limits like daily word volume and batch size. If a long batch stops partway, that's usually the reason; split it and continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need Walter's API for these prompts?&lt;/strong&gt;&lt;br&gt;
No. Everything here runs through the MCP server and works as normal messages to Claude or Claude Code. The REST API is only for calling Walter from your own code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between the MCP server and Walter Skills?&lt;/strong&gt;&lt;br&gt;
The server gives Claude the tools: &lt;code&gt;walter_humanize&lt;/code&gt;, &lt;code&gt;walter_detect&lt;/code&gt;, and &lt;code&gt;walter_batch_humanize&lt;/code&gt;. Skills are instruction files that tell Claude how to use those tools for a specific workflow, like agency QC or local SEO. You can use the server without skills, but the batch prompts work much better with the relevant skill installed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I install a single Walter skill?&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;npx skills add walterwritesai/walter-skills --skill walter-agency-qc&lt;/code&gt;, swapping in the skill you need, such as &lt;code&gt;walter-local-seo&lt;/code&gt;, &lt;code&gt;walter-programmatic-seo&lt;/code&gt;, or &lt;code&gt;walter-content-repurposer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is waltermcp.com official?&lt;/strong&gt;&lt;br&gt;
No. It's a community-run guide to Walter's MCP setup and skills. It's a useful second reference, but Walter's own docs and the GitHub repo are the source of truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do these prompts work in Claude Code?&lt;/strong&gt;&lt;br&gt;
Yes, and the file-based versions only work there, since Claude Code can read files with &lt;code&gt;@path&lt;/code&gt; references and write results back to your repo. Add the server with &lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp -t http --scope user&lt;/code&gt; and authenticate with &lt;code&gt;/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I make sure locked keywords survive humanization?&lt;/strong&gt;&lt;br&gt;
List the exact terms rather than describing them, and ask Claude to check each one against the preservation report &lt;code&gt;walter_humanize&lt;/code&gt; returns. That gives you a visible confirmation instead of an assumption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which humanization mode should I use?&lt;/strong&gt;&lt;br&gt;
The Walter skills use balanced mode for most content, light mode for casual social formats, and aggressive mode only when a passage still scores high after a balanced pass. Start with balanced and only go stronger if detection says you need to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work in languages other than English?&lt;/strong&gt;&lt;br&gt;
Yes. Walter's humanizer supports more than 80 languages, and the prompts work the same way since they describe process, not language.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Walter MCP in Gemini CLI: Setup and Workflows</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Tue, 13 Oct 2026 22:58:45 +0000</pubDate>
      <link>https://dev.to/mohab_karim/walter-mcp-in-gemini-cli-setup-and-workflows-2ap5</link>
      <guid>https://dev.to/mohab_karim/walter-mcp-in-gemini-cli-setup-and-workflows-2ap5</guid>
      <description>&lt;h3&gt;
  
  
  One config entry, one OAuth sign-in, and the terminal workflows that make the connection worth setting up.
&lt;/h3&gt;

&lt;p&gt;Gemini CLI can read and write the files in your project, which makes it a good home for writing tools, not just coding tools. Connect Walter Writes AI's MCP server and Gemini can run AI detection on a markdown file, humanize only the passages that need it, and write the result back to disk without you copying text between a terminal and a browser tab.&lt;/p&gt;

&lt;p&gt;Before the setup, one thing has changed this year that affects who should follow which part of this guide. On June 18, 2026, Google stopped serving Gemini CLI requests for free-tier users and Google AI Pro and Ultra subscribers and moved those users to the new Antigravity CLI. Gemini CLI still works with paid Gemini API keys, Gemini Code Assist Standard and Enterprise licenses, and Google Cloud access. If you're in that group, sections 2 through 8 are for you. If you were moved to Antigravity CLI, jump to section 9. The Walter server and the workflows are the same either way. Only the config file changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What you're connecting&lt;/li&gt;
&lt;li&gt;Add the server (settings.json)&lt;/li&gt;
&lt;li&gt;Or add it with one command&lt;/li&gt;
&lt;li&gt;Authenticate with OAuth&lt;/li&gt;
&lt;li&gt;Verify the connection&lt;/li&gt;
&lt;li&gt;Scope, tool filtering, and confirmations&lt;/li&gt;
&lt;li&gt;Add Walter Skills (optional)&lt;/li&gt;
&lt;li&gt;Workflows worth using&lt;/li&gt;
&lt;li&gt;Using Antigravity CLI instead&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What you're connecting
&lt;/h2&gt;

&lt;p&gt;Walter's MCP server is a remote server at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp-server.walterwrites.ai/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses OAuth, so there's no API key to paste. You sign in to your Walter account once in a browser, and tool calls then run under your Walter workspace's plan and limits.&lt;/p&gt;

&lt;p&gt;It exposes three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;walter_humanize&lt;/strong&gt; rewrites AI-generated text to read naturally, with adjustable rewrite strength. It preserves keywords, named entities, URLs, and document structure, and returns a preservation report with the rewritten text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;walter_detect&lt;/strong&gt; scores text from 0 to 100 for AI patterns and returns a verdict (likely human, unclear, or likely AI) plus the specific high-risk spans behind the score. Explanations can be turned on for detailed writing advice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;walter_batch_humanize&lt;/strong&gt; handles several texts in one call, with shared defaults and per-item overrides. If one item fails, the others still return, with per-item reports and a batch summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside Gemini CLI, MCP tools get a qualified name in the format &lt;code&gt;mcp_{serverName}_{toolName}&lt;/code&gt;. With the server named &lt;code&gt;walter-writes&lt;/code&gt;, you'll see names like &lt;code&gt;mcp_walter-writes_walter_detect&lt;/code&gt;. You don't need to type these. Gemini picks the tool from your request, but it helps to recognize them in confirmation prompts and logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add the server (settings.json)
&lt;/h2&gt;

&lt;p&gt;This is the method in Walter's official Gemini CLI guide.&lt;/p&gt;

&lt;p&gt;Open your user settings file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/.gemini/settings.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Walter under &lt;code&gt;mcpServers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to watch:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge, don't replace.&lt;/strong&gt; If your &lt;code&gt;settings.json&lt;/code&gt; already has content (theme, model, auth settings, other MCP servers), add the &lt;code&gt;walter-writes&lt;/code&gt; entry inside the existing &lt;code&gt;mcpServers&lt;/code&gt; object instead of overwriting the file. A full file might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"general"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultApprovalMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-github"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't add a &lt;code&gt;transport&lt;/code&gt; key.&lt;/strong&gt; Walter's docs call this out specifically: adding &lt;code&gt;"transport"&lt;/code&gt; causes a validation error, because Gemini CLI infers the transport from the URL fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on &lt;code&gt;url&lt;/code&gt; vs &lt;code&gt;httpUrl&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you've read older Gemini CLI docs, you may have seen that &lt;code&gt;url&lt;/code&gt; is for SSE and &lt;code&gt;httpUrl&lt;/code&gt; is for Streamable HTTP. That's out of date. In current Gemini CLI source, &lt;code&gt;httpUrl&lt;/code&gt; is marked deprecated, and a bare &lt;code&gt;url&lt;/code&gt; tries Streamable HTTP first and falls back to SSE only if that fails. Walter's server is Streamable HTTP, so &lt;code&gt;url&lt;/code&gt; works. If you want to be explicit, current versions accept an optional &lt;code&gt;type&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;code&gt;type&lt;/code&gt;, not &lt;code&gt;transport&lt;/code&gt;. Either form works on a current build.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Or add it with one command
&lt;/h2&gt;

&lt;p&gt;If you'd rather not edit JSON by hand, Gemini CLI has an &lt;code&gt;mcp add&lt;/code&gt; command that writes the same entry for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini mcp add &lt;span class="nt"&gt;--scope&lt;/span&gt; user &lt;span class="nt"&gt;--transport&lt;/span&gt; http walter-writes https://mcp-server.walterwrites.ai/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the flags do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--scope user&lt;/code&gt; writes to &lt;code&gt;~/.gemini/settings.json&lt;/code&gt;, so Walter is available in every project. The default is &lt;code&gt;project&lt;/code&gt;, which writes to &lt;code&gt;.gemini/settings.json&lt;/code&gt; in the current directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--transport http&lt;/code&gt; tells Gemini CLI this is a Streamable HTTP server. The default transport for &lt;code&gt;mcp add&lt;/code&gt; is &lt;code&gt;stdio&lt;/code&gt;, which is wrong for a remote URL, so don't skip this flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To check or undo it later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini mcp list
gemini mcp remove &lt;span class="nt"&gt;--scope&lt;/span&gt; user walter-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;remove&lt;/code&gt; also defaults to project scope, so pass &lt;code&gt;--scope user&lt;/code&gt; if that's where you added it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Authenticate with OAuth
&lt;/h2&gt;

&lt;p&gt;Start Gemini CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have it installed globally, &lt;code&gt;npx @google/gemini-cli&lt;/code&gt; works too.&lt;/p&gt;

&lt;p&gt;Then, inside the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mcp auth walter-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A browser window opens for Walter Writes AI. Sign in, approve access, and return to the terminal. Gemini CLI stores the token in &lt;code&gt;~/.gemini/mcp-oauth-tokens.json&lt;/code&gt; and reuses it in later sessions, so you only do this once per machine unless you revoke access.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;/mcp auth&lt;/code&gt; without a server name lists every server that needs authentication, which is handy if you've just added several.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working over SSH or in a container?&lt;/strong&gt; The OAuth flow needs a local browser and a localhost callback, so it won't complete in a headless environment. Authenticate on a machine with a browser first, or run Gemini CLI locally for the initial sign-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify the connection
&lt;/h2&gt;

&lt;p&gt;Inside the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;walter-writes&lt;/code&gt; listed as connected, with its three tools. If it shows as disconnected or needing auth, run &lt;code&gt;/mcp auth walter-writes&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Then do a quick real call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the walter-writes MCP server to run walter_detect on this text and show the score, verdict, and any high-risk spans:

"In today's fast-paced digital landscape, organizations must leverage innovative solutions to unlock new opportunities and drive meaningful growth."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini will ask you to confirm the tool call (more on that next). Approve it, and you should get a score and verdict back. That sentence is about as formulaic as AI writing gets, so expect it to score high.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Scope, tool filtering, and confirmations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;User vs project scope.&lt;/strong&gt; User scope (&lt;code&gt;~/.gemini/settings.json&lt;/code&gt;) makes Walter available everywhere. Project scope (&lt;code&gt;.gemini/settings.json&lt;/code&gt; in a repo) keeps it to one project and lets you commit the config so teammates get the same setup. Each person still authenticates with their own Walter account, because tokens live in each user's home directory, not in the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limit which tools load.&lt;/strong&gt; If a project only needs detection, an allowlist keeps Gemini from reaching for the other tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"walter_detect"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;excludeTools&lt;/code&gt; does the opposite and takes precedence if you set both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmations.&lt;/strong&gt; By default, Gemini CLI asks before every MCP tool call. You'll see four options: allow once, always allow this tool, always allow this server, or cancel. For daily use, "always allow this tool" on &lt;code&gt;walter_detect&lt;/code&gt; and &lt;code&gt;walter_humanize&lt;/code&gt; is a reasonable middle ground.&lt;/p&gt;

&lt;p&gt;There's also a &lt;code&gt;"trust": true&lt;/code&gt; setting that skips all confirmations for a server. It's convenient for scripted runs (see workflow 5), but it applies to every tool on that server, so only use it in a config you control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable or disable without deleting.&lt;/strong&gt; &lt;code&gt;gemini mcp disable walter-writes&lt;/code&gt; and &lt;code&gt;gemini mcp enable walter-writes&lt;/code&gt; (or &lt;code&gt;/mcp disable&lt;/code&gt; and &lt;code&gt;/mcp enable&lt;/code&gt; inside a session) toggle the server without removing its config. Add &lt;code&gt;--session&lt;/code&gt; to make the change apply only to the current session.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Add Walter Skills (optional)
&lt;/h2&gt;

&lt;p&gt;The MCP server gives Gemini the tools. Walter Skills give it repeatable instructions for using them on specific kinds of content: SEO articles, agency QC, local SEO pages, content refreshes, docs, newsletters, and more. There are 12 in the free, open-source repo at &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;github.com/walterwritesai/walter-skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; walter-seo-writer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skills installer supports Claude Code, Cursor, Windsurf, and 60+ other agents. If Gemini CLI isn't among the agents it offers to install for on your machine, you can still use any skill by copying its &lt;code&gt;SKILL.md&lt;/code&gt; into your project's &lt;code&gt;.gemini/skills/&lt;/code&gt; folder, or by referencing it from your &lt;code&gt;GEMINI.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Workflows worth using
&lt;/h2&gt;

&lt;p&gt;These are the workflows where running Walter from a terminal agent beats pasting text into a web app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 1: Detect first, humanize only what's flagged
&lt;/h3&gt;

&lt;p&gt;Gemini CLI lets you reference files with &lt;code&gt;@&lt;/code&gt;. Use that to point it at a draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read @content/posts/launch-announcement.md.
Use the walter-writes MCP server to run walter_detect on it with explanations on.
Then run walter_humanize only on the high-risk spans it returns.
Keep all headings, code blocks, links, and front matter unchanged.
Write the result back to the same file and show me a short diff summary plus the before and after scores.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps your own writing intact in the sections that were already fine, and it uses fewer words from your Walter plan than humanizing the whole file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 2: Protect technical terms
&lt;/h3&gt;

&lt;p&gt;Docs and dev content are full of terms that must not change: API names, flags, product names, version numbers. Walter preserves keywords and entities by default, but naming them removes any guesswork:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Humanize the "Overview" and "Why this matters" sections of @docs/getting-started.md with walter_humanize.
Do not change these terms: "Webhooks API", "--dry-run", "v3.2", "OAuth 2.0", or anything inside backticks.
Leave all other sections untouched.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Workflow 3: Batch a folder
&lt;/h3&gt;

&lt;p&gt;For a directory of short pieces (product descriptions, release notes, social captions), batch them in one call instead of one at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read every .md file in @content/changelog/.
Send the body text of each to walter_batch_humanize in a single call.
Save each result to content/changelog/humanized/ with the same filename.
Then list each file with its walter_detect score before and after.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because batch calls return per-item reports, one failed item doesn't sink the whole run. Gemini can tell you which file failed and retry just that one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 4: Make it the default with GEMINI.md
&lt;/h3&gt;

&lt;p&gt;Gemini CLI loads &lt;code&gt;GEMINI.md&lt;/code&gt; files as standing instructions. Put your house rules there so you don't repeat them in every prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Writing workflow&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; When asked to humanize content, always run walter_detect first and only humanize spans flagged as high risk.
&lt;span class="p"&gt;-&lt;/span&gt; Never change text inside code blocks, inline code, URLs, or YAML front matter.
&lt;span class="p"&gt;-&lt;/span&gt; Preserve product names, API names, and version numbers exactly.
&lt;span class="p"&gt;-&lt;/span&gt; After humanizing, run walter_detect again and report before and after scores.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put it at the project root for one repo, or in &lt;code&gt;~/.gemini/GEMINI.md&lt;/code&gt; to apply it everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow 5: Headless runs for scripts
&lt;/h3&gt;

&lt;p&gt;Gemini CLI's &lt;code&gt;-p&lt;/code&gt; flag runs a single prompt non-interactively, which makes it scriptable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Run walter_detect on @drafts/post.md and print only the score and verdict."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-mcp-server-names&lt;/span&gt; walter-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--allowed-mcp-server-names&lt;/code&gt; limits the run to the Walter server, so nothing else loads.&lt;/p&gt;

&lt;p&gt;Two things make headless runs work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate interactively first.&lt;/strong&gt; Run &lt;code&gt;gemini&lt;/code&gt; and &lt;code&gt;/mcp auth walter-writes&lt;/code&gt; once on the machine. The stored token is reused by later headless runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle confirmations.&lt;/strong&gt; There's no one to approve tool calls in a headless run. Rather than using &lt;code&gt;--yolo&lt;/code&gt; (which auto-approves every tool, including shell commands), set &lt;code&gt;"trust": true&lt;/code&gt; on just the &lt;code&gt;walter-writes&lt;/code&gt; entry in a project-scoped &lt;code&gt;.gemini/settings.json&lt;/code&gt; used for these scripts. That skips confirmations for Walter's tools only.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A practical use is a pre-publish check: run detection on every changed markdown file in a branch and print the scores, so you see which pages might draw a false positive before they go live.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Using Antigravity CLI instead
&lt;/h2&gt;

&lt;p&gt;If you were on Gemini CLI's free tier or a Google AI Pro or Ultra subscription, you're now on Antigravity CLI, launched with &lt;code&gt;agy&lt;/code&gt;. Walter's server works the same way there, but the config moved and one key changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The config file is different.&lt;/strong&gt; Antigravity CLI doesn't read MCP servers from &lt;code&gt;~/.gemini/settings.json&lt;/code&gt;. It uses a standalone file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global: &lt;code&gt;~/.gemini/config/mcp_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Workspace: &lt;code&gt;.agents/mcp_config.json&lt;/code&gt; in your project root&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The key is &lt;code&gt;serverUrl&lt;/code&gt;.&lt;/strong&gt; Antigravity doesn't support the legacy &lt;code&gt;url&lt;/code&gt; or &lt;code&gt;httpUrl&lt;/code&gt; keys for remote servers. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"walter-writes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serverUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp-server.walterwrites.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OAuth is handled for you.&lt;/strong&gt; For servers that support dynamic client registration, Antigravity runs the OAuth flow automatically, so &lt;code&gt;serverUrl&lt;/code&gt; is all you need. Sign in to Walter when prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managing it.&lt;/strong&gt; Type &lt;code&gt;/mcp&lt;/code&gt; in Antigravity CLI to open the MCP manager, where you can see server status, reload configs, and check connection logs. To turn a server off temporarily, set &lt;code&gt;"disabled": true&lt;/code&gt;. To hide specific tools, use &lt;code&gt;disabledTools&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating an existing setup.&lt;/strong&gt; Antigravity's &lt;code&gt;agy plugin import gemini&lt;/code&gt; command converts Gemini CLI extensions, including their MCP server definitions. For a server you added directly to &lt;code&gt;settings.json&lt;/code&gt;, like Walter, copy the entry into &lt;code&gt;mcp_config.json&lt;/code&gt; and rename &lt;code&gt;url&lt;/code&gt; to &lt;code&gt;serverUrl&lt;/code&gt;. Your &lt;code&gt;GEMINI.md&lt;/code&gt; files keep working unchanged. Workspace skills need to move from &lt;code&gt;.gemini/skills/&lt;/code&gt; to &lt;code&gt;.agents/skills/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Walter's documentation doesn't have an Antigravity-specific guide yet, so this section follows Antigravity's own MCP docs. Everything in section 8 carries over, since the tools and prompts are identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/mcp&lt;/code&gt; shows walter-writes as disconnected.&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;/mcp auth walter-writes&lt;/code&gt;. If it still fails, check the URL for typos and confirm it ends in &lt;code&gt;/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation error after editing settings.json.&lt;/strong&gt;&lt;br&gt;
Usually a &lt;code&gt;"transport"&lt;/code&gt; key or a JSON syntax issue such as a missing comma between server entries. Remove &lt;code&gt;transport&lt;/code&gt; (use &lt;code&gt;type&lt;/code&gt; if you need to be explicit) and validate the JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;gemini mcp add&lt;/code&gt; created a stdio server.&lt;/strong&gt;&lt;br&gt;
You left off &lt;code&gt;--transport http&lt;/code&gt;. Remove the entry and add it again with the flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser window never opens during auth.&lt;/strong&gt;&lt;br&gt;
You're likely in a headless environment. Authenticate on a machine with a browser first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini answers without calling Walter.&lt;/strong&gt;&lt;br&gt;
Name the server and the tool in your prompt ("use the walter-writes MCP server to run walter_detect"). If you've set &lt;code&gt;includeTools&lt;/code&gt;, confirm the tool you're asking for is in the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headless runs hang or skip the tool.&lt;/strong&gt;&lt;br&gt;
There's no one to approve the call. Set &lt;code&gt;"trust": true&lt;/code&gt; on the Walter entry in a project config used only for those scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calls stop working partway through a big batch.&lt;/strong&gt;&lt;br&gt;
You may have hit your Walter plan's daily word volume or batch size limit. Split the job or check your Walter account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini CLI says it can't serve requests for your account.&lt;/strong&gt;&lt;br&gt;
If you're on the free tier or a Google AI Pro or Ultra plan, that's the June 18, 2026 change. Set Walter up in Antigravity CLI using section 9.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I add Walter MCP to Gemini CLI?&lt;/strong&gt;&lt;br&gt;
Add a &lt;code&gt;walter-writes&lt;/code&gt; entry under &lt;code&gt;mcpServers&lt;/code&gt; in &lt;code&gt;~/.gemini/settings.json&lt;/code&gt; with &lt;code&gt;"url": "https://mcp-server.walterwrites.ai/mcp"&lt;/code&gt;, or run &lt;code&gt;gemini mcp add --scope user --transport http walter-writes https://mcp-server.walterwrites.ai/mcp&lt;/code&gt;. Then run &lt;code&gt;/mcp auth walter-writes&lt;/code&gt; inside Gemini CLI to sign in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Walter MCP need an API key in Gemini CLI?&lt;/strong&gt;&lt;br&gt;
No. Walter's MCP server uses OAuth. You sign in to your Walter account once in a browser, and Gemini CLI stores the token for later sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I use &lt;code&gt;url&lt;/code&gt; or &lt;code&gt;httpUrl&lt;/code&gt; for Walter in Gemini CLI?&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;url&lt;/code&gt;, as Walter's docs show. In current Gemini CLI, &lt;code&gt;httpUrl&lt;/code&gt; is deprecated, and &lt;code&gt;url&lt;/code&gt; tries Streamable HTTP first with SSE as a fallback. You can add &lt;code&gt;"type": "http"&lt;/code&gt; to be explicit, but don't add a &lt;code&gt;transport&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I still use Gemini CLI after June 18, 2026?&lt;/strong&gt;&lt;br&gt;
Yes, with paid Gemini API keys, Gemini Code Assist Standard or Enterprise licenses, or Google Cloud access. Free-tier users and Google AI Pro and Ultra subscribers were moved to Antigravity CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I set up Walter MCP in Antigravity CLI?&lt;/strong&gt;&lt;br&gt;
Add Walter to &lt;code&gt;~/.gemini/config/mcp_config.json&lt;/code&gt; (or &lt;code&gt;.agents/mcp_config.json&lt;/code&gt; for one project) under &lt;code&gt;mcpServers&lt;/code&gt;, using &lt;code&gt;"serverUrl": "https://mcp-server.walterwrites.ai/mcp"&lt;/code&gt;. Antigravity handles OAuth automatically for servers that support dynamic client registration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can Gemini CLI humanize files directly?&lt;/strong&gt;&lt;br&gt;
Yes. Reference a file with &lt;code&gt;@path/to/file.md&lt;/code&gt;, ask Gemini to run Walter's tools on it, and tell it to write the result back. Gemini CLI handles the file reading and writing, and Walter handles detection and humanization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stop Gemini CLI from asking to confirm every Walter call?&lt;/strong&gt;&lt;br&gt;
Choose "always allow this tool" or "always allow this server" at the confirmation prompt. For scripts, set &lt;code&gt;"trust": true&lt;/code&gt; on the Walter server entry, which skips confirmations for that server only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Walter MCP work with Walter Skills in Gemini CLI?&lt;/strong&gt;&lt;br&gt;
Yes. Install them with &lt;code&gt;npx skills add walterwritesai/walter-skills&lt;/code&gt;. If the installer doesn't list Gemini CLI on your machine, copy a skill's &lt;code&gt;SKILL.md&lt;/code&gt; into &lt;code&gt;.gemini/skills/&lt;/code&gt; or reference it from &lt;code&gt;GEMINI.md&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>mcp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Deepfake Detection API: Adding Image and Video Verification to Your Platform</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Mon, 05 Oct 2026 23:01:08 +0000</pubDate>
      <link>https://dev.to/mohab_karim/deepfake-detection-api-adding-image-and-video-verification-to-your-platform-3ep6</link>
      <guid>https://dev.to/mohab_karim/deepfake-detection-api-adding-image-and-video-verification-to-your-platform-3ep6</guid>
      <description>&lt;p&gt;If your platform accepts user-uploaded images or video, synthetic media is already in your pipeline. Profile photos, ID selfies during onboarding, product listing images, UGC clips submitted to a newsroom, video testimonials. Some fraction of that is generated or manipulated, and the fraction is growing.&lt;/p&gt;

&lt;p&gt;This guide covers adding detection using the DeepfakeDetector.ai API: getting a key, scanning an image, handling video asynchronously, parsing the response defensively, and, the part most guides skip, designing your product around a result that is a probability rather than a fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What the API does&lt;/li&gt;
&lt;li&gt;Getting an API key&lt;/li&gt;
&lt;li&gt;Your first request: an image by URL&lt;/li&gt;
&lt;li&gt;Uploading a file instead&lt;/li&gt;
&lt;li&gt;Reading the response&lt;/li&gt;
&lt;li&gt;Video: sync, async, and webhooks&lt;/li&gt;
&lt;li&gt;A production-ready wrapper&lt;/li&gt;
&lt;li&gt;Designing around a probabilistic verdict&lt;/li&gt;
&lt;li&gt;Privacy and data handling&lt;/li&gt;
&lt;li&gt;Rate limits, pricing, and cost control&lt;/li&gt;
&lt;li&gt;Integration patterns&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What the API does
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://deepfakedetector.ai/deepfake-detection-api" rel="noopener noreferrer"&gt;DeepfakeDetector.ai API&lt;/a&gt; takes an image, video, or voice file and returns a verdict on whether it's AI-generated or manipulated, with a confidence score and the signals that drove it.&lt;/p&gt;

&lt;p&gt;Everything sits behind one base URL with the same auth, response shape, and error semantics across media types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://app.deepfakedetector.ai/api/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoints, with the typical latencies the docs list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/v1/detect/image&lt;/code&gt; at around 0.8 seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/v1/detect/video&lt;/code&gt; at around 2.1 seconds for short clips&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/v1/detect/voice&lt;/code&gt; at around 1.4 seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/v1/jobs/{id}&lt;/code&gt; at around 50 milliseconds, for checking async jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For images, it targets AI-generated images and faces from the major generators, including Midjourney, DALL·E, Stable Diffusion, and Flux. For video, it covers face swaps, lip-sync manipulation, and fully AI-generated clips, analyzed frame by frame. This guide focuses on images and video; voice works the same way through its own endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Getting an API key
&lt;/h2&gt;

&lt;p&gt;API access starts on the Starter plan. The free tier gives you 50 detections a month in the web app, which is useful for evaluating accuracy on your own media before you pay, but it doesn't include API access.&lt;/p&gt;

&lt;p&gt;Create an account, subscribe to a plan with API access, and generate a key. Two things to handle correctly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key is shown once.&lt;/strong&gt; DeepfakeDetector.ai hashes keys at rest and displays the full key only at creation. Copy it into your secrets manager immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep it server-side.&lt;/strong&gt; Requests authenticate with a Bearer token, and a key in a browser bundle is a key anyone can read. All calls below belong on your backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DFD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk_live_..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Your first request: an image by URL
&lt;/h2&gt;

&lt;p&gt;The simplest call passes a publicly reachable HTTPS URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.deepfakedetector.ai/api/v1/detect/image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DFD_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "media_url": "https://example.com/profile-photo.jpg" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request accepts a few optional parameters worth knowing from the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;media_type&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;video&lt;/code&gt;, or &lt;code&gt;voice&lt;/code&gt;. Inferred from the MIME type if you leave it out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;strict_mode&lt;/code&gt;&lt;/strong&gt;: lowers the confidence threshold, which catches more synthetic media at the cost of more false positives. Defaults to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;callback_url&lt;/code&gt;&lt;/strong&gt;: a webhook for async jobs, covered in section 6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;retain&lt;/code&gt;&lt;/strong&gt;: keeps the file for 30 days for audit purposes. Defaults to &lt;code&gt;false&lt;/code&gt;, meaning the file is purged 60 seconds after the verdict.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your media lives in private storage, a signed URL with a short expiry works well: the API fetches the file, and the link dies shortly after.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Uploading a file instead
&lt;/h2&gt;

&lt;p&gt;When you have the file in hand rather than a URL, send it as multipart form data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.deepfakedetector.ai/api/v1/detect/image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DFD_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@./uploads/selfie.jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Either &lt;code&gt;media_url&lt;/code&gt; or a multipart upload is required, not both.&lt;/p&gt;

&lt;p&gt;Supported formats include MP4, MOV, and WEBM for video, and JPG, PNG, and WEBP for images, along with common audio formats. File size and duration limits depend on your plan; paid plans handle video up to 10 minutes per detection.&lt;/p&gt;

&lt;p&gt;A practical point for user uploads: scan the file your user actually submitted, before you transcode, resize, or compress it for storage. Re-encoding can degrade the artifacts detectors rely on, and the docs note that heavy compression is a common reason for an inconclusive result.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reading the response
&lt;/h2&gt;

&lt;p&gt;A completed detection returns structured JSON. The example in the API documentation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"job_8mTk2x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generator_family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diffusion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skin_texture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unnatural"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"geometric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symmetric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lighting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consistent"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flagged_segments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1842&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each field gives you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;verdict&lt;/code&gt; and &lt;code&gt;confidence&lt;/code&gt;&lt;/strong&gt; are what most of your logic will hang off. Confidence is a float between 0 and 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;signals&lt;/code&gt;&lt;/strong&gt; explains what drove the verdict, which is what makes a flag reviewable rather than opaque.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;generator_family&lt;/code&gt;&lt;/strong&gt; indicates the likely type of generator, for example diffusion-based.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;flagged_segments&lt;/code&gt;&lt;/strong&gt; applies to video and voice, giving start and end times in seconds for the portions that failed. For video moderation, this is the most useful field in the response: a reviewer can jump straight to second 2.4 instead of watching the whole clip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;job_id&lt;/code&gt;&lt;/strong&gt; identifies the request. Store it with every result, since it's how you trace a decision back to a specific detection later.&lt;/p&gt;

&lt;p&gt;One caution worth taking seriously. DeepfakeDetector.ai's web app presents verdicts as Authentic, Likely Synthetic, or Inconclusive with a 0 to 100 TrustScore, and the examples across its own pages don't use identical field names and verdict strings. Before you write logic that matches exact verdict values, confirm the current enum against the live API documentation, and build your thresholds on &lt;code&gt;confidence&lt;/code&gt; rather than on string matching alone. The wrapper in section 7 does exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Video: sync, async, and webhooks
&lt;/h2&gt;

&lt;p&gt;Images and short clips return quickly. Video processing scales with duration at roughly real time, so a three-minute clip takes on the order of three minutes. Holding an HTTP request open that long is a bad idea, and the API is designed for you not to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For files over 60 seconds, use a webhook.&lt;/strong&gt; Pass a &lt;code&gt;callback_url&lt;/code&gt; and the API will POST the verdict to it when the analysis finishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://app.deepfakedetector.ai/api/v1/detect/video &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$DFD_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "media_url": "https://storage.example.com/signed/clip.mp4",
    "callback_url": "https://your-app.com/webhooks/deepfake"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a &lt;code&gt;job_id&lt;/code&gt; back immediately. Store it alongside the record it belongs to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat the webhook as a notification, not as truth.&lt;/strong&gt; The official SDKs handle webhook signature verification for you. If you're calling the API directly, the simplest robust pattern is to use the webhook only as a trigger, then fetch the authoritative result yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://app.deepfakedetector.ai/api/v1/jobs/{job_id}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way a forged POST to your webhook endpoint can't push a fake verdict into your system, because the result you act on always comes from an authenticated call you made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a reconciliation sweep.&lt;/strong&gt; Webhooks occasionally fail to arrive. A scheduled job that checks any detection still pending after a reasonable window, using the same jobs endpoint, closes that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A production-ready wrapper
&lt;/h2&gt;

&lt;p&gt;Here's a Node wrapper that handles timeouts, rate limits, and the verdict normalization from section 5:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://app.deepfakedetector.ai/api/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15000&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;media_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mediaUrl&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callback_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/detect/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DFD_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retry-after&lt;/span&gt;&lt;span class="dl"&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;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Detector returned &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Async job: no verdict yet&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;flaggedSegments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagged_segments&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;detector_timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;data.job_id ?? data.id&lt;/code&gt; fallback is deliberate, given the field-name variation noted in section 5. Once you've confirmed the live schema, you can tighten it.&lt;/p&gt;

&lt;p&gt;The Python equivalent, with exponential backoff:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE&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://app.deepfakedetector.ai/api/v1&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DFD_API_KEY&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="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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_media&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_url&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;media_type&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;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;callback_url&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="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;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;media_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;media_url&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;callback_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;callback_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callback_url&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/detect/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;media_type&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="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;15&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="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="nf"&gt;int&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="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&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;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;pending&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;verdict&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verdict&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="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&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;signals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signals&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;flagged_segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flagged_segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&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: retries exhausted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather not write this yourself, DeepfakeDetector.ai publishes official SDKs that handle auth, retries, backoff, file streaming, and webhook signature verification. The API page lists clients for JavaScript and TypeScript, Python, Go, Ruby, PHP, Java, .NET, and Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Designing around a probabilistic verdict
&lt;/h2&gt;

&lt;p&gt;This is where integrations succeed or quietly fail. DeepfakeDetector.ai describes its own detection as probabilistic rather than absolute, and your product has to reflect that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use three outcomes, not two.&lt;/strong&gt; A binary pass or block forces every uncertain result into the wrong bucket. A tiered design gives the middle somewhere to go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaiting_result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;human_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high_confidence_flag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that function as a starting point, not a recommendation. Where you set the boundaries depends on what a mistake costs in your product, and you should tune them against real samples of your own media.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose &lt;code&gt;strict_mode&lt;/code&gt; by which error costs more.&lt;/strong&gt; It raises recall and lowers precision. For KYC onboarding, where a missed synthetic ID is expensive, strict mode makes sense. For a social feed, where wrongly flagging real users damages trust, the default is usually better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route inconclusive results to a person, and ask for better input.&lt;/strong&gt; Heavy compression is a common cause of low confidence. Requesting the original file, or a higher-quality copy, often resolves it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never automate a consequence off the score alone.&lt;/strong&gt; Blocking an account, rejecting an ID, or labeling a user's content as fake should involve review, especially when the verdict is anywhere near your threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Combine signals where you can.&lt;/strong&gt; A detection result is strongest alongside provenance checks, such as Content Credentials where they exist, account history, and the context of the upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Privacy and data handling
&lt;/h2&gt;

&lt;p&gt;If you're processing user media, the data handling defaults matter as much as the accuracy, and they're reasonable here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Files are purged 60 seconds after the verdict by default.&lt;/strong&gt; Setting &lt;code&gt;retain: true&lt;/code&gt; keeps a file for 30 days, which is useful for audit trails in regulated workflows. Leave it off unless you have a specific reason to turn it on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keys are hashed at rest&lt;/strong&gt;, and every call returns a request ID for auditing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EU data residency&lt;/strong&gt; is available on Business plans and above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SOC 2 is in progress, not complete.&lt;/strong&gt; The company states this plainly, and it's worth being equally plain with your own compliance team rather than assuming certification.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Rate limits, pricing, and cost control
&lt;/h2&gt;

&lt;p&gt;Rate limits run from 60 to 300 requests per minute depending on plan, with burst allowance for known clients. The API targets a 99.9 percent uptime SLO, with a status page at &lt;code&gt;status.deepfakedetector.ai&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Plans with API access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starter:&lt;/strong&gt; $49 a month for 1,000 detections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business:&lt;/strong&gt; $199 a month for 5,000 detections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; $599 a month for 20,000 detections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Annual billing saves up to 20 percent. Your monthly detections are shared between the web app and the API, so a team using both draws from one pool.&lt;/p&gt;

&lt;p&gt;A few habits keep usage under control:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't scan what can't be synthetic.&lt;/strong&gt; System-generated thumbnails, your own marketing assets, and media from trusted internal sources don't need checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deduplicate.&lt;/strong&gt; Hash uploaded files and cache results, since the same meme or stock photo can arrive thousands of times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scan at the decision point.&lt;/strong&gt; Check media when it's about to matter, at onboarding, at publication, at payout, rather than on every view.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Integration patterns
&lt;/h2&gt;

&lt;p&gt;The same API slots into quite different products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KYC and identity verification.&lt;/strong&gt; Check selfies and ID images at onboarding, before an account is approved. This is where strict mode and a mandatory review step earn their keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust and safety.&lt;/strong&gt; Screen uploaded images and video before they reach a public feed, routing flagged items to a moderation queue with &lt;code&gt;flagged_segments&lt;/code&gt; attached so moderators review only the relevant seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Newsrooms and fact-checking.&lt;/strong&gt; Verify user-submitted footage before publication, with &lt;code&gt;retain: true&lt;/code&gt; if you need an audit record of what was checked and when.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketplaces.&lt;/strong&gt; Check listing photos and seller verification images, where generated images are used to fake inventory or identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Financial fraud prevention.&lt;/strong&gt; Verify voice messages and video calls that request payments, using the voice endpoint alongside video for impersonation attempts targeting finance teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which plans include API access?&lt;/strong&gt;&lt;br&gt;
Starter, Business, and Enterprise. The free tier offers 50 detections a month in the web app but doesn't include API access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I authenticate?&lt;/strong&gt;&lt;br&gt;
With a Bearer token in the &lt;code&gt;Authorization&lt;/code&gt; header. Keys are shown once at creation and hashed at rest, so store yours immediately in a secrets manager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I send a file instead of a URL?&lt;/strong&gt;&lt;br&gt;
Yes. Send a multipart upload, or pass an HTTPS &lt;code&gt;media_url&lt;/code&gt;. One of the two is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How should I handle long videos?&lt;/strong&gt;&lt;br&gt;
Use a &lt;code&gt;callback_url&lt;/code&gt;. Video processes at roughly real time, and the API recommends webhooks for files over 60 seconds. Confirm results by fetching &lt;code&gt;GET /api/v1/jobs/{id}&lt;/code&gt; rather than trusting the webhook payload directly, unless you're using an official SDK that verifies webhook signatures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does &lt;code&gt;strict_mode&lt;/code&gt; do?&lt;/strong&gt;&lt;br&gt;
It lowers the confidence threshold, increasing recall at the cost of precision. Use it where missing a deepfake is more expensive than flagging a real file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to uploaded files?&lt;/strong&gt;&lt;br&gt;
They're purged 60 seconds after the verdict by default. Set &lt;code&gt;retain: true&lt;/code&gt; to keep a file for 30 days for audit purposes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the API SOC 2 certified?&lt;/strong&gt;&lt;br&gt;
Not yet. DeepfakeDetector.ai lists SOC 2 as in progress. EU data residency is available on Business plans and above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How accurate is it?&lt;/strong&gt;&lt;br&gt;
DeepfakeDetector.ai describes detection as probabilistic rather than absolute and returns a confidence score with every verdict. Test it against a sample of your own media before setting thresholds, since performance depends on the generators, compression, and content types your users actually upload.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Walter MCP in Claude Code: A Practical Setup Guide</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:44:03 +0000</pubDate>
      <link>https://dev.to/mohab_karim/walter-mcp-in-claude-code-a-practical-setup-guide-6mo</link>
      <guid>https://dev.to/mohab_karim/walter-mcp-in-claude-code-a-practical-setup-guide-6mo</guid>
      <description>

&lt;p&gt;title: "Walter MCP in Claude Code: A Practical Setup GuideMost Walter MCP guides cover Claude on the web: Settings, Connectors, paste a URL. Claude Code is a different setup and a meaningfully different tool. It runs in your terminal, reads and writes files in your repo, and connects to MCP servers through a CLI rather than a settings page.&lt;/p&gt;

&lt;p&gt;That last difference changes what the integration is useful for. In the web app you paste text in and get text back. In Claude Code the agent can walk a directory of markdown files, humanize a docs folder, or run a QC pass on every post in a content repo before you open a PR. This guide covers the setup and then the workflows that are actually worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What you're setting up&lt;/li&gt;
&lt;li&gt;Add the server&lt;/li&gt;
&lt;li&gt;Choose your scope&lt;/li&gt;
&lt;li&gt;Authenticate&lt;/li&gt;
&lt;li&gt;Verify the connection&lt;/li&gt;
&lt;li&gt;Install Walter Skills&lt;/li&gt;
&lt;li&gt;Workflows that only work in Claude Code&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What you're setting up
&lt;/h2&gt;

&lt;p&gt;Two separate pieces, and you need both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walter MCP&lt;/strong&gt; is the remote server that gives Claude the actual tools. It exposes three: &lt;code&gt;walter_humanize&lt;/code&gt; for rewriting, &lt;code&gt;walter_detect&lt;/code&gt; for AI detection, and &lt;code&gt;walter_batch_humanize&lt;/code&gt; for processing several texts in one run. These are tool calls out to Walter, not things Claude can do on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walter Skills&lt;/strong&gt; are markdown instruction files that tell Claude how to run a workflow: which detection threshold counts as a flag, which humanization mode to use, what the output should look like. Twelve of them, free and MIT licensed, in the &lt;a href="https://github.com/walterwritesai/walter-skills" rel="noopener noreferrer"&gt;walter-skills repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Without the server, a loaded skill gets followed perfectly and still can't perform the humanization or detection steps. Without the skills, the server works but you're re-describing your process in every session. Section 2 through 5 set up the server, section 6 adds the skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add the server
&lt;/h2&gt;

&lt;p&gt;One command, straight from Walter's official Claude Code docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-t http&lt;/code&gt; is the short form of &lt;code&gt;--transport http&lt;/code&gt;, and it's the part that matters. It tells Claude Code this is a remote server to connect to rather than a local command to launch. Leave it off and Claude Code treats the URL as something to execute, which fails. HTTP is the recommended transport for remote servers, and Walter's server runs over streamable HTTP.&lt;/p&gt;

&lt;p&gt;Use the name &lt;code&gt;walter-writes&lt;/code&gt; rather than inventing your own. It's what Walter's documentation uses throughout, so anything you look up later will match what you see in your terminal.&lt;/p&gt;

&lt;p&gt;After the command runs, the server is registered but not yet authenticated. That happens in section 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Choose your scope
&lt;/h2&gt;

&lt;p&gt;The command above adds Walter at local scope, which is the default and the one that surprises people most. Local scope means the server exists only for you, only in the project directory where you ran the command. Open a different repo and Walter isn't there.&lt;/p&gt;

&lt;p&gt;Three options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local (default).&lt;/strong&gt; Only you, only this project. Fine for trying it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User.&lt;/strong&gt; Only you, but across every project on your machine. This is the right choice for most individual writers and developers, since you'll want Walter available wherever you're working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Project.&lt;/strong&gt; Written to a &lt;code&gt;.mcp.json&lt;/code&gt; file in the repo root, which you commit so everyone on the team inherits the server. This is the one to use for a shared content or docs repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp &lt;span class="nt"&gt;-t&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project scope shares the server definition, not anyone's credentials. Each teammate still authenticates with their own Walter account, and Walter links each authorization to that person's own workspace, so tool calls run within their own plan's limits. That's also exactly what you want given Walter's terms prohibit account sharing.&lt;/p&gt;

&lt;p&gt;If the same server name exists at more than one scope, local takes precedence over project, and project takes precedence over user. Worth knowing when a teammate reports behavior you can't reproduce.&lt;/p&gt;

&lt;p&gt;One more note on project scope: Claude Code asks for approval before using servers from a checked-in &lt;code&gt;.mcp.json&lt;/code&gt;, and its own documentation is explicit that you should verify you trust a server before connecting it. That's a sensible default rather than a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Authenticate
&lt;/h2&gt;

&lt;p&gt;Walter uses OAuth, so there's no API key to paste into the command.&lt;/p&gt;

&lt;p&gt;Launch Claude Code and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then follow Walter's documented flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;walter-writes&lt;/strong&gt; from the server list and choose &lt;strong&gt;Authenticate&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Sign in to your Walter account in the browser window that opens, or create one.&lt;/li&gt;
&lt;li&gt;Approve access on the authorization consent screen.&lt;/li&gt;
&lt;li&gt;Return to Claude Code and confirm the server shows as connected and authenticated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You only authorize once. After that the connection is linked to your Walter workspace, and you can revoke access at any time from your Walter account.&lt;/p&gt;

&lt;p&gt;No config files to edit, no key in an environment variable, and nothing sensitive in your shell history.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify the connection
&lt;/h2&gt;

&lt;p&gt;Check the configuration from your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp list
claude mcp get walter-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a session, &lt;code&gt;/mcp&lt;/code&gt; shows connection status, and you should see Walter's three tools listed.&lt;/p&gt;

&lt;p&gt;The real test is a functional one. Ask Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Humanize this sentence with Walter and show the detection score before and after: "In today's rapidly evolving digital landscape, organizations must strategically leverage innovative technologies to optimize operational efficiency."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If both tools are working, you'll get the rewrite back with a preservation report, plus a detection score from 0 to 100 and a verdict of likely human, unclear, or likely AI. Ask Claude to explain the results and &lt;code&gt;walter_detect&lt;/code&gt; also returns the high-risk spans with notes on why they were flagged.&lt;/p&gt;

&lt;p&gt;If you get a rewrite with no scores, Claude is paraphrasing on its own and the Walter tools aren't being called, which means the server isn't connected or authenticated yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Install Walter Skills
&lt;/h2&gt;

&lt;p&gt;Claude Code is one of the agents the skills registry supports directly, so installation is a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That installs all twelve. To install just one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add walterwritesai/walter-skills &lt;span class="nt"&gt;--skill&lt;/span&gt; walter-docs-writer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude Code specifically, three are worth knowing first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SK-12, Docs Writer.&lt;/strong&gt; Built for help center articles and product documentation, with code blocks, commands, and technical terms passing through unchanged. This is the one to reach for in a repo, because a general humanizer that paraphrases a command breaks it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SK-02, Agency QC Pipeline.&lt;/strong&gt; Runs a full detect, humanize, flag, and report pass. Useful before a PR that touches content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SK-09, Content Refresh.&lt;/strong&gt; Updates older posts without discarding what's already ranking, which pairs naturally with Claude Code's ability to walk a folder of existing files.&lt;/p&gt;

&lt;p&gt;If you'd rather browse before installing, &lt;a href="https://waltermcp.com" rel="noopener noreferrer"&gt;waltermcp.com&lt;/a&gt; is a community-maintained guide with walkthroughs and one-click skill copying. It's community-run rather than official, so treat the GitHub repo as the source of truth for anything that needs to be current.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Workflows that only work in Claude Code
&lt;/h2&gt;

&lt;p&gt;This is the part that justifies the terminal setup over the web connector. Because Claude Code operates on your actual files, a few workflows become single prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humanize a folder, not a paste.&lt;/strong&gt; In a static site or blog repo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Go through every markdown file in &lt;code&gt;content/posts/&lt;/code&gt; from the last month. Run Walter detection on each, humanize only the ones scoring above 40, keep each post's target keyword exactly as written, and don't touch front matter or code blocks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent reads the files, sends them through Walter, and writes the changes back. You review the diff like any other change.&lt;/p&gt;

&lt;p&gt;When several files go through &lt;code&gt;walter_batch_humanize&lt;/code&gt; together, each is processed individually. If one fails, for example by exceeding your word limit, the rest still succeed and you get partial results back rather than losing the whole run. How many items a batch can hold, and your daily word volume, depend on your Walter plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QC before you open a PR.&lt;/strong&gt; With the QC skill loaded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run the QC pipeline on every markdown file changed on this branch and give me a summary I can paste into the PR description.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This turns content review into a step in your normal git workflow rather than a separate process in a separate tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix documentation without breaking it.&lt;/strong&gt; With Docs Writer loaded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rewrite the prose in &lt;code&gt;docs/&lt;/code&gt; so it reads naturally. Leave every code block, command, flag, and API path exactly as it is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Protect terms across a whole repo.&lt;/strong&gt; Keyword and brand preservation scale in a way manual editing doesn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Humanize the README and every file in &lt;code&gt;guides/&lt;/code&gt;. The product name, CLI command names, and all version numbers must survive unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Keep it interactive.&lt;/strong&gt; Walter's usage terms describe the MCP server as intended for interactive, user-to-assistant workflows, which is exactly what everything above is: you prompt, Claude acts, you review. If you want humanization running unattended, in CI or a scheduled job with nobody in the loop, that's what Walter's REST API is for, not the MCP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope what the agent touches.&lt;/strong&gt; Worth saying plainly, since Claude Code can write to any file in scope: Walter rewrites prose. It does nothing useful to code logic, and you shouldn't point it at source files. Name the directories and file types explicitly in your prompt, and review the diff before committing. The approval step Claude Code shows for each tool call is there for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"MCP server walter-writes already exists in local config."&lt;/strong&gt; You ran the add command twice at the same scope. Either it's already set up, or remove it first with &lt;code&gt;claude mcp remove walter-writes&lt;/code&gt; and re-add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walter isn't showing up in a different project.&lt;/strong&gt; It's at local scope. Re-add it with &lt;code&gt;--scope user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The URL gets treated as a command.&lt;/strong&gt; You left off &lt;code&gt;-t http&lt;/code&gt;. Remove and re-add with the flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rewrites come back with no detection scores.&lt;/strong&gt; The server isn't connected or isn't authenticated. Run &lt;code&gt;/mcp&lt;/code&gt;, select walter-writes, and choose Authenticate if it's offered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills installed but nothing different happens.&lt;/strong&gt; Skills describe a workflow; they need the server to execute it. Check the connection first, since this is almost always a missing or unauthenticated server rather than a skills problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A teammate sees different behavior.&lt;/strong&gt; Check for the same server name at multiple scopes, since local overrides project overrides user.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I add Walter MCP to Claude Code?&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;claude mcp add walter-writes https://mcp-server.walterwrites.ai/mcp -t http&lt;/code&gt;, then launch Claude Code, run &lt;code&gt;/mcp&lt;/code&gt;, select walter-writes, choose Authenticate, and sign in with your Walter account. Add &lt;code&gt;--scope user&lt;/code&gt; to make it available across all your projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an API key?&lt;/strong&gt;&lt;br&gt;
No. Walter authenticates through OAuth. You sign in with your Walter account in the browser during the &lt;code&gt;/mcp&lt;/code&gt; Authenticate step, so no key goes into the command or your environment. API keys are only needed if you use Walter's REST API directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which scope should I use?&lt;/strong&gt;&lt;br&gt;
User scope for personal use across all your projects. Project scope for a shared team repo, which writes a &lt;code&gt;.mcp.json&lt;/code&gt; you commit. The default local scope only works in the directory where you ran the command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can my team share one setup?&lt;/strong&gt;&lt;br&gt;
Yes, with project scope. The server definition is shared through the committed &lt;code&gt;.mcp.json&lt;/code&gt;, while each person authenticates with their own Walter account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is this different from Walter in the Claude web app?&lt;/strong&gt;&lt;br&gt;
Same tools, different context. The web connector works on text you paste in. Claude Code works on files in your repo, so it can humanize a whole folder, run QC on a branch before a PR, or rewrite a docs directory while leaving code blocks intact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will it modify my code?&lt;/strong&gt;&lt;br&gt;
Only if you point it at code, which you shouldn't. Walter rewrites prose. Scope your prompts to specific directories and file types, and review the diff before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need Walter Skills too?&lt;/strong&gt;&lt;br&gt;
For repeatable workflows, yes. The server provides the tools and the skills define the process. Install with &lt;code&gt;npx skills add walterwritesai/walter-skills&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is waltermcp.com official?&lt;/strong&gt;&lt;br&gt;
No, it's a community-maintained guide. Useful as a secondary reference, but check the GitHub repo and Walter's own documentation for anything that needs to be current.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What tools does Walter add to Claude Code?&lt;/strong&gt;&lt;br&gt;
Three: &lt;code&gt;walter_humanize&lt;/code&gt; for rewriting with keyword, entity, URL, and structure preservation; &lt;code&gt;walter_detect&lt;/code&gt; for a 0 to 100 score, a verdict, and high-risk spans; and &lt;code&gt;walter_batch_humanize&lt;/code&gt; for processing multiple texts in one run with partial results if any single item fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run Walter MCP in CI or a scheduled job?&lt;/strong&gt;&lt;br&gt;
Walter describes the MCP server as intended for interactive, user-to-assistant workflows. For unattended automation, use Walter's REST API instead, documented at docs.walterwrites.ai.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>mcp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Humanizer Skill: Yes, It Already Exists, Here's How to Use It</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Fri, 25 Sep 2026 20:36:07 +0000</pubDate>
      <link>https://dev.to/mohab_karim/claude-humanizer-skill-yes-it-already-exists-heres-how-to-use-it-3585</link>
      <guid>https://dev.to/mohab_karim/claude-humanizer-skill-yes-it-already-exists-heres-how-to-use-it-3585</guid>
      <description>&lt;h3&gt;
  
  
  Two skills, one shared knowledge base, MIT licensed. Plus the watermark side effect nobody in this category is discussing.
&lt;/h3&gt;

&lt;p&gt;Search volume for "claude humanizer skill" spiked this year and most of the results treat it as an open question or a DIY project. It isn't either. A working, open-source implementation has been published, and you can read every instruction it gives Claude before you install it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Yes, a Claude humanizer skill already exists&lt;/li&gt;
&lt;li&gt;What it's actually doing under the hood&lt;/li&gt;
&lt;li&gt;Why humanizers like this also strip the watermark&lt;/li&gt;
&lt;li&gt;Watermark removal is not detector evasion&lt;/li&gt;
&lt;li&gt;Where this leaves you&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Yes, a Claude humanizer skill already exists
&lt;/h2&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/walterwritesai/no-slop-ai-humanizer-rewriter" rel="noopener noreferrer"&gt;walterwritesai/no-slop-ai-humanizer-rewriter&lt;/a&gt;, MIT licensed, and it ships two skills rather than one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writer&lt;/strong&gt; (&lt;code&gt;writer/&lt;/code&gt;) is instructions for generating prose that avoids AI tells from the start. Use it when drafting articles, emails, or social posts where you'd rather not need a second-pass rewrite at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humanizer&lt;/strong&gt; (&lt;code&gt;humanizer/&lt;/code&gt;) is instructions for taking existing AI output and rewriting it so it reads like a person wrote it.&lt;/p&gt;

&lt;p&gt;Both draw from the same reference files, which is the design decision that matters most here. Same word lists, same structural patterns, same before-and-after examples. Whether you're preventing the problem or fixing it, the definition of the problem is identical.&lt;/p&gt;

&lt;p&gt;The structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── README.md
├── LICENSE
├── writer/
│   ├── SKILL.md
│   └── references/
│       ├── phrases.md
│       ├── structures.md
│       └── examples.md
└── humanizer/
    ├── SKILL.md
    └── references/
        ├── phrases.md
        ├── structures.md
        └── examples.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setup is manual, and worth being upfront about that.&lt;/strong&gt; There's no install command. You clone or download the repo, open the &lt;code&gt;SKILL.md&lt;/code&gt; for whichever skill you want, and paste its contents into your Claude project instructions along with the reference files. That's more friction than a package install, though it has a compensating benefit: you read exactly what you're loading before it runs, which is not the default experience with most agent tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What it's actually doing under the hood
&lt;/h2&gt;

&lt;p&gt;The skill works by naming specific patterns rather than issuing a general instruction to sound human. That distinction is the whole reason it produces different output from a "rewrite this to sound natural" prompt, which mostly gets you a thesaurus pass.&lt;/p&gt;

&lt;p&gt;Three reference files carry the actual knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;phrases.md&lt;/code&gt;&lt;/strong&gt; catalogs words and phrases that appear in AI output at significantly higher rates than in human writing, grouped by function: verbs, adjectives, nouns, transitions, and full phrases. Each entry has human alternatives attached, so it's a substitution table rather than a blocklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;structures.md&lt;/code&gt;&lt;/strong&gt; is the more interesting file, and it's the one that separates this from vocabulary-level tools. It catalogs the sentence and paragraph shapes models default to: setup-payoff pairs, three-item list sentences, hedge chains, reframe constructions, summary closers, and identical paragraph opening patterns. Those are structural tells, and they survive any amount of synonym swapping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;examples.md&lt;/code&gt;&lt;/strong&gt; holds before-and-after rewrites showing what each replacement looks like applied to real prose, which is what keeps the instructions from being interpreted loosely.&lt;/p&gt;

&lt;p&gt;The word lists and patterns are derived from Walter Writes' research on common ChatGPT vocabulary, which itself aggregates observations from the writing community. The repo credits stop-slop as inspiration.&lt;/p&gt;

&lt;p&gt;For comparison on how this category generally works: Mehul Gupta's publicly documented approach runs a three-pass system across roughly 29 categorized patterns with before-and-after examples for each. Different implementation, same underlying insight, which is that effective humanization is pattern-specific rather than a vibe you can prompt for.&lt;/p&gt;

&lt;p&gt;The practical mechanism in all of these is a full rewrite rather than an edit. The skill isn't nudging individual words. It's regenerating prose against a set of constraints about what the output must not contain structurally. That distinction becomes load-bearing in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why humanizers like this also strip the watermark
&lt;/h2&gt;

&lt;p&gt;Any tool that fully rewrites text clears Anthropic's watermark as a side effect, because the watermark is a statistical pattern across a long sequence of token choices rather than a tag attached to the file. Replace the sequence and there's nothing left to detect. This applies to this repo, to &lt;a href="https://walterwrites.ai/ai-humanizer/" rel="noopener noreferrer"&gt;Walter Writes&lt;/a&gt; as a maintained hosted implementation of the same mechanism, and to any other tool doing genuine rewriting.&lt;/p&gt;

&lt;p&gt;The mechanism, briefly, since it explains the behavior. Claude's watermark is applied at generation time by seeding the sampler with a secret key plus a window of the preceding tokens rather than a general random number. At positions where several next-tokens are roughly equally good, the keyed value biases the choice. One biased choice proves nothing; hundreds across a passage produce a correlation that a verifier holding the key can detect. Nothing is inserted into the text, there are no special characters, and it survives copy-paste because the mark is the word sequence itself.&lt;/p&gt;

&lt;p&gt;Two properties follow, and both are relevant to what a humanizer does to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mark is distributed, not located.&lt;/strong&gt; There is no token you could point at and call the watermarked one. It exists only as a property of the sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Removal scales with how much of the sequence you replace.&lt;/strong&gt; Anthropic's own documentation says light editing probably won't remove the watermark completely, while a complete rewrite in which every word is replaced will. That's the mechanical reason a full-rewrite humanizer has this effect while a grammar checker doesn't.&lt;/p&gt;

&lt;p&gt;Worth stating plainly, because it's the honest framing: this is a byproduct, not a feature. Neither this repo nor any hosted humanizer is built to target the watermark, and describing any of them as a watermark remover overstates what they are. A rewrite clears it for the same reason that rewriting a paragraph clears any property that depended on the original wording.&lt;/p&gt;

&lt;p&gt;Independent work supports the scale of the effect. Testing by ETH Zürich's SRI Lab found a baseline paraphraser scrubbed SynthID-Text watermarks from the large majority of passages, characterizing the scheme as harder to spoof but easier to scrub than earlier designs. Forging a mark without the key is very hard. Destroying one is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Watermark removal is not detector evasion
&lt;/h2&gt;

&lt;p&gt;These are two separate outcomes with two separate mechanisms, and the distinction is the first thing a technical reader should get straight.&lt;/p&gt;

&lt;p&gt;An AI detector reads the writing itself, inferring from stylistic patterns like word predictability and sentence-length variance. It holds no key and no privileged information. A watermark check looks for a specific key-derived statistical correlation and isn't inferring from style at all.&lt;/p&gt;

&lt;p&gt;Which means a humanizer skill addresses them through different paths. It targets detectors directly, since detectors read phrasing and the skill rewrites phrasing. It affects the watermark only incidentally, as a consequence of replacing the token sequence.&lt;/p&gt;

&lt;p&gt;Every tool in this category, including the four most commonly cited implementations, is built and marketed around the first case. None mentions the second.&lt;/p&gt;

&lt;p&gt;One practical note on scope: because the watermark only attaches to tokens the model chose, a document's watermark density is proportional to how much Claude actually wrote versus lightly touched. Hand Claude a finished draft for grammar fixes and there may be very little to detect. Ask for 2,000 words from a prompt and the mark is dense throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where this leaves you
&lt;/h2&gt;

&lt;p&gt;If you want a Claude humanizer skill, clone the repo, read the three reference files, and paste the SKILL.md you need into your project instructions. Twenty minutes, no dependencies, and you can audit every rule before it touches your writing.&lt;/p&gt;

&lt;p&gt;If you want the same mechanism without the manual setup, hosted tools do the same category of rewrite with a UI attached.&lt;/p&gt;

&lt;p&gt;And on the watermark, the accurate summary is narrower than the discussion around it suggests: a genuine rewrite clears it because the original sequence is gone, that's a consequence rather than a capability, and detection access is currently limited to organizations with specific legal obligations under EU law rather than being broadly available. Useful to understand. Not urgent to act on.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Is Claude Watermarking Code? What Developers Need to Know</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Thu, 17 Sep 2026 15:43:56 +0000</pubDate>
      <link>https://dev.to/mohab_karim/is-claude-watermarking-code-what-developers-need-to-know-9gh</link>
      <guid>https://dev.to/mohab_karim/is-claude-watermarking-code-what-developers-need-to-know-9gh</guid>
      <description>&lt;p&gt;Since August 2, 2026, Claude embeds a statistical watermark in its text output, and Anthropic's coverage list explicitly includes Claude Code. That fact generated two very different reactions in developer communities: a panic about Anthropic somehow claiming ownership of generated code, and a quieter, more legitimate question about provenance fingerprints sitting in a codebase.&lt;/p&gt;

&lt;p&gt;The first concern is unfounded. The second is real but smaller than it sounds. Both are worth working through properly, because the mechanism has specific properties that make code a genuinely bad host for this kind of watermark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Does Claude's watermark apply to code?&lt;/li&gt;
&lt;li&gt;Does this mean Anthropic can prove I used Claude to write this function?&lt;/li&gt;
&lt;li&gt;Will my formatter or linter break the watermark anyway?&lt;/li&gt;
&lt;li&gt;What's the actual risk here?&lt;/li&gt;
&lt;li&gt;If you want a clean pass on Claude-assisted comments or docs&lt;/li&gt;
&lt;li&gt;This is not the same thing as the C2PA file signature&lt;/li&gt;
&lt;li&gt;The practical takeaway&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Does Claude's watermark apply to code?
&lt;/h2&gt;

&lt;p&gt;Yes. Claude Code output falls under the same watermarking as everything else Claude generates, since it's applied at the model level rather than per-product. But code is a poor carrier for it, and the signal there is substantially weaker than in prose.&lt;/p&gt;

&lt;p&gt;The reason is entropy. The watermark works by biasing the model's choice at points where multiple next-tokens are roughly equally good, then accumulating those biased choices into a statistical pattern. That mechanism needs choices to exist. Prose offers many near-equivalent phrasings at every step. Code does not: identifiers have to match their declarations, syntax is rigid, and swapping a token usually breaks something rather than producing an equally valid alternative.&lt;/p&gt;

&lt;p&gt;There's public measurement supporting this, though with a caveat worth stating clearly. An open research repo measuring watermark strength across domains found that z-score and entropy move together almost exactly, with a z ratio of roughly 0.45x for code against prose at matched sample length, tracking an entropy ratio of 0.46x. In one run, 3 of 8 code samples fell to or below the detection threshold with no attack applied at all. The tightest sample, a bare algorithm at around 0.2 bits per token, scored 1.7 against a 2.33 threshold, which is a miss on clean, unmodified output.&lt;/p&gt;

&lt;p&gt;The caveat: those measurements were run on open-weight models, not Claude. They demonstrate the mechanism's behavior, not Claude's specific numbers. Anthropic has not published a false-positive rate for code, and reasoning from the published mechanism is not the same as confirmed behavior.&lt;/p&gt;

&lt;p&gt;One detail from that same research is worth flagging because it's counterintuitive. With extended thinking disabled, a model's pure code output scored z = 4.31 unattacked, barely above threshold. With thinking enabled, the same model in the same domain at the same length reached z = 25.53. The reasoning preamble is ordinary prose, and it carries the mark normally. The watermark isn't really in the code. It's in the text around the code.&lt;/p&gt;

&lt;p&gt;That holds for everything Claude Code emits that isn't a diff: plan summaries, commit messages, PR descriptions, and chat explanations are all prose and should mark like any other prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Does this mean Anthropic can prove I used Claude to write this function?
&lt;/h2&gt;

&lt;p&gt;No. The watermark has the same core limitation in code that it has in prose, and the low signal in code makes it worse rather than better.&lt;/p&gt;

&lt;p&gt;Anthropic states directly that the watermark cannot distinguish a case where Claude wrote something from one where Claude heavily edited it. It marks involvement, not authorship. It also carries no identifying information: nothing in the watermark or its key can be traced back to a user, an organization, or a conversation.&lt;/p&gt;

&lt;p&gt;Anthropic is equally explicit that the watermark speaks to neither ownership nor authorship, and doesn't change your rights under its terms. Whatever you generate with Claude is yours on exactly the same basis it was before August.&lt;/p&gt;

&lt;p&gt;Add the entropy problem and the reliability question gets worse in both directions. A weak signal means more false negatives, because genuinely Claude-generated code can fall below threshold on its own. It also means any threshold tuned for prose is miscalibrated for code, which pushes toward false positives if someone applies a single confidence cutoff across domains without adjusting.&lt;/p&gt;

&lt;p&gt;Anthropic's own announcement floated checking whether a PR was generated by Claude Code as a use case. That suggestion is doing more work than the mechanism supports. The PR description is prose and marks well. The diff is low-entropy code where there's little room to encode a signal. Screening contributions this way, before anyone publishes a false-positive rate on code specifically, would be building policy on the weakest application of the technology rather than the strongest.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Will my formatter or linter break the watermark anyway?
&lt;/h2&gt;

&lt;p&gt;Probably, in many cases, though this is genuinely unresolved rather than confirmed. The reasoning is straightforward: the watermark lives in exact token sequences, and the entire purpose of a formatter is to rewrite token sequences to a canonical form.&lt;/p&gt;

&lt;p&gt;What a normal dev workflow does to whatever signal survived generation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formatters&lt;/strong&gt; rewrite whitespace, line breaks, quote style, and trailing commas. explainx.ai's analysis describes a single Prettier pass as close to an adversarial attack on a token-level watermark, which is a fair characterization given that the tool's job is normalizing exactly the variation the watermark depends on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linters and auto-fixers&lt;/strong&gt; standardize naming conventions and quote characters, which touches the identifier tokens that carry what little entropy code offers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring tools&lt;/strong&gt; replace identifiers in bulk, which is the most destructive of the three for a pattern built on specific token choices in sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code review&lt;/strong&gt; rewrites chunks of the diff outright, and those rewritten sections contribute nothing to detection afterward.&lt;/p&gt;

&lt;p&gt;Stack those in the normal order, generate, format on save, lint in CI, address review comments, and there's little reason to expect a clean signal at the end. But nobody has published measurements on Claude output specifically through a real toolchain, so treat this as a well-reasoned expectation rather than a settled result. If it matters to a decision you're making, that gap is the thing to note, not paper over.&lt;/p&gt;

&lt;p&gt;Worth noting the asymmetry this creates. Your commit messages, PR bodies, and README changes go through no such pipeline. They're prose, they don't get formatted, and they carry the mark fully.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What's the actual risk here?
&lt;/h2&gt;

&lt;p&gt;The risk is a provenance fingerprint persisting in your codebase without any organizational policy for what it means. It is not Anthropic claiming your code, and it's worth dismissing that version directly.&lt;/p&gt;

&lt;p&gt;The panic version says something like: Anthropic has marked my repo, they can identify me, they have a claim on my work. Every part of that is wrong. The watermark contains no identifying information, doesn't establish authorship, doesn't affect ownership, and the detection API is currently in private preview limited to organizations with legal obligations under EU law. Your employer cannot currently check your commits, and neither can anyone else outside that group.&lt;/p&gt;

&lt;p&gt;The real version is more mundane and worth actually planning for. Detection access is expanding over time. At some point, a signal may be checkable across parts of a codebase, and organizations that have never articulated a position on AI-assisted code will be asked to have one reactively, under whatever circumstances prompted the question.&lt;/p&gt;

&lt;p&gt;Concretely, the questions worth answering before someone else asks them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is AI-assisted code acceptable in this repo, and where?&lt;/strong&gt; Most teams have an implicit answer and no written one. The implicit answer stops working the moment a specific case is disputed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the answer change by context?&lt;/strong&gt; Prototypes versus production, internal tools versus client deliverables, your own repos versus code contributed upstream. These plausibly have different answers, and writing them down is cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do your client contracts actually say?&lt;/strong&gt; This is the one that bites agencies and contractors. If a contract has an AI clause, a provenance signal in delivered code becomes a contractual question rather than a technical one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your position on contributions?&lt;/strong&gt; If you maintain an open-source project, decide whether AI-assisted PRs are welcome and say so in CONTRIBUTING.md. Don't leave it to be litigated in a thread.&lt;/p&gt;

&lt;p&gt;None of these are new questions. The watermark just removes the ability to keep deferring them.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. If you want a clean pass on Claude-assisted comments or docs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://walterwrites.ai/ai-humanizer/" rel="noopener noreferrer"&gt;Walter Writes&lt;/a&gt; handles the prose parts of a repo: READMEs, docstrings, code comments, and documentation. It rewrites text, which means the watermark doesn't survive as a byproduct of the rewrite, not because it targets the watermark.&lt;/p&gt;

&lt;p&gt;Being precise about scope, since this matters for a developer audience: this applies to prose only. It does nothing to code logic, syntax, or structure, and you should not run it over source files expecting it to. The prose in a repo is where the watermark actually lives anyway, per section 1, so that's also where a rewrite has any effect.&lt;/p&gt;

&lt;p&gt;The practical case for it is less about watermarks than about the same thing that motivates rewriting AI-drafted docs generally: generated documentation tends toward a recognizable register, and a rewrite that keeps your technical terms intact while varying the phrasing produces docs that read like your project rather than like any project. If your team has a documentation voice, that's worth preserving.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. This is not the same thing as the C2PA file signature
&lt;/h2&gt;

&lt;p&gt;Claude applies two different provenance mechanisms, and conflating them causes confusion. The text watermark is statistical and lives inside the word sequence. C2PA content credentials are cryptographic metadata attached to files, using the same open standard camera manufacturers use.&lt;/p&gt;

&lt;p&gt;The survivability difference is the practical one. A text watermark survives copy-paste, because copying the text copies the pattern. C2PA metadata is attached rather than embedded, so re-saving, converting, or running a file through a pipeline that strips metadata removes it cleanly. Different mechanism, different failure mode, different implications for anything in your build process.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The practical takeaway
&lt;/h2&gt;

&lt;p&gt;Use Claude for the draft, run a rewrite pass on the prose before it ships. That applies to documentation, READMEs, and comments, and it's a claim about text, not about code logic.&lt;/p&gt;

&lt;p&gt;On the code itself, the honest summary is that this is a weaker signal than either the announcement or the reaction suggested. Code is low entropy, the mark barely attaches, and standard tooling degrades whatever did. The part of your repo that genuinely carries a watermark is the prose, and that's the part worth thinking about.&lt;/p&gt;

&lt;p&gt;The useful move right now is not technical. It's writing down where your team stands on AI-assisted code before the question arrives with a specific case attached to it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>AI Humanizer API in 2026: A Developer Integration Guide</title>
      <dc:creator>Mohab Abdelkarim</dc:creator>
      <pubDate>Tue, 08 Sep 2026 23:50:25 +0000</pubDate>
      <link>https://dev.to/mohab_karim/ai-humanizer-api-in-2026-a-developer-integration-guide-3ici</link>
      <guid>https://dev.to/mohab_karim/ai-humanizer-api-in-2026-a-developer-integration-guide-3ici</guid>
      <description>&lt;p&gt;If your product generates text with a language model and ships it anywhere a detector might see it, raw model output is a liability. Humanizer APIs solve a narrow problem: take generated text, rewrite it so it reads like a person wrote it, and hand it back as JSON you can act on.&lt;/p&gt;

&lt;p&gt;This guide covers integrating one end to end using Walter's Humanizer API, which is the most completely documented option in this category right now. The patterns transfer to any competitor with a REST surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What You're Actually Integrating&lt;/li&gt;
&lt;li&gt;Getting a Key and Choosing Scopes&lt;/li&gt;
&lt;li&gt;Your First Request&lt;/li&gt;
&lt;li&gt;Reading the Response&lt;/li&gt;
&lt;li&gt;Sync vs Async: Picking a Mode&lt;/li&gt;
&lt;li&gt;The Combined Detect-and-Humanize Endpoint&lt;/li&gt;
&lt;li&gt;A Production-Ready Wrapper&lt;/li&gt;
&lt;li&gt;Credits, Pricing, and Cost Control&lt;/li&gt;
&lt;li&gt;Failure Modes Worth Planning For&lt;/li&gt;
&lt;li&gt;What the API Can't Do&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What You're Actually Integrating
&lt;/h2&gt;

&lt;p&gt;A humanizer API takes text in, returns rewritten text out. Underneath, it restructures sentence rhythm and paragraph flow rather than swapping synonyms, which matters because detectors measure statistical properties like token predictability and sentence-length variance, not vocabulary.&lt;/p&gt;

&lt;p&gt;Walter launched its unified API Platform in 2026, consolidating what were previously separate products behind one key and one credit balance. The available endpoints today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/api/humanizer/&lt;/code&gt; for rewriting AI text into natural output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/detector/&lt;/code&gt; for sentence-level AI probability scoring&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/detect-and-humanize/&lt;/code&gt; for both in one call&lt;/li&gt;
&lt;li&gt;Image detection, with voice detection, plagiarism, and grammar checking on the roadmap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every endpoint shares the same auth, request shape, and response structure. Practically, that means adding a second endpoint later is a few lines rather than a second integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Getting a Key and Choosing Scopes
&lt;/h2&gt;

&lt;p&gt;Sign in to the Developer Portal and create a key on the API Keys page. You select scopes at creation time: Humanizer, AI Detector, Image Detector, or any combination.&lt;/p&gt;

&lt;p&gt;Two things to get right here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope narrowly.&lt;/strong&gt; If a service only humanizes, don't give its key detector scope. This is standard least-privilege practice and costs nothing to do upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The full key is shown once.&lt;/strong&gt; Copy it at creation and store it in your secrets manager immediately. There's no retrieval later.&lt;/p&gt;

&lt;p&gt;Keys live server-side only. This is a backend integration, same reasoning as any other secret you wouldn't ship in a browser bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Your First Request
&lt;/h2&gt;

&lt;p&gt;The humanizer endpoint is a single POST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://developer-portal.walterwrites.ai/api/humanizer/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "Artificial intelligence has revolutionized numerous industries by automating complex tasks and enhancing decision-making processes."
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole minimum request. Text in the &lt;code&gt;content&lt;/code&gt; field, key in the &lt;code&gt;X-API-Key&lt;/code&gt; header. No job setup, no queue configuration.&lt;/p&gt;

&lt;p&gt;Note the auth scheme, because it's a common integration bug: this is a custom &lt;code&gt;X-API-Key&lt;/code&gt; header, not &lt;code&gt;Authorization: Bearer&lt;/code&gt;. If you're copying a wrapper from another API, that line needs changing.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reading the Response
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI has transformed many industries, making complex tasks automatic and improving how decisions are made."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"word_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credits_remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1982&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four fields matter for building on this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;result&lt;/code&gt; is your rewritten text. &lt;code&gt;status&lt;/code&gt; tells you whether the job completed. &lt;code&gt;word_count&lt;/code&gt; is what you were billed for. &lt;code&gt;credits_remaining&lt;/code&gt; is the field most integrations ignore and then regret ignoring.&lt;/p&gt;

&lt;p&gt;Log &lt;code&gt;credits_remaining&lt;/code&gt; on every call from day one. It's the cheapest possible quota monitoring, and it means you can alert on depletion before a pipeline starts failing rather than after. A simple threshold check costs you nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credits_remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;LOW_CREDIT_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Walter credits low: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credits_remaining&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; remaining`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Sync vs Async: Picking a Mode
&lt;/h2&gt;

&lt;p&gt;All service endpoints support both synchronous and asynchronous processing. Choosing correctly is mostly about where the call sits relative to a user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synchronous&lt;/strong&gt; is the default. You send the request, the connection holds, the rewritten text comes back. Execution time in the example above was 2.34 seconds for 18 words. Fine for short text in a background job. Risky if a user is watching a spinner and your input is a 2,000-word article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async with webhooks&lt;/strong&gt; is the right default for anything long-form or batch. Pass a &lt;code&gt;callback_url&lt;/code&gt; and the API posts results when the job finishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://developer-portal.walterwrites.ai/api/humanizer/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "Your long-form draft here...",
    "callback_url": "https://your-app.com/webhooks/humanizer"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Async with polling&lt;/strong&gt; exists for environments that can't accept inbound webhooks, which covers a lot of internal tooling and local development. You get a job ID back and check it on an interval.&lt;/p&gt;

&lt;p&gt;You can also combine webhook and polling on the same request, which is worth doing if webhook delivery reliability is a concern in your infrastructure. Belt and braces.&lt;/p&gt;

&lt;p&gt;A practical rule: if the input is under roughly 300 words and the call is already inside a background worker, sync is simpler and there's no reason to add job-tracking complexity. Above that, or anywhere in a request path a user is waiting on, go async.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Combined Detect-and-Humanize Endpoint
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/api/detect-and-humanize/&lt;/code&gt; is the endpoint most integrations should reach for, and it's underused because people build the two-step version first.&lt;/p&gt;

&lt;p&gt;The naive pipeline is: score everything, then humanize everything above a threshold. That's two round trips, two sets of error handling, and you're burning credits humanizing text that was already fine. The combined endpoint collapses that into one call.&lt;/p&gt;

&lt;p&gt;This matters for cost as much as for latency. If half your content doesn't need rewriting, humanizing all of it doubles your spend on the humanizer side for no benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A Production-Ready Wrapper
&lt;/h2&gt;

&lt;p&gt;Here's a Node wrapper with the things you'll otherwise add later after something breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WALTER_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://developer-portal.walterwrites.ai/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;humanize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callback_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;WALTER_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/humanizer/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WALTER_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retry-after&lt;/span&gt;&lt;span class="dl"&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;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Humanizer returned &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;wordCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;creditsRemaining&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credits_remaining&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;executionTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;humanizer_timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Python equivalent, with exponential backoff on rate limits:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE&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://developer-portal.walterwrites.ai/api&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WALTER_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;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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;humanize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&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;callback_url&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="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;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;content&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;callback_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;callback_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callback_url&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/humanizer/&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;wait&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&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;wait&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="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;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;result&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;word_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word_count&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;credits_remaining&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credits_remaining&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;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;humanizer: retries exhausted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both treat timeouts and rate limits as expected conditions rather than exceptions to handle someday. If this call sits in a publishing pipeline, a hung request must not be able to block the publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Credits, Pricing, and Cost Control
&lt;/h2&gt;

&lt;p&gt;Billing is credit-based and refreshingly simple to reason about: &lt;strong&gt;1 credit = 1 word&lt;/strong&gt; for text endpoints. Image detection is priced by resolution instead.&lt;/p&gt;

&lt;p&gt;Published tiers run from $49/month for 300,000 words up to $1,699/month for 25 million, with custom enterprise pricing above that. There's a free tier requiring no credit card, which is enough to build and test an integration before committing.&lt;/p&gt;

&lt;p&gt;Three things that actually control cost in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't humanize what doesn't need it.&lt;/strong&gt; Use the combined endpoint or a detection threshold. This is the single biggest lever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set a minimum length.&lt;/strong&gt; Humanizing a 12-word string burns credits for negligible benefit. A floor of a couple hundred words is reasonable for most pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache aggressively.&lt;/strong&gt; If the same input can be submitted twice, hash it and store the result. Re-humanizing identical text costs credits and, since these systems are largely deterministic, usually returns near-identical output anyway.&lt;/p&gt;

&lt;p&gt;The shared credit balance across endpoints is genuinely useful here. A detect-then-humanize pipeline draws from one pool, so you're not forecasting two budgets separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Failure Modes Worth Planning For
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fail open, not closed.&lt;/strong&gt; If humanization fails, your pipeline should log it and flag the item for review, not reject the content. An outage in a nice-to-have service should never become an outage in your core flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhook deliveries can be missed.&lt;/strong&gt; If you're webhook-only, add a reconciliation job that polls for jobs stuck in a pending state past some threshold. Or just enable polling alongside webhooks on the same request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency is your problem.&lt;/strong&gt; Store a hash of the input alongside the job so a retry after a network blip doesn't submit and bill twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output can shift meaning.&lt;/strong&gt; This is the failure mode people forget because it doesn't throw an error. A rewrite can alter a technical claim, a figure, or a sentence where one word carries the argument. If your content has non-negotiable terms, check whether your tool supports phrase preservation and use it. If it doesn't, diff the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What the API Can't Do
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because the marketing in this category rarely does.&lt;/p&gt;

&lt;p&gt;Walter publishes a 96.4% combined pass rate tested against GPTZero, Proofademic, Turnitin AI, Originality.ai, Copyleaks, and Sapling. That's a vendor-published number, and it's higher than most competitors publish, but 96.4% is not 100% and shouldn't be architected around as if it were.&lt;/p&gt;

&lt;p&gt;The vendor's own documentation cites two papers worth reading before you build anything high-stakes on this. The RAID benchmark (arXiv 2405.07940) found humanization drops detector accuracy by 30 to 70 percentage points across multiple systems, which quantifies both how well this works and how much it varies by detector. Sadasivan et al. 2023 (arXiv 2303.11156) argued that any detector can theoretically be evaded through paraphrasing, which cuts both ways: it explains why humanizers work, and why detection-evasion is a moving target rather than a solved state.&lt;/p&gt;

&lt;p&gt;Practically: humanizers work best on well-structured input, occasionally produce awkward phrasing that needs a human pass, and vary in effectiveness by which detector you're up against. Build a review step. Don't build a system whose correctness depends on a detection score staying below a threshold indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What authentication does the Walter Humanizer API use?&lt;/strong&gt;&lt;br&gt;
A custom &lt;code&gt;X-API-Key&lt;/code&gt; header, not bearer tokens. Keys are created in the Developer Portal with selectable scopes, and the full key is displayed only once at creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the humanizer endpoint synchronous or asynchronous?&lt;/strong&gt;&lt;br&gt;
Both. Every service endpoint supports sync responses, async with webhook delivery via &lt;code&gt;callback_url&lt;/code&gt;, async with polling, or webhook and polling combined on the same request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is usage billed?&lt;/strong&gt;&lt;br&gt;
Credit-based, at 1 credit per word for text endpoints. Image detection is priced by resolution. Every endpoint draws from a single shared monthly balance, so a detect-then-humanize pipeline doesn't require forecasting two budgets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I detect and humanize in one request?&lt;/strong&gt;&lt;br&gt;
Yes, via &lt;code&gt;/api/detect-and-humanize/&lt;/code&gt;. It's usually the better default than two separate calls, since it avoids spending humanizer credits on content that scored clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free tier for testing?&lt;/strong&gt;&lt;br&gt;
Yes. You can create an account, generate a key, and make your first calls with no credit card required, which is enough to build and validate an integration before choosing a paid tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to my keywords and product names during humanization?&lt;/strong&gt;&lt;br&gt;
That depends on whether you use phrase preservation. Humanizers routinely paraphrase brand names, technical terms, and cited figures unless told not to. If exact terms matter in your output, verify preservation support before wiring this into anything automated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should the humanizer sit in my request path or a background job?&lt;/strong&gt;&lt;br&gt;
Background job, in almost every case. Sync execution took 2.34 seconds on an 18-word sample, and longer inputs scale from there. Anything user-facing should go async with a webhook.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>python</category>
    </item>
  </channel>
</rss>
