/ Docs v0.6.0
Home GitHub npm

Documentation

Everything you need to integrate imrobot into your application. From a quick drop-in widget to middleware-protected APIs with Proof-of-Agent tokens and invisible verification.

Installation

Terminal
npm install imrobot

imrobot ships ESM and CJS builds with full TypeScript declarations. Zero runtime dependencies. Tree-shakeable.

ℹ️

Package exports: imrobot, imrobot/core, imrobot/react, imrobot/vue, imrobot/svelte, imrobot/web-component, imrobot/server

Quick Start

Get running in three steps with the Web Component. No build tools required.

1
Add the script tag
HTML
<script type="module">
  import { register } from 'imrobot/web-component'
  register()
</script>
2
Place the widget
HTML
<imrobot-widget difficulty="medium" theme="light"></imrobot-widget>
3
Listen for verification
JavaScript
document.querySelector('imrobot-widget')
  .addEventListener('imrobot-verified', (e) => {
    console.log('Verified!', e.detail)
    // e.detail is an ImRobotToken
  })

Framework Integration

First-class components for React, Vue, and Svelte. Same API, native feel.

React / TSX
import { ImRobot } from 'imrobot/react'

function App() {
  return (
    <ImRobot
      difficulty="medium"
      theme="light"
      onVerified={(token) => {
        console.log('Robot verified!', token)
        // Send token to your server for validation
      }}
      onError={(error) => {
        console.error('Challenge error:', error)
      }}
    />
  )
}
Vue / SFC
<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>
Svelte
<script>
  import ImRobot from 'imrobot/svelte'
</script>

<ImRobot
  difficulty="medium"
  theme="light"
  onVerified={(token) => console.log('Verified!', token)}
/>
💡

For production use, validate the token server-side with the Server SDK.

Web Component

Works in any HTML page, Angular, Astro, or any framework. No build step needed.

Attributes

AttributeValuesDefault
difficultyeasy | medium | hardmedium
themelight | darklight

Events

EventDetailDescription
imrobot-verifiedImRobotTokenFires when the challenge is solved correctly

Methods

JavaScript
const widget = document.querySelector('imrobot-widget')

widget.getChallenge()            // Returns the current Challenge object
widget.submitAnswer(answer)      // Submit a computed answer
widget.reset()                   // Generate a new challenge

Headless Core

Full control without any UI. Use imrobot/core for custom integrations, testing, or building your own widget.

TypeScript
import {
  generateChallenge,
  solveChallenge,
  verifyAnswer,
  createToken,
} from 'imrobot/core'

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

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

// 3. Verify
const isValid = verifyAnswer(challenge, answer) // true

// 4. Create a verification token
const token = createToken(challenge, answer, startTime)

Server SDK Setup

The Server SDK adds HMAC-SHA256 signing for tamper-proof, stateless verification. No database required.

⚠️

Keep your secret server-side. Never expose the HMAC secret to clients. Minimum 16 characters.

1
Set your secret
.env
IMROBOT_SECRET=your-secret-key-at-least-16-characters
2
Create a verifier
TypeScript
import { createVerifier } from 'imrobot/server'

const verifier = createVerifier({
  secret: process.env.IMROBOT_SECRET,
  difficulty: 'medium',  // optional, default: 'medium'
  ttl: 20_000,           // optional, default: per difficulty
})

Generate Signed Challenges

async verifier.generate(overrides?: { difficulty?: Difficulty; ttl?: number }): Promise<SignedChallenge>

Creates a challenge with an HMAC-SHA256 signature. Send the full SignedChallenge to the client agent.

TypeScript
const challenge = await verifier.generate()
// { ...challenge, hmac: '64-char-hex', expiresAt: 1711234567890 }

// Override per-request:
const hard = await verifier.generate({ difficulty: 'hard', ttl: 10_000 })

Stateless: The HMAC signature encodes all verification state. No database needed. Send the challenge to the client and verify when it comes back.

Verify Answers

async verifier.verify(challenge: SignedChallenge, answer: string): Promise<VerifyResult>

Checks HMAC integrity, expiration, and answer correctness in a single call.

TypeScript
const result = await verifier.verify(challenge, answer)

// VerifyResult {
//   valid: boolean
//   reason?: 'expired' | 'invalid_hmac' | 'wrong_answer' | 'tampered'
//   elapsed?: number      // ms since challenge creation
//   suspicious?: boolean  // true if elapsed > 5s
// }

Verification pipeline

OrderCheckFailure
1HMAC signature validityinvalid_hmac
2Challenge expirationexpired
3Answer vs verification hashwrong_answer
4Pipeline re-executiontampered

Express / Next.js Examples

server.ts
import express from 'express'
import { createVerifier } from 'imrobot/server'

const app = express()
app.use(express.json())

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

app.get('/api/challenge', async (req, res) => {
  const challenge = await verifier.generate()
  res.json(challenge)
})

app.post('/api/verify', async (req, res) => {
  const { challenge, answer } = req.body
  const result = await verifier.verify(challenge, answer)

  if (result.valid) {
    res.json({ verified: true, elapsed: result.elapsed })
  } else {
    res.status(403).json({ verified: false, reason: result.reason })
  }
})

app.listen(3000)
app/api/challenge/route.ts
import { createVerifier } from 'imrobot/server'

const verifier = createVerifier({
  secret: process.env.IMROBOT_SECRET!,
})

export async function GET() {
  const challenge = await verifier.generate()
  return Response.json(challenge)
}
app/api/verify/route.ts
import { createVerifier } from 'imrobot/server'

const verifier = createVerifier({
  secret: process.env.IMROBOT_SECRET!,
})

export async function POST(req: Request) {
  const { challenge, answer } = await req.json()
  const result = await verifier.verify(challenge, answer)

  if (!result.valid) {
    return Response.json({ verified: false, reason: result.reason }, { status: 403 })
  }
  return Response.json({ verified: true, elapsed: result.elapsed })
}
agent.ts
import { solveChallenge } from 'imrobot/core'

// 1. Fetch signed challenge from your server
const res = await fetch('/api/challenge')
const challenge = await res.json()

// 2. Solve the challenge
const answer = solveChallenge(challenge)

// 3. Submit answer for server-side verification
const verifyRes = await fetch('/api/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ challenge, answer }),
})

const result = await verifyRes.json()
console.log(result) // { verified: true, elapsed: 12 }

Middleware

Framework-agnostic middleware that protects your endpoints by requiring a valid Proof-of-Agent token. Works with Express, Fastify, Next.js, or any Node.js server.

requireAgent(options)

Returns a middleware function that extracts the X-Agent-Proof header, verifies the token, and rejects unauthorized requests with a 401 or 429 status.

TypeScript
import express from 'express'
import { requireAgent, createAgentRouter } from 'imrobot/server'

const app = express()
app.use(express.json())

// Mount challenge/verify endpoints
const router = createAgentRouter({
  secret: process.env.IMROBOT_SECRET!,
  difficulty: 'medium',
  tokenTTL: 300_000, // 5 min token lifetime
})
app.post('/imrobot/challenge', router.challenge)
app.post('/imrobot/verify', router.verify)

// Protect your API with the middleware
const agentOnly = requireAgent({
  secret: process.env.IMROBOT_SECRET!,
  rateLimit: { windowMs: 60_000, maxRequests: 30 },
})
app.get('/api/data', agentOnly, (req, res) => {
  res.json({ message: 'Only verified agents reach here' })
})

createAgentRouter(options)

Factory that returns { challenge, verify } request handlers. Mount them at your preferred paths. The verify handler issues a Proof-of-Agent token on success.

ℹ️

Rate limiting: Both createAgentRouter and requireAgent include built-in per-IP rate limiting via rateLimit: { windowMs, maxRequests }. Requests exceeding the limit receive a 429 Too Many Requests response with standard headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After).

Proof-of-Agent Tokens

JWT-like tokens issued upon successful verification. Format: base64url(header).base64url(payload).base64url(signature), signed with HMAC-SHA256.

ProofTokenIssuer

TypeScript
import { createTokenIssuer } from 'imrobot/server'

const issuer = createTokenIssuer({
  secret: process.env.IMROBOT_SECRET!,
  issuer: 'my-app',
  tokenTTL: 300_000, // 5 minutes
})

// Issue a token after successful challenge verification
const token = await issuer.issue({
  challengeId: challenge.id,
  difficulty: 'medium',
  solveTimeMs: 42,
  suspicious: false,
})

// Verify a token from the X-Agent-Proof header
const result = await issuer.verify(token)
// { valid: true, payload: { iss, sub, iat, exp, imr: { ... } } }

// Decode without verification (for inspection)
const payload = ProofTokenIssuer.decode(token)
alg'HMAC-SHA256'Signing algorithm
typ'agent+jwt'Token type identifier
issstringIssuer identifier
imrobjectimrobot claims: challenge_id, difficulty, solve_time_ms, suspicious, version

Invisible Verification

Zero-UI verification for AI agents that need to prove themselves programmatically. Handles the full challenge → solve → verify flow with retry and exponential backoff.

TypeScript
import { invisibleVerify } from 'imrobot/core'

const result = await invisibleVerify({
  challengeUrl: 'https://api.example.com/imrobot/challenge',
  verifyUrl: 'https://api.example.com/imrobot/verify',
  agentId: 'my-bot-v1',
  maxRetries: 3,
  timeout: 10_000,
})

if (result.success) {
  console.log('Verified!', result.proofToken)
  console.log(`Solved in ${result.solveTime}ms, ${result.attempts} attempt(s)`)

  // Use the proof token for subsequent API calls
  fetch('/api/protected', {
    headers: { 'X-Agent-Proof': result.proofToken },
  })
} else {
  console.error('Failed:', result.error)
}

Retry strategy: Exponential backoff starting at 100ms (100ms → 200ms → 400ms). The function returns after the first successful verification or after exhausting all retries.

CLI Tool

A built-in command-line interface for testing, benchmarking, and inspecting imrobot challenges. No installation needed beyond the package.

Terminal
# Generate a challenge
npx imrobot challenge --difficulty hard

# Solve a challenge (pipe from generate)
npx imrobot challenge | npx imrobot solve

# Run a performance benchmark
npx imrobot benchmark --iterations 1000 --difficulty medium

# Show library info
npx imrobot info
CommandDescription
challengeGenerate a new challenge (JSON output)
solveSolve a challenge from stdin
verifyVerify a challenge + answer pair
benchmarkRun performance benchmarks with stats (avg, min, max, p99)
infoShow version, features, and operation counts

Agent Discovery (.well-known/imrobot.json)

Inspired by the A2A Agent Card pattern, imrobot supports a discovery endpoint that lets AI agents automatically find and interact with your imrobot-protected service. Agents fetch /.well-known/imrobot.json and receive a structured document describing the protocol, endpoints, and verification instructions.

Express setup

TypeScript
import { createDiscoveryHandler } from 'imrobot/server'

const discovery = createDiscoveryHandler({
  challengePath: '/imrobot',
  name: 'My Agent API',
  description: 'Agent-verified data service',
})

app.get('/.well-known/imrobot.json', discovery)

Framework-agnostic

For Hono, Koa, Fastify, or any other framework, use buildDiscoveryDocument() directly and serve the result as JSON:

TypeScript
import { buildDiscoveryDocument } from 'imrobot/server'

const doc = buildDiscoveryDocument({
  challengePath: '/imrobot',
  name: 'My Agent API',
})
// Serve `doc` as JSON at /.well-known/imrobot.json

Discovery document

The response includes everything an agent needs to complete verification:

JSON
{
  "protocol": "imrobot",
  "version": "1.0",
  "name": "My Agent API",
  "description": "Agent-verified data service",
  "endpoints": {
    "challenge": "/imrobot/challenge",
    "verify": "/imrobot/verify",
    "proofHeader": "X-Agent-Proof"
  },
  "difficulties": ["easy", "medium", "hard"],
  "instructions": "1. GET the challenge endpoint..."
}

Configuration options

OptionTypeDefaultDescription
challengePathstring'/imrobot'Base path for challenge/verify endpoints
difficultiesDifficulty[]['easy','medium','hard']Supported difficulty levels
namestringService name
descriptionstringAuto-generatedHuman-readable service description
contactstringContact URL or email for the operator
metadataRecord<string, unknown>Additional metadata to include

API Reference

generateChallenge(config?)

generateChallenge(config?: Partial<ImRobotConfig>): Challenge

Generates a new challenge with a random seed and operation pipeline.

solveChallenge(challenge)

solveChallenge(challenge: Challenge): string

Executes the pipeline and returns the correct answer. This is what AI agents call.

verifyAnswer(challenge, answer)

verifyAnswer(challenge: Challenge, answer: string): boolean

Client-side verification using FNV-1a hash comparison. Checks TTL expiration.

createToken(challenge, answer, startTime)

createToken(challenge: Challenge, answer: string, startTime: number): ImRobotToken

Creates a verification token with elapsed time and suspicious flag.

createVerifier(config)

createVerifier(config: ServerConfig): ImRobotVerifier

Factory function to create a server-side verifier with HMAC-SHA256 signing.

invisibleVerify(options)

invisibleVerify(options: InvisibleVerifyOptions): Promise<InvisibleVerifyResult>

Zero-UI verification flow with automatic retry and exponential backoff. Returns proof token on success.

createTokenIssuer(config)

createTokenIssuer(config: ProofTokenConfig): ProofTokenIssuer

Creates a JWT-like Proof-of-Agent token issuer with HMAC-SHA256 signing.

requireAgent(options)

requireAgent(options: RequireAgentOptions): MiddlewareFunction

Framework-agnostic middleware that verifies X-Agent-Proof header tokens. Includes rate limiting.

createAgentRouter(options)

createAgentRouter(options: RequireAgentOptions): { challenge, verify }

Returns handler pair for mounting challenge/verify endpoints. Supports optional rateLimit config to protect both endpoints from brute-force attacks.

RateLimiter

new RateLimiter(config?: RateLimiterConfig)

In-memory sliding window rate limiter. Zero external dependencies, automatic cleanup of expired entries. Can be used standalone or via the rateLimit option on middleware.

TypeScript
import { RateLimiter } from 'imrobot/server'

const limiter = new RateLimiter({
  windowMs: 60_000,    // 1-minute sliding window
  maxRequests: 30,     // max 30 requests per window
  onLimitReached: (key) => console.warn(`Rate limited: ${key}`),
})

// Check if a request is allowed
if (!limiter.isAllowed(clientIp)) {
  // 429 Too Many Requests
}

// Inspect current status
const status = limiter.getStatus(clientIp)
// { remaining: 7, resetAt: 1711540860000 }

// Reset a specific key or all keys
limiter.reset(clientIp)
limiter.reset() // clear all

// Clean shutdown
limiter.destroy()
⚙️

RateLimiterConfig options: windowMs (default 60000) — sliding window in ms. maxRequests (default 30) — max requests per window per key. onLimitReached — optional callback when a client exceeds the limit. Expired entries are auto-purged at regular intervals.

formatOperationNL(op)

formatOperationNL(op: Operation): string

Returns a random natural-language description of a single operation. Each call picks from 3–4 distinct phrasings, making regex-based scraping of display text unreliable. Import from imrobot/core.

formatPipelineNL(seed, pipeline)

formatPipelineNL(seed: string, pipeline: Operation[]): string

Formats an entire challenge pipeline using randomised natural-language descriptions. Each call produces different phrasing for both the intro line and every step. Agents must parse the JSON pipeline to solve challenges — scraping the display text is unreliable. Import from imrobot/core.

import { generateChallenge } from 'imrobot/core'
import { formatPipelineNL } from 'imrobot/core'

const challenge = generateChallenge({ difficulty: 'hard' })
console.log(formatPipelineNL(challenge.visibleSeed, challenge.pipeline))
// "Begin with the text: "a7f3..."
//  Step 1: Flip the string backwards
//  Then 2: Shift every letter 7 positions in the alphabet
//  Next 3: Bitwise-XOR every character with the value 42

Tip: The original formatOperation / formatPipeline functions remain unchanged for stable, deterministic output.

Operations Reference

Challenges are built from a pipeline of these 27 operations. Each takes a string input and returns a string output.

OperationParametersDescriptionMin. Difficulty
reverseReverse the stringEasy
to_upperConvert to uppercaseEasy
to_lowerConvert to lowercaseEasy
sort_charsSort characters alphabeticallyEasy
lengthReturn string length as stringEasy
slice_alternateKeep characters at even indicesEasy
vowel_countCount vowels (a, e, i, o, u)Easy
atbashAtbash cipher (a↔z, b↔y, ...)Easy
base64_encodeBase64 encodeMedium
rot13ROT13 cipher (involutory)Medium
hex_encodeHex-encode each characterMedium
char_code_sumSum of all character codesMedium
substringstart, endExtract a substringMedium
caesarshiftCaesar cipher with given shiftMedium
count_charscharCount occurrences of a characterMedium
consonant_extractExtract only consonant lettersMedium
run_length_encodeRun-length encode (e.g., “aaabb” → “3a2b”)Medium
repeattimesRepeat string N timesHard
replacesearch, replacementReplace all occurrencesHard
pad_startlength, fillLeft-pad with fill characterHard
xor_encodekeyXOR each char code with keyHard
fnv1a_hashFNV-1a hash (8-char hex output)Hard
sha256_hashSHA-256 hash (sync FNV-based approximation)Hard
byte_xorkeyXOR each byte with a key arrayHard
hash_chainroundsIterated FNV-1a hash chainHard
nibble_swapSwap high/low nibbles of each byteHard
bit_rotatebitsRotate bits left within each byteHard

Configuration

Client: ImRobotConfig

difficulty'easy' | 'medium' | 'hard'Challenge difficulty. Default: 'medium'
ttlnumberTime-to-live in ms. Default: 30s / 20s / 15s per difficulty
theme'light' | 'dark'Widget theme. Default: 'light'
size'compact' | 'standard'Widget size. 'compact' for smaller footprint (320px). Default: 'standard'
onVerified(token: ImRobotToken) => voidCalled when challenge is solved
onError(error: Error) => voidCalled on error

Server: ServerConfig

secretstringRequired. HMAC secret, min 16 chars. Keep server-side only.
difficultyDifficultyDefault difficulty. Default: 'medium'
ttlnumberDefault TTL in ms. Default: per difficulty

Difficulty levels

LevelPipelineNonceTTLOperations pool
easy2–3 ops4 chars30sBasic transforms (reverse, case, sort, length, slice, vowel_count, atbash)
medium3–5 ops6 chars20s+ encoding, ciphers, substring, counting, consonant_extract, run_length_encode
hard5–7 ops8 chars15s+ XOR, hashing, repetition, padding, SHA-256, byte XOR, hash chains, nibble swap, bit rotate

TypeScript Types

TypeScript
import type {
  Challenge,              // Base challenge object
  SignedChallenge,        // Challenge + HMAC signature
  Operation,              // Discriminated union of 27 operations
  Difficulty,             // 'easy' | 'medium' | 'hard'
  ImRobotToken,           // Client verification token
  ImRobotConfig,          // Client widget configuration
  ServerConfig,           // Server verifier configuration
  VerifyResult,           // Server verification result
  AgentProofToken,        // JWT-like proof-of-agent token payload
  SerializedProofToken,   // Encoded token string
  InvisibleVerifyResult,  // Result from invisible verification
} from 'imrobot'

// Or import from specific entry points:
import type { SignedChallenge, VerifyResult } from 'imrobot/server'
import type { Challenge, Operation } from 'imrobot/core'
import { invisibleVerify, AdaptiveDifficulty, ImageChallengePool } from 'imrobot/core'
import { ProofTokenIssuer, requireAgent, createAgentRouter } from 'imrobot/server'

Adaptive Difficulty

The adaptive difficulty engine auto-adjusts challenge difficulty per agent based on behavioral patterns — inspired by Arkose Labs (FunCaptcha) progressive difficulty and reCAPTCHA v3 risk scoring.

TypeScript
import { AdaptiveDifficulty } from 'imrobot/core'

const adaptive = new AdaptiveDifficulty({
  initialDifficulty: 'medium',
  escalateAfterFailures: 2,   // escalate after 2 consecutive failures
  relaxAfterSuccesses: 5,     // relax after 5 consecutive successes
})

// Record outcomes as agents solve challenges
adaptive.recordAttempt('agent_123', { success: true, solveTimeMs: 42 })

// Get recommended difficulty
const diff = adaptive.getDifficulty('agent_123')

// Get risk assessment (0-1 score with breakdown)
const risk = adaptive.getRiskAssessment('agent_123')
// { score: 0.15, level: 'low', factors: { failureRate, abnormalTiming, rapidAttempts, inconsistentTiming } }

Risk scoring

The risk score is a weighted composite of four factors:

FactorWeightDescription
Failure rate35%Ratio of failed to total attempts
Abnormal timing25%Too fast (<5ms, possible replay) or too slow (>5s, possible human relay)
Rapid attempts25%High request frequency (>2/sec = max risk)
Inconsistent timing15%High coefficient of variation in solve times

Risk levels: low (<0.25) · medium (0.25–0.5) · high (0.5–0.75) · critical (>0.75)

AI Image Challenges EXPERIMENTAL

Foundation for AI-generated image verification challenges. Pre-generate pools of images with known ground truth, then serve them as additional challenge layers.

Recommended approach: Use AI to generate challenge images (scenes with known objects/counts), not to ask users to detect AI. Human accuracy at distinguishing AI images is declining and will only worsen.

Static provider (no API needed)

TypeScript
import { ImageChallengePool } from 'imrobot/core'

const pool = new ImageChallengePool({
  provider: {
    type: 'static',
    images: [
      { imageUrl: '/img/kitchen.png', type: 'object_count',
        question: 'How many red apples?', answer: '3' },
    ],
  },
})

await pool.initialize()
const challenge = pool.getChallenge()
const correct = pool.verifyAnswer(challenge.id, userAnswer)

Custom provider (bring your own generator)

TypeScript
const pool = new ImageChallengePool({
  provider: {
    type: 'custom',
    generate: async (prompt) => {
      const result = await myAIGenerator(prompt)
      return { imageUrl: result.url }
    },
  },
  poolSize: 100,
  challengeTypes: ['object_count', 'spatial_reasoning'],
  rotationIntervalMs: 3_600_000, // rotate pool every hour
})

Challenge types

TypeExample question
object_count“How many red apples are in the image?”
spatial_reasoning“What is to the left of the bench?”
color_identification“What color is the largest object?”
scene_description“What type of place is shown? (one word)”
text_recognition“What word is displayed in the image?”
odd_one_out“Which object does not belong?”

Security Model

Client-only mode

Verification uses FNV-1a hash comparison. Suitable for internal tools and low-stakes scenarios where you trust the client environment.

Server SDK mode RECOMMENDED

Adds multiple layers of protection for production deployments:

ProtectionMechanism
Tamper-proof challengesHMAC-SHA256 signs id + verification + expiresAt + difficulty + pipeline
Replay preventionUnique challenge ID + expiration timestamp
Pipeline integrityVerification hash covers the original answer; modified pipelines produce mismatched hashes
Timing attack resistanceConstant-time HMAC comparison
Stateless designNo database — all state encoded in the signed challenge
Human relay detectionResponses > 5 seconds flagged as suspicious

Best practice: Use the Server SDK for any public-facing deployment. Generate challenges server-side, send to the client agent, and verify when the answer comes back. Never expose your HMAC secret.