Skip to main content
AgentDoor provides a complete pre-authentication layer for AI agents, enabling them to register, authenticate, and access your API programmatically in under 500ms—no browser automation required.

Architecture Overview

AgentDoor sits as middleware between your API and incoming agent requests, handling three core responsibilities:
  1. Agent Discovery - Agents find your service and capabilities via /.well-known/agentdoor.json
  2. Registration & Authentication - Challenge-response protocol using Ed25519 signatures
  3. Request Authorization - JWT-based or signature-based auth on every API call

Complete Flow

Here’s the complete agent onboarding and request flow with timing:

Phase 1: Discovery (~50ms)

The agent fetches your service’s capabilities and requirements:
const discovery = await fetch('https://api.example.com/.well-known/agentdoor.json');
const config = await discovery.json();
Response:
{
  "agentdoor_version": "1.0",
  "service_name": "Weather API",
  "service_description": "Real-time weather data and forecasts",
  "registration_endpoint": "/agentdoor/register",
  "auth_endpoint": "/agentdoor/auth",
  "scopes_available": [
    {
      "id": "weather.read",
      "description": "Read current weather data",
      "price": "$0.001/req",
      "rate_limit": "1000/hour"
    }
  ],
  "auth_methods": ["ed25519-challenge", "x402-wallet", "jwt"],
  "payment": {
    "protocol": "x402",
    "version": "2.0",
    "networks": ["base"],
    "currency": ["USDC"]
  }
}

Phase 2: Registration (~100ms)

The agent submits its public key and requests scopes:
import { generateKeypair } from '@agentdoor/core';

const keypair = generateKeypair();

const registerResponse = await fetch('https://api.example.com/agentdoor/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    public_key: keypair.publicKey,  // Base64-encoded Ed25519 public key
    scopes_requested: ['weather.read'],
    x402_wallet: '0x1234...abcd',  // Optional: for payments
    metadata: {
      framework: 'langchain',
      version: '0.1.0'
    }
  })
});

const challenge = await registerResponse.json();
Response:
{
  "agent_id": "ag_V1StGXR8_Z5jdHi6B",
  "challenge": {
    "nonce": "Xy9k3mP7qR2sT5vW8zA1bC4dE6fG9hJ0",
    "message": "agentdoor:register:ag_V1StGXR8_Z5jdHi6B:1709481600:Xy9k3mP7qR2sT5vW8zA1bC4dE6fG9hJ0",
    "expires_at": "2024-03-03T12:10:00.000Z"
  }
}
Server-side: AgentDoor generates a unique agent ID and creates a time-limited challenge. The challenge message format is:
agentdoor:register:{agent_id}:{timestamp}:{nonce}
This challenge is stored temporarily (default: 5 minutes) and associated with the pending registration.

Phase 3: Challenge Response (~200ms)

The agent signs the challenge message with its private key and returns the signature:
import { signChallenge } from '@agentdoor/core';

// Agent signs the challenge with its private key
const signature = signChallenge(
  challenge.challenge.message,
  keypair.secretKey
);

const verifyResponse = await fetch('https://api.example.com/agentdoor/register/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    agent_id: challenge.agent_id,
    signature: signature  // Base64-encoded Ed25519 signature
  })
});

const credentials = await verifyResponse.json();
Response:
{
  "agent_id": "ag_V1StGXR8_Z5jdHi6B",
  "api_key": "agk_live_Xy9k3mP7qR2sT5vW8zA1bC4dE6fG9hJ0kL2mN4oP6qR8",
  "scopes_granted": ["weather.read"],
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_expires_at": "2024-03-03T13:05:00.000Z",
  "rate_limit": {
    "requests": 1000,
    "window": "1h"
  },
  "x402": {
    "payment_address": "0xYourWallet...",
    "network": "base",
    "currency": "USDC"
  }
}
Server-side: AgentDoor:
  1. Retrieves the stored challenge for this agent_id
  2. Verifies the signature using the agent’s public key
  3. Checks that the challenge hasn’t expired
  4. Creates the agent record in storage
  5. Generates an API key (hashed for storage, returned in plaintext once)
  6. Issues a JWT token for immediate use
  7. Returns credentials and rate limits
The api_key is only returned once during registration. Store it securely. The server stores a SHA-256 hash of the key, not the plaintext.

Phase 4: Making Authenticated Requests

Once registered, the agent can make authenticated requests using either:

Option A: JWT Bearer Token (Most Common)

const response = await fetch('https://api.example.com/api/weather/forecast?city=sf', {
  headers: {
    'Authorization': `Bearer ${credentials.token}`
  }
});
Server-side: The AgentDoor middleware:
  1. Extracts the JWT from the Authorization header
  2. Verifies the signature using the shared secret
  3. Checks expiration
  4. Extracts agent context (ID, scopes, metadata)
  5. Attaches to req.agent for your route handlers
  6. Checks rate limits
  7. Proceeds to your API logic

Option B: API Key Authentication

const response = await fetch('https://api.example.com/api/weather/forecast?city=sf', {
  headers: {
    'Authorization': `Bearer ${credentials.api_key}`
  }
});
Server-side: AgentDoor:
  1. Detects the agk_ prefix (API key vs JWT)
  2. Hashes the provided key
  3. Looks up the agent record by key hash
  4. Checks agent status (active/suspended/banned)
  5. Attaches agent context to the request
  6. Checks rate limits

Phase 5: Token Refresh (When JWT Expires)

JWTs are short-lived (default: 1 hour). When a token expires, agents refresh via signature-based auth:
import { buildAuthMessage, signChallenge } from '@agentdoor/core';

const timestamp = new Date().toISOString();
const message = buildAuthMessage(agent_id, timestamp);
const signature = signChallenge(message, keypair.secretKey);

const authResponse = await fetch('https://api.example.com/agentdoor/auth', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    agent_id: agent_id,
    timestamp: timestamp,
    signature: signature
  })
});

const { token, expires_at } = await authResponse.json();
Auth message format:
agentdoor:auth:{agent_id}:{timestamp}
Server-side: AgentDoor:
  1. Retrieves the agent’s stored public key
  2. Verifies the signature
  3. Checks timestamp freshness (default: within 5 minutes)
  4. Issues a fresh JWT
  5. Updates lastAuthAt timestamp
The AgentDoor SDK handles token refresh automatically. Agents never need to manually implement refresh logic.

Total Timing Breakdown

PhaseOperationTimeCumulative
1Discovery~50ms50ms
2Registration~100ms150ms
3Challenge-Response~200ms350ms
4First Request~50ms400ms
Compare this to browser automation:
  • Puppeteer/Playwright: 30-60 seconds
  • Human signup flow: 2-5 minutes

Request Context in Your API

Once AgentDoor middleware is active, every incoming request is enriched with agent context:
app.get('/api/weather/forecast', (req, res) => {
  // Check if request is from an agent
  if (req.isAgent) {
    console.log('Agent request detected');
    
    // Access authenticated agent context
    if (req.agent) {
      console.log('Agent ID:', req.agent.id);
      console.log('Scopes:', req.agent.scopes);
      console.log('Framework:', req.agent.metadata.framework);
      console.log('Reputation:', req.agent.reputation);
    }
  }
  
  res.json({ forecast: 'sunny' });
});
Available context:
interface AgentContext {
  id: string;                    // "ag_V1StGXR8_Z5jdHi6B"
  publicKey: string;              // Base64-encoded Ed25519 public key
  scopes: string[];               // ["weather.read", "weather.write"]
  rateLimit: {                    // Agent-specific rate limit
    requests: number;
    window: string;
  };
  reputation?: number;            // 0-100 score
  metadata: Record<string, string>; // framework, version, etc.
}

Security Model

Private Key Never Leaves Agent

Unlike API keys or OAuth tokens, the agent’s private key never touches the network:
// ✅ SAFE: Signature is sent, not the key
const signature = signChallenge(message, privateKey);
fetch('/agentdoor/register/verify', { 
  body: JSON.stringify({ signature })
});

// ❌ NEVER: Private key stays local
fetch('/agentdoor/register', { 
  body: JSON.stringify({ private_key: privateKey })
});

Time-Limited Challenges

Every challenge expires after 5 minutes (configurable). Replay attacks are prevented:
if (Date.now() > challenge.expiresAt.getTime()) {
  throw new ChallengeExpiredError('Challenge expired. Request a new one.');
}

API Key Hashing

API keys are only shown once at registration. Stored as SHA-256 hashes:
import { hashApiKey } from '@agentdoor/core';

// At registration
const rawKey = generateApiKey('live'); // "agk_live_..."
const hash = hashApiKey(rawKey);       // SHA-256 hex

// Store hash, return raw key to agent once
await storage.createAgent({
  apiKeyHash: hash  // Only hash is stored
});

return { api_key: rawKey };  // Returned once, then discarded

JWT Short-Lived Tokens

JWTs expire quickly (default: 1 hour). Refresh requires signature proof:
// Token refresh requires signing a fresh timestamp
const timestamp = new Date().toISOString();
const message = `agentdoor:auth:${agent_id}:${timestamp}`;
const signature = signChallenge(message, privateKey);

// Server verifies signature before issuing new token
// Stolen JWT alone cannot be refreshed

Storage Layer

AgentDoor supports multiple storage backends:
DriverUse CaseConfiguration
memoryDevelopment, testingNo setup required
sqliteSingle-server production{ driver: 'sqlite', url: 'file:./agentdoor.db' }
postgresMulti-server production{ driver: 'postgres', url: 'postgresql://...' }
redisEdge workers, high-scale{ driver: 'redis', url: 'redis://...' }
Stored data:
  • Agents: ID, public key, x402 wallet, scopes, API key hash, rate limits, reputation, metadata
  • Challenges: Pending registration challenges (temporary, auto-expire)
  • Rate limits: Request counts per agent per time window
  • Sessions: JWT blacklist for revocation (optional)

Next Steps

Authentication

Deep dive into Ed25519 challenge-response and JWT handling

Discovery Protocol

Learn how agents find and connect to your API

Agent Detection

Understand how AgentDoor identifies agent traffic

Payments

Integrate x402 protocol for per-request payments

Build docs developers (and LLMs) love