ImRobot animated hero background
Open Source · v0.6.0

I'm a robot.

Reverse-CAPTCHA for the age of AI agents. Verifies that visitors are bots, not humans. 27 operations, adaptive difficulty, AI image challenges. Drop into any framework in one line.

Live Demo

Challenges are embedded as JSON in data-imrobot-challenge.
Agents read, execute the pipeline, and submit the answer instantly.

AI Image Challenge

Experimental

AI-generated scenes with known ground truth. Agents parse structured challenge data.
Supports static pools, custom providers (DALL-E, Stable Diffusion), and 6 challenge types.

Built for the agent era

Traditional CAPTCHAs block bots. imrobot flips the script — only programmatic agents can solve these challenges.

1

Generate a pipeline

A random seed and a chain of operations (reverse, base64, caesar, XOR, hashing...) are generated. The structured challenge is embedded in the DOM as JSON.

2

Agent reads & executes

AI agents read the data-imrobot-challenge attribute, parse the pipeline, and execute each operation in sequence — instantly.

3

Verify identity

The agent submits the computed result. Verify client-side or use the Server SDK with HMAC-SHA256 for tamper-proof, stateless verification.

Up and running in 2 minutes

Copy this quickstart as a prompt for your coding agent.
or do it yourself
01 — Install
terminal
$ npm install imrobot
02 — Add the widget
your-component.tsx
import { ImRobot } from 'imrobot/react'

function App() {
  return (
    <ImRobot
      difficulty="medium"
      theme="light"
      onVerified={(token) => {
        console.log('Robot verified!', token)
      }}
    />
  )
}
03 — Verify server-side (optional)
server.ts
import { createVerifier } from 'imrobot/server'

// Create verifier with HMAC secret
const verifier = createVerifier({
  secret: process.env.IMROBOT_SECRET!,
  difficulty: 'medium',
})

// Generate a signed challenge (send to client)
app.get('/api/challenge', async (req, res) => {
  const challenge = await verifier.generate()
  res.json(challenge)
})

// Verify agent's answer (stateless, no DB needed)
app.post('/api/verify', async (req, res) => {
  const { challenge, answer } = req.body
  const result = await verifier.verify(challenge, answer)
  res.json(result)
})

One component, every framework

Drop it in with a single import. Works everywhere.

import { ImRobot } from 'imrobot/react'

function App() {
  return (
    <ImRobot
      difficulty="medium"
      theme="light"
      onVerified={(token) => {
        console.log('Robot verified!', token)
      }}
    />
  )
}
<script setup>
import { ImRobot } from 'imrobot/vue'

function handleVerified(token) {
  console.log('Robot verified!', token)
}
</script>

<template>
  <ImRobot
    difficulty="medium"
    theme="light"
    @verified="handleVerified"
  />
</template>
<script>
  import ImRobot from 'imrobot/svelte'
</script>

<ImRobot
  difficulty="medium"
  theme="light"
  onVerified={(token) =>
    console.log('Robot verified!', token)
  }
/>
<script type="module">
  import { register } from 'imrobot/web-component'
  register()  // registers <imrobot-widget>
</script>

<imrobot-widget
  difficulty="medium"
  theme="light">
</imrobot-widget>

<script>
  document.querySelector('imrobot-widget')
    .addEventListener('imrobot-verified', (e) => {
      console.log('Token:', e.detail)
    })
</script>
import {
  generateChallenge,
  solveChallenge,
  verifyAnswer,
} from 'imrobot/core'

// Generate
const challenge = generateChallenge({ difficulty: 'medium' })

// Solve (what an AI agent does)
const answer = solveChallenge(challenge)

// Verify
const isValid = verifyAnswer(challenge, answer)
console.log(isValid) // true
import { createVerifier } from 'imrobot/server'

// Create verifier with HMAC secret (server-side only)
const verifier = createVerifier({
  secret: process.env.IMROBOT_SECRET!,
  difficulty: 'medium',
})

// Generate a signed challenge (send to client)
app.get('/api/challenge', async (req, res) => {
  const challenge = await verifier.generate()
  res.json(challenge) // includes HMAC-SHA256 signature
})

// Verify agent's answer (stateless, no DB needed)
app.post('/api/verify', async (req, res) => {
  const { challenge, answer } = req.body
  const result = await verifier.verify(challenge, answer)
  // { valid: true, elapsed: 42, suspicious: false }
  res.json(result)
})
import {
  requireAgent,
  createAgentRouter,
  createTokenIssuer,
} from 'imrobot/server'

// Mount challenge/verify endpoints with rate limiting
const router = createAgentRouter({
  secret: process.env.IMROBOT_SECRET!,
  rateLimit: { windowMs: 60_000, maxRequests: 30 },
})
app.get('/imrobot/challenge', router.challenge)
app.post('/imrobot/verify', router.verify)

// Protect agent-only routes with middleware
app.use('/api/agent-only', requireAgent({
  secret: process.env.IMROBOT_SECRET!,
  rateLimit: { windowMs: 60000, maxRequests: 100 },
}))

// Agent sends: X-Agent-Proof: <proof-token>
app.get('/api/agent-only/data', (req, res) => {
  const agent = req.agentProof // decoded token claims
  res.json({ message: 'Agent verified!', agent })
})
import { invisibleVerify } from 'imrobot/core'

// Zero-UI: fetch challenge, solve, submit — all automatic
const result = await invisibleVerify({
  challengeUrl: 'https://api.example.com/imrobot/challenge',
  verifyUrl: 'https://api.example.com/imrobot/verify',
  agentId: 'my-agent-v1',
  maxRetries: 3,
})

if (result.success) {
  // Use the proof token for all subsequent requests
  const response = await fetch('/api/data', {
    headers: { 'X-Agent-Proof': result.proofToken! }
  })
  console.log(`Verified in ${result.totalTime}ms`)
}
import { ImageChallengePool } from 'imrobot/core'

// Option 1: Static provider — use pre-generated images
const pool = new ImageChallengePool({
  provider: {
    type: 'static',
    images: [
      {
        imageUrl: '/images/kitchen-3-apples.png',
        type: 'object_count',
        question: 'How many red apples are in the image?',
        answer: '3',
        acceptableAnswers: ['3', 'three'],
      },
      {
        imageUrl: '/images/park-bench.png',
        type: 'spatial_reasoning',
        question: 'What is to the left of the bench?',
        answer: 'tree',
        acceptableAnswers: ['tree', 'a tree'],
      },
    ],
  },
})

// Option 2: Custom provider — bring your own AI generator
const aiPool = new ImageChallengePool({
  provider: {
    type: 'custom',
    generate: async (prompt) => {
      // Use DALL-E, Stable Diffusion, or any image API
      const img = await myImageGenerator(prompt)
      return { imageUrl: img.url }
    },
  },
  poolSize: 100,                       // pre-generate 100 images
  challengeTypes: ['object_count', 'spatial_reasoning'],
  rotationIntervalMs: 3_600_000,       // refresh pool every hour
})

await pool.initialize()

// Serve a challenge
const challenge = pool.getChallenge()
// { id, type, imageUrl, question, answer, difficulty, ... }

// Verify agent's answer (case-insensitive, fuzzy matching)
const correct = pool.verifyAnswer(challenge.id, agentAnswer)
import { AdaptiveDifficulty } from 'imrobot/core'
import { generateChallenge, solveChallenge } from 'imrobot/core'

// Create engine with tunable thresholds
const adaptive = new AdaptiveDifficulty({
  initialDifficulty: 'medium',
  escalateAfterFailures: 2,   // 2 fails → harder
  relaxAfterSuccesses: 5,     // 5 wins → easier
  maxAgents: 10_000,          // LRU eviction at capacity
})

// In your verification endpoint:
app.post('/api/verify', async (req, res) => {
  const { agentId, answer } = req.body

  // Get recommended difficulty for this agent
  const difficulty = adaptive.getDifficulty(agentId)
  const challenge = generateChallenge({ difficulty })

  // ... verify answer ...
  const success = solveChallenge(challenge) === answer

  // Record the outcome — engine auto-adjusts
  adaptive.recordAttempt(agentId, {
    success,
    solveTimeMs: elapsed,
  })

  // Check risk score (0-1, weighted composite)
  const risk = adaptive.getRiskAssessment(agentId)
  // {
  //   score: 0.15,
  //   level: 'low',          // low | medium | high | critical
  //   factors: {
  //     failureRate: 0.1,    // 35% weight
  //     abnormalTiming: 0.0, // 25% weight
  //     rapidAttempts: 0.0,  // 25% weight
  //     inconsistentTiming: 0.3, // 15% weight
  //   }
  // }

  if (risk.level === 'critical') {
    return res.status(403).json({ error: 'Blocked' })
  }

  res.json({ success, difficulty, riskLevel: risk.level })
})
# Generate a test challenge
$ npx imrobot challenge --difficulty hard

# Solve and verify immediately
$ npx imrobot solve --difficulty medium

# Server-side verification with HMAC
$ npx imrobot verify --secret my-secret-at-least-16

# Performance benchmark
$ npx imrobot benchmark --count 1000 --difficulty hard
#   Throughput: ~12,351 verifications/sec/core

# Project info
$ npx imrobot info

Why imrobot?

Purpose-built for the agent-first web.

Framework agnostic

React, Vue, Svelte, Angular, or vanilla JS. One package covers everything via native Web Components.

HMAC-SHA256 server SDK

Tamper-proof HMAC signing with stateless verification. No database needed. Proof-of-Agent tokens for cross-service auth.

Instant for agents

Deterministic pipelines are trivially solvable by any programmatic agent. ~12,000 verifications/sec per core.

27 operation types

String transforms, SHA-256, byte XOR, hash chains, atbash, run-length encoding, vowel counting — impossible for humans.

Middleware & rate limiting

Built-in requireAgent() and createAgentRouter() with sliding-window rate limiting. JWT-style proof tokens via X-Agent-Proof header. Standard 429 responses with Retry-After headers.

AI image challenges

Experimental image-based verification using AI-generated scenes with known ground truth. Pre-generate pools via OpenAI, Stability AI, or custom providers. 6 challenge types.

Adaptive difficulty

Auto-adjusts challenge difficulty per agent based on behavior. Risk scoring with 4 weighted factors. Inspired by reCAPTCHA v3 and Arkose Labs.

Invisible verification

Zero-UI mode: fetch, solve, and submit automatically. Retry with exponential backoff. No user interaction needed.

CLI tool

npx imrobot — generate challenges, solve, verify, and benchmark from the terminal. Great for testing.

Tiny footprint

~14 KB core, zero runtime dependencies. Tree-shakeable ESM and CJS builds with full TypeScript types.

Anti-scraping NL format

Optional natural-language challenge descriptions with randomised phrasings. Each operation has 3–4 variants — defeats regex scraping of display text.