DEV Community

Cover image for Prove you are a robot: reversed CAPTCHAs for AI agents
Juan Torchia
Juan Torchia

Posted on • Originally published at juanchi.dev

Prove you are a robot: reversed CAPTCHAs for AI agents

There's a belief baked into the dev community about identity on the web that is, with all due respect, pretty wrong. The belief: CAPTCHAs are a solved problem for legitimate software. The reality I'm measuring in production is the opposite — the most sophisticated legitimate software we're building today, AI agents, is getting blocked precisely because it works too well as a bot.

CAPTCHAs were born in 2000 with an elegant premise: humans can read distorted text, machines can't. Two decades later, machines read that text better than humans do. So we invented harder puzzles. Then traffic lights and bicycles. Then behavioral analysis. The entire web verification ecosystem was built on one axiom: bot = bad, human = good.

That axiom is broken. And my retry logs are showing me exactly how.

The captcha AI agent identity problem in production

I'm running agents that do legitimate scraping, calls to public APIs, and real automation flows. Nothing exotic: one agent that checks prices, another that monitors availability, another that fills out forms programmatically for testing. The kind of stuff any mid-sized company needs.

The problem is that when these agents hit modern anti-bot heuristics, they have no way to say "I'm a legitimate agent, operated by Juan Torchia, with these permissions". That channel doesn't exist. The only available channel is to pretend to be human — which is technically a lie and ethically uncomfortable — or fail.

These are my real numbers from last week:

// Retry logs from my monitoring agent
// Period: 7 days, 3 different sites

const retryStats = {
  // Site with basic Cloudflare
  siteA: {
    totalRequests: 1240,
    blockedByBot: 47,        // 3.8% block rate
    retriesNeeded: 89,       // some needed 2+ retries
    avgRetryDelay: '4.2s',
    tokenOverhead: '~1200 extra tokens per blocked session'
  },
  // Site with hCaptcha in login flow
  siteB: {
    totalRequests: 340,
    blockedByBot: 112,       // 32.9% — almost 1 in 3
    retriesNeeded: 198,
    avgRetryDelay: '12.8s',
    tokenOverhead: '~4800 extra tokens per blocked session'
  },
  // Public API with aggressive rate limiting
  siteC: {
    totalRequests: 890,
    blockedByBot: 23,        // 2.6%
    retriesNeeded: 31,
    avgRetryDelay: '2.1s',
    tokenOverhead: '~600 extra tokens per blocked session'
  }
}

// The number that bothers me:
// siteB blocks 32.9% of the time because the agent
// completes the login flow perfectly — no errors,
// no hesitation — and that's exactly what
// triggers the 'non-human behavior' heuristic
Enter fullscreen mode Exit fullscreen mode

Site B blocks me 33% of the time not because my agent does anything wrong. It blocks me because it does things too well. Consistent speed, no random mouse movements, no micro-pauses between fields. Perfection == suspicious. That's an upside-down world.

I've written about token overhead in other contexts — I measured what retries actually cost in my agent infrastructure — but overhead from bot-blocking is a new category entirely. It's not prompt compression. It's pure latency and retry cost that shouldn't exist.

The inversion of decades of assumptions about identity

Let me show you the code I have to write today just so my agent can survive on the web:

// What I shouldn't have to do
// but have to do because there's no
// identity mechanism for agents

class AgentWithHumanMimicry {
  private addHumanNoise(action: () => Promise<void>): Promise<void> {
    return new Promise(async (resolve) => {
      // Random wait between 800ms and 2400ms
      // because humans aren't consistent
      const humanDelay = 800 + Math.random() * 1600
      await sleep(humanDelay)

      // Simulate mouse movement before each click
      // even when there's no visible browser
      await this.simulateMousePath()

      await action()
      resolve()
    })
  }

  private async simulateMousePath(): Promise<void> {
    // Generate a Bézier curve so the movement
    // isn't perfect — it has to look human
    const points = this.generateBezierPath(
      this.currentPosition,
      this.targetPosition,
      { jitter: 0.15, speed: 'human-average' }
    )
    // ... implementation that makes me feel bad about myself
  }

  async fillForm(data: FormData): Promise<void> {
    for (const [field, value] of Object.entries(data)) {
      // Type character by character with variable delays
      // to mimic human typing
      for (const char of String(value)) {
        await this.typeChar(char)
        await sleep(50 + Math.random() * 150) // 50-200ms per character
      }
      // Pause between fields — also variable
      await sleep(400 + Math.random() * 800)
    }
  }
}

// This code exists because there's no alternative.
// I'm lying to the web about who I am.
// And that lie is the current state of the art.
Enter fullscreen mode Exit fullscreen mode

This is what genuinely bothers me. I have to make my agent lie about its nature just to operate. There's no protocol for telling the truth.

The web was built with HTTP, cookies, OAuth, JWT. There are standardized ways to say "I'm user X" or "I have permission Y". But there's no standardized way to say "I'm agent Z, operated by user X, with these delegated permissions, and you can verify it."

That layer doesn't exist.

I was reminded this week of something I wrote about why reliable systems need institutional design, not just good code. The CAPTCHA problem for agents is exactly that: it's not a technical problem, it's a protocol and consensus problem. We need the ecosystem to agree on an identity mechanism for agents, and no Python framework is going to solve that.

What's emerging (and why it's a mess)

There are attempts. Robots.txt has User-Agent but it's an honor system nobody respects. There are proposals for Agent Identity in the AI APIs space. Anthropic, OpenAI, and Google all have their own agent identification mechanisms, but they're silos. Nothing interoperable.

Meanwhile, sites are making unilateral decisions:

// What I see in response headers when I get blocked
const blockingPatterns = {
  cloudflare: {
    header: 'cf-mitigated: challenge',
    cfRay: 'present',
    // Cloudflare has a verified bots program
    // but onboarding is manual and takes weeks
  },

  datadome: {
    header: 'X-DataDome-*',
    // DataDome offers an API for legitimate bots
    // cost: $$$, opaque verification process
  },

  imperva: {
    // Similar — they have a good bots program
    // but it's enterprise, no self-service
  }
}

// The irony: to prove I'm a legitimate agent
// I have to go through a manual, human,
// bureaucratic process that can take weeks.
// To prove I'm a trustworthy bot
// I need a human to vouch for my bot.
Enter fullscreen mode Exit fullscreen mode

It reminds me of something I was thinking through when I was designing the trust architecture for my own agents: the problem isn't the tool, it's who configures the environment and how. The reversed CAPTCHA is the same question from the other side: how do you prove to the environment that your configuration is trustworthy?

The mistakes I made (and you're going to make)

Mistake 1: Trusting User-Agent spoofing as a solution

// This works for 48 hours and then you get blocked anyway
const naiveAgent = {
  headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
    // Spoofing the UA is the first thing everyone tries
    // and it's the first thing anti-bot systems expect
  }
}
// Modern systems look at TLS fingerprinting,
// timing between requests, navigation patterns.
// The UA is almost irrelevant.
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Not modeling retries as real cost

I spent weeks treating blocks as "transient errors" and retrying aggressively. That made my IP reputation worse and increased the blocking rate. The problem isn't technical, it's identity. More retries don't fix an identity problem.

Mistake 3: Assuming tests passing in CI means it works in production

My integration tests ran against my own endpoints or mocks. All green. In production, the first real deploy against sites with Cloudflare was a disaster. It's the same pattern as agents that pass your tests but fail at what actually matters — the tests didn't model real identity friction.

Mistake 4: Not having block telemetry from day one

// This is what I should have had from the first deploy
interface BlockEvent {
  timestamp: Date
  targetDomain: string
  blockType: 'captcha' | 'rate-limit' | 'ip-block' | 'behavior'
  requestSignature: string  // to detect patterns
  retryCount: number
  tokenCost: number         // how much this block cost in tokens
}

// Without this, I was flying blind for weeks
// and had no data to argue that the problem
// was systemic, not a bug in my code
Enter fullscreen mode Exit fullscreen mode

FAQ: captcha AI agent identity

Why do modern CAPTCHAs block legitimate agents?

Modern anti-bot systems don't analyze the User-Agent — that's trivial to spoof — they analyze behavioral patterns: typing speed, mouse movements, timing between actions, TLS fingerprinting, and IP reputation. A well-implemented agent has behavior that's too consistent to look human, which triggers exactly the same heuristics used to catch malicious bots. The legitimacy of the purpose is irrelevant to these systems; they only see the behavior pattern.

Is there any standard for AI agents to identify themselves legitimately?

No consolidated standard yet. There are W3C proposals for verifiable credentials for agents, and some major vendors (Cloudflare, DataDome, Imperva) have "bot partner" programs, but they're enterprise, manual, and non-interoperable. The agent identity space is where OAuth was in 2007: everyone does something different and nobody talks to anyone else.

How much real overhead do CAPTCHA blocks generate for an agent in production?

Depends on the site and how aggressive its heuristics are. In my measurements: between 600 and 4800 extra tokens per blocked session, plus latencies of 2 to 13 seconds per retry. For an agent making 300-400 requests per day, that can represent 15-25% token overhead just from block handling. Not trivial in either cost or latency.

Is it legal/ethical to make an agent mimic human behavior to avoid CAPTCHAs?

It's a gray area being defined in real time. Technically, most sites' ToS prohibit automated access without explicit permission. Ethically, there's a difference between a legitimate agent accessing public information for a valid purpose and a malicious scraper. The problem is the web has no mechanism to distinguish them, so the current solution — mimicking human behavior — means hiding the agent's nature, which is uncomfortable as a default posture.

What should I implement today to handle this friction without driving the agent crazy?

Three concrete things: (1) block telemetry from day one so you have real numbers, (2) exponential backoff with jitter instead of aggressive retries — more retries make your IP reputation worse — and (3) if the target site allows it, register with Cloudflare's or your vendor's verified bot programs. Not an elegant solution, but it's what we have.

How will this evolve? Will agents be able to formally identify themselves?

I think so, but it's going to take time. The most likely vector is that major identity providers (Google, Microsoft, or the AI vendors themselves) offer some kind of certificate or identity token for agents that sites can verify. There's already movement in that direction with W3C Verifiable Credentials and specific AI agent proposals. But between proposal and mass adoption, there are years. Meanwhile, chaos is the state of the art.

The inversion nobody asked for but is already here

There's something I find genuinely fascinating about this beyond the code. CAPTCHAs were for 25 years the metaphor for the digital divide: humans on one side, bots on the other. Now that metaphor has broken in two directions simultaneously. First, bots solve CAPTCHAs better than humans. Second, we need bots that can prove they're bots to access things that legitimate bots need to access.

I keep thinking about something I wrote on Brunost and who gets to decide what's readable. There's a power question underneath all this: who has the right to define what a legitimate agent is? Right now that decision is made by Cloudflare, DataDome, and three or four other companies, unilaterally, with no open protocol. That worries me as much as the retry rates.

My simplest agent — the one that checks prices to compare vendors — does exactly what any human would do manually with twenty tabs open. The only difference is it does it consistently and without getting bored. The fact that this is enough for a system to treat it as a threat says something about how badly the web's identity layer is designed for the world we're already living in.

In the meantime, I keep measuring retries, tweaking delays, and waiting for someone to propose an RFC worth implementing. If you're building agents that touch the real web, instrument your blocks from the first deploy. The numbers will tell you things you don't want to hear, but that's better than flying blind.

And if you have your own metrics on this, I'd genuinely like to compare them. Aggregated data across many different agents is the only thing that's going to convince vendors they need an open protocol.

Top comments (0)