🚀 AI Passport Registry - Developer Guide

Welcome to the AI Passport Registry! This comprehensive guide will help you integrate agent verification, policy enforcement, and MCP support into your applications.

📚 Table of Contents

🚀 Quick Start

1. Verify an Agent (No Auth Required)

curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c"

Response:

{
  "agent_id": "ap_a2d10232c6534523812423eec8a1425c",
  "name": "Customer Support Agent",
  "status": "active",
  "capabilities": [{"id": "messaging.send"}],
  "limits": {"requests_per_minute": 100},
  "regions": ["US", "CA"],
  "mcp": {
    "servers": ["https://mcp.stripe.com"],
    "tools": ["stripe.refunds.create"]
  },
  "evaluation": {
    "pack_id": "finance.payment.refund.v1",
    "assurance_ok": true,
    "capability_ok": true,
    "limits_ok": true,
    "regions_ok": true,
    "mcp_ok": true,
    "reasons": []
  },
  "verified_at": "2025-01-15T10:30:00Z"
}

2. Create Your First Agent

curl -X POST "https://aport.io/api/issue" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My AI Assistant",
    "role": "Customer Support",
    "description": "AI assistant for customer support queries",
    "contact": "[email protected]",
    "regions": ["US"],
    "controller_type": "person",
    "status": "active",
    "capabilities": [
      {"id": "messaging.send", "params": {"max_length": 1000}}
    ],
    "limits": {
      "requests_per_minute": 60,
      "daily_requests": 10000
    },
    "mcp": {
      "servers": ["https://mcp.stripe.com"],
      "tools": ["stripe.refunds.create"]
    }
  }'

3. Get Started with Middleware

Express.js:

const { agentPassportMiddleware } = require('@agent-passport/middleware-express');

app.use(agentPassportMiddleware());

app.get('/api/data', (req, res) => {
  // req.agent contains verified passport data
  res.json({ 
    message: `Hello from agent ${req.agent.name}`,
    capabilities: req.agent.capabilities 
  });
});

FastAPI:

from aporthq_middleware_fastapi import AgentPassportMiddleware

app.add_middleware(AgentPassportMiddleware)

@app.get("/api/data")
async def get_data(request: Request):
    agent = request.state.agent
    return {"message": f"Hello from agent {agent.name}"}

🔐 Authentication

The API supports multiple authentication methods:

1. JWT Tokens (User Sessions)

curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

2. API Keys (Programmatic Access)

curl -H "Authorization: Bearer apk_GYqsBZxPRWXjf6n35PocCTcg9EdO-nvxAcYEoDem4Dc"

3. No Authentication (Verification Only)

curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c"

🧠 Core Concepts

Agent Passport Structure

interface PassportData {
  // Core Identity
  agent_id: string;           // "ap_a2d10232c6534523812423eec8a1425c456"
  name: string;              // "Customer Support Agent"
  owner_id: string;          // "ap_user_xxx" or "ap_org_xxx"
  owner_type: "org" | "user";
  controller_type: "org" | "person" | "api";
  
  // Agent Details
  role: string;              // "Customer Support"
  description: string;       // Detailed description
  capabilities: Capability[]; // What the agent can do
  limits: PassportLimits;    // Rate limits and constraints
  regions: string[];         // ["US", "CA"] - ISO-3166 codes
  
  // Status & Verification
  status: "draft" | "active" | "suspended" | "revoked";
  verification_status: "unverified" | "email_verified" | "github_verified";
  assurance_level: "L0" | "L1" | "L2" | "L3" | "L4KYC" | "L4FIN";
  
  // MCP Support (NEW!)
  mcp?: {
    servers?: string[];      // ["https://mcp.stripe.com"]
    tools?: string[];        // ["stripe.refunds.create"]
  };
  
  // Policy Evaluation (NEW!)
  evaluation?: {
    pack_id: string;         // "finance.payment.refund.v1"
    assurance_ok: boolean;   // Meets assurance requirements
    capability_ok: boolean;  // Has required capabilities
    limits_ok: boolean;      // Limits are within policy bounds
    regions_ok: boolean;     // Operates in allowed regions
    mcp_ok: boolean;         // MCP allowlist compliant
    reasons: string[];       // Failure reasons if any
  };
  
  // Registry Signature (for active agents)
  registry_sig?: string;     // Cryptographic signature
  verified_at?: string;      // When signature was created
}

Capabilities System

Capabilities define what an agent can do:

{
  "capabilities": [
    {
      "id": "messaging.send",
      "params": {
        "max_length": 1000,
        "channels": ["email", "slack"]
      }
    },
    {
      "id": "data.export",
      "params": {
        "formats": ["csv", "json"],
        "max_rows": 10000
      }
    }
  ]
}

Limits System

Limits enforce rate limiting and constraints:

{
  "limits": {
    "requests_per_minute": 100,
    "daily_requests": 10000,
    "refund_amount_max_per_tx": 5000,
    "refund_amount_daily_cap": 50000,
    "max_export_rows": 100000,
    "allow_pii": false
  }
}

📖 API Reference

Core Endpoints

🔍 Verify Agent

GET /api/verify/{agent_id}

Public endpoint - No authentication required. Returns complete passport data including MCP configuration and policy evaluation.

🎫 Issue Passport (Self-Serve)

POST /api/issue
Authorization: Bearer {jwt_token_or_api_key}
Content-Type: application/json

Create a passport for your authenticated user's agent.

👤 Get Current User

GET /api/auth/me
Authorization: Bearer {jwt_token_or_api_key}

Returns information about the currently authenticated user.

📊 System Status

GET /api/status

Public endpoint - Returns system health and SLA metrics.

Admin Endpoints

🏗️ Create Agent (Admin)

POST /api/admin/create
Authorization: Bearer {admin_token}

Create agents with full control over all fields.

✏️ Update Agent (Admin)

PUT /api/admin/update
Authorization: Bearer {admin_token}

Update any agent field including status.

📋 List Agents (Admin)

GET /api/admin/agents
Authorization: Bearer {admin_token}

List all agents with filtering options.

⏸️ Suspend Agent (Admin)

POST /api/admin/status
Authorization: Bearer {admin_token}

Change agent status (suspend/activate/revoke).

API Key Management

🔑 Create API Key

POST /api/keys
Authorization: Bearer {jwt_token}
Content-Type: application/json

{
  "owner_id": "ap_user_123",
  "owner_type": "user",
  "scopes": ["read", "issue"],
  "name": "My API Key"
}

📝 List API Keys

GET /api/keys
Authorization: Bearer {jwt_token_or_api_key}

🗑️ Revoke API Key

DELETE /api/keys/{key_id}
Authorization: Bearer {jwt_token_or_api_key}

Organization Management

🏢 List Organizations

GET /api/orgs
Authorization: Bearer {jwt_token}

🔧 Update Organization

PUT /api/org/update
Authorization: Bearer {jwt_token}

⏸️ Suspend Organization Agents

POST /api/orgs/{org_id}/suspend
Authorization: Bearer {jwt_token_or_api_key}

User Self-Management

⏸️ Suspend Own Agent

POST /api/owners/{owner_id}/suspend
Authorization: Bearer {jwt_token_or_api_key}

Taxonomy & Metadata

🏷️ Get Taxonomy

GET /api/taxonomy

Public endpoint - Returns available categories and frameworks.

🔧 Middleware Integration

Express.js Middleware

Basic Setup

const { 
  agentPassportMiddleware,
  mcpEnforcementMiddleware 
} = require('@agent-passport/middleware-express');

// Basic agent verification
app.use(agentPassportMiddleware({
  baseUrl: 'https://aport.io',
  failClosed: true
}));

// Add MCP enforcement
app.use(mcpEnforcementMiddleware());

app.get('/api/protected', (req, res) => {
  // req.agent contains verified passport
  // req.mcp contains MCP headers
  res.json({ 
    agent: req.agent.name,
    mcp_server: req.mcp?.server 
  });
});

Policy-Specific Enforcement

const { createMCPAwarePolicyMiddleware } = require('@agent-passport/middleware-express');

// Apply finance.payment.refund.v1 policy with MCP checks
app.use('/api/refunds/*', createMCPAwarePolicyMiddleware('finance.payment.refund.v1'));

app.post('/api/refunds/create', (req, res) => {
  // This endpoint is protected by:
  // 1. Agent passport verification
  // 2. MCP allowlist checks (if headers present)
  // 3. finance.payment.refund.v1 policy requirements
  
  const { amount, customer_id } = req.body;
  res.json({ 
    success: true,
    refund_id: 'rf_123',
    processed_via_mcp: !!(req.mcp?.server || req.mcp?.tool)
  });
});

FastAPI Middleware

Basic Setup

from aporthq_middleware_fastapi import (
    AgentPassportMiddleware,
    MCPEnforcementMiddleware,
    AgentPassportMiddlewareOptions
)

# Basic agent verification
options = AgentPassportMiddlewareOptions(
    base_url="https://aport.io",
    fail_closed=True
)
app.add_middleware(AgentPassportMiddleware, options=options)

# Add MCP enforcement
app.add_middleware(MCPEnforcementMiddleware)

@app.get("/api/protected")
async def protected_endpoint(request: Request):
    agent = request.state.agent
    mcp = request.state.mcp
    
    return {
        "agent": agent.name,
        "mcp_server": mcp.server if mcp else None
    }

Policy-Specific Enforcement

from aporthq_middleware_fastapi import create_mcp_aware_policy_middleware

# Create policy-aware middleware
refunds_middleware = create_mcp_aware_policy_middleware('finance.payment.refund.v1')

@app.middleware("http")
async def apply_refunds_policy(request: Request, call_next):
    if request.url.path.startswith("/api/refunds/"):
        return await refunds_middleware(request, call_next)
    return await call_next(request)

@app.post("/api/refunds/create")
async def create_refund(request: Request, refund_data: RefundRequest):
    # Protected by finance.payment.refund.v1 policy + MCP checks
    return {"success": True, "refund_id": "rf_123"}

🤖 MCP (Model Context Protocol) Support

MCP support allows you to enforce allowlists for MCP servers and tools that agents can use.

MCP Headers

Your application should include these headers when making MCP-related requests:

X-MCP-Server: https://mcp.stripe.com
X-MCP-Tool: stripe.refunds.create
X-MCP-Session: session_123

MCP Configuration in Passports

{
  "mcp": {
    "servers": [
      "https://mcp.stripe.com",
      "urn:mcp:acme:helpdesk",
      "https://mcp.notion.com"
    ],
    "tools": [
      "stripe.refunds.create",
      "stripe.payments.capture",
      "notion.pages.export",
      "acme.tickets.create"
    ]
  }
}

MCP Validation

The middleware automatically validates MCP headers against passport allowlists:

// Express.js
app.post('/api/process', (req, res) => {
  const mcpHeaders = req.mcp;
  
  if (mcpHeaders.server && !req.agent.mcp?.servers?.includes(mcpHeaders.server)) {
    // This would be caught by middleware, but shown for illustration
    return res.status(403).json({
      error: 'mcp_denied',
      reason: 'server_not_allowlisted',
      server: mcpHeaders.server
    });
  }
  
  res.json({ success: true });
});

MCP Error Responses

When MCP validation fails, you'll receive:

{
  "error": "mcp_denied",
  "reason": "server_not_allowlisted",
  "server": "https://unauthorized-server.com"
}
{
  "error": "mcp_denied",
  "reason": "tool_not_allowlisted", 
  "tool": "unauthorized.tool.action"
}

📋 Policy Packs

Policy packs define compliance requirements for specific use cases.

Available Policy Packs

finance.payment.refund.v1

  • Purpose: Protects refund operations
  • Requirements:
    • Capabilities: finance.payment.refund
    • Assurance: L1 minimum
    • Limits: refund_amount_max_per_tx, refund_amount_daily_cap
    • Regions: US, EU
    • MCP: Enforced if present

data.export.create.v1

  • Purpose: Protects data export operations
  • Requirements:
    • Capabilities: data.export
    • Assurance: L1 minimum
    • Limits: max_export_rows, allow_pii
    • Regions: US, EU, CA
    • MCP: Enforced if present

Policy Evaluation

Every passport includes an evaluation field showing compliance:

{
  "evaluation": {
    "pack_id": "finance.payment.refund.v1",
    "assurance_ok": true,
    "capability_ok": true,
    "limits_ok": true,
    "regions_ok": true,
    "mcp_ok": true,
    "reasons": []
  }
}

If non-compliant:

{
  "evaluation": {
    "pack_id": "finance.payment.refund.v1",
    "assurance_ok": false,
    "capability_ok": false,
    "limits_ok": true,
    "regions_ok": true,
    "mcp_ok": true,
    "reasons": [
      "Missing required capability: refunds",
      "Insufficient assurance level: L0 < L1"
    ]
  }
}

🛠️ SDKs & Examples

JavaScript/Node.js

Python

cURL Examples

✅ Best Practices

1. Always Verify Agents

// ✅ Good - Always verify before processing
const agent = await verifyAgent(agentId);
if (agent.status !== 'active') {
  return res.status(403).json({ error: 'agent_not_active' });
}

// ❌ Bad - Trusting without verification
processRequest(agentId);

2. Use Appropriate Authentication

// ✅ Good - API keys for server-to-server
const apiKey = 'apk_...';
curl -H "Authorization: Bearer ${apiKey}"

// ✅ Good - JWT for user sessions
const userToken = 'eyJ0eXAi...';
curl -H "Authorization: Bearer ${userToken}"

3. Implement Proper Error Handling

// ✅ Good - Handle all error cases
try {
  const agent = await verifyAgent(agentId);
  return processRequest(agent);
} catch (error) {
  if (error.code === 'AGENT_NOT_FOUND') {
    return res.status(404).json({ error: 'agent_not_found' });
  }
  if (error.code === 'AGENT_SUSPENDED') {
    return res.status(403).json({ error: 'agent_suspended' });
  }
  // Handle other errors...
}

4. Cache Verification Results

// ✅ Good - Cache with TTL
const cachedAgent = cache.get(`agent:${agentId}`);
if (cachedAgent && cachedAgent.ttl > Date.now()) {
  return cachedAgent.data;
}

const agent = await verifyAgent(agentId);
cache.set(`agent:${agentId}`, {
  data: agent,
  ttl: Date.now() + (60 * 1000) // 1 minute
});

5. Use Policy-Specific Middleware

// ✅ Good - Apply policies where needed
app.use('/api/refunds/*', createMCPAwarePolicyMiddleware('finance.payment.refund.v1'));
app.use('/api/export/*', createMCPAwarePolicyMiddleware('data.export.create.v1'));

// ❌ Bad - Generic middleware everywhere
app.use('*', genericMiddleware);

6. Monitor MCP Usage

// ✅ Good - Log MCP context for auditing
app.post('/api/process', (req, res) => {
  logger.info('Processing request', {
    agent_id: req.agent.agent_id,
    mcp_server: req.mcp?.server,
    mcp_tool: req.mcp?.tool,
    mcp_session: req.mcp?.session
  });
  
  // Process request...
});

🔧 Troubleshooting

Common Issues

1. "Agent not found" errors

# Check if agent exists
curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c"

# Check agent status
curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c" | jq '.status'

2. Authentication failures

# Test your API key
curl -H "Authorization: Bearer apk_your_key" \
  "https://aport.io/api/auth/me"

# Check API key scopes
curl -H "Authorization: Bearer apk_your_key" \
  "https://aport.io/api/keys" | jq '.[].scopes'

3. MCP validation failures

# Check agent's MCP allowlists
curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c" | jq '.mcp'

# Test with correct headers
curl -X POST "your-app.com/api/endpoint" \
  -H "X-Agent-Passport-Id: ap_a2d10232c6534523812423eec8a1425c" \
  -H "X-MCP-Server: https://mcp.stripe.com" \
  -H "X-MCP-Tool: stripe.refunds.create"

4. Policy compliance issues

# Check policy evaluation
curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c" | jq '.evaluation'

# Review specific failures
curl "https://aport.io/api/verify/ap_a2d10232c6534523812423eec8a1425c" | jq '.evaluation.reasons'

Debug Mode

Enable debug logging in middleware:

// Express.js
app.use(agentPassportMiddleware({
  debug: true,
  logLevel: 'debug'
}));

// FastAPI
import logging
logging.basicConfig(level=logging.DEBUG)

Rate Limits

Limits are counted per client IP address, per minute, and resolved in this
order: the number the endpoint declares, then the tier's environment variable
(VERIFY_RPM, ADMIN_RPM, ORG_RPM), then the tier default.

Tier Production default Where it is set
Verification (/api/verify/:agent_id) 1,000/minute VERIFY_RPM
Policy verification (/api/verify/policy/:pack_id) 200,000/minute POLICY_RATE_LIMIT_PER_MINUTE
Admin operations 100/minute ADMIN_RPM
Organization and API key operations 30/minute ORG_RPM

Exception: /api/verify/:agent_id skips its KV rate-limit check for common
browser user agents and conditional cached requests. That endpoint is optimized
for public passport reads from web pages, so its verification tier is enforced
for non-browser clients such as SDKs, CLIs, bots and server-to-server traffic,
not for every browser-shaped request. Do not treat a browser response's
x-ratelimit-limit value as proof that the request consumed that allowance.

Individual endpoints set their own number where they need a different one, and
that number now wins over the tier variable. Key issuance is the lowest at
10/minute; passport reads and list endpoints run at 120-300/minute. The exact
figure for a counted response is in its x-ratelimit-limit header.

A refusal returns 429 with retry-after in seconds and
{"error": "rate_limit_exceeded"} in the body.

When the refusal is an ordinary one, because your counted requests reached the
limit, retry-after is computed: it is when enough of them actually leave the
trailing window, not the next clock minute, so waiting it out works.

There is a second kind of refusal where it does not. If the counter write fails
on a limit small enough to be enforced strictly, the request is denied with
retry-after: 1, and that 1 is a floor rather than a calculation: the write
usually fails because the key is being written faster than KV sustains, so
retrying a second later can meet the same denial. Back off progressively rather
than tightly retrying a 1-second hint.

One caveat worth knowing, because it is a property of the storage rather than a
setting. Every limit here is approximate. None of them is a hard quota, and
that includes the 30-a-minute organisation tier. The counter is kept in
Cloudflare KV, and the limiter reads the count and then writes it back, so
requests that arrive together can all read the same number and all write back the
same increment. A burst is undercounted at 30 a minute for the same reason it is
undercounted at 30,000. Treat the configured number as the point where sustained
traffic starts being refused, not as a ceiling a burst cannot cross.

KV also sustains only about one write per second per key, so a single counter
records roughly 60 requests a minute. That is what separates the tiers, and it
separates them by what happens when a counter write fails, not by whether the
limit is exact. At or below about 60 a failed write means the caller really is
over the limit, so the request is refused. Above it, from 500 a minute up, the
request is admitted and NOT ENFORCED is logged, because capping verification
and policy traffic at 60 would be an outage rather than enforcement.

A separate path fails open: if the counter cannot be read at all, the request is
admitted with a full allowance. So a KV disruption lifts the limit rather than
refusing traffic.

If you need a number that holds exactly, enforce it on your own side. Exact
enforcement here needs an atomic counter, which is a deployment change.

Support


🎯 What's Next?

  1. Try the examples in the examples/ directory
  2. Integrate middleware for automatic verification
  3. Set up policy enforcement for audit and governance workflows
  4. Configure MCP allowlists for enhanced security
  5. Monitor your agents with webhooks and metrics

Ready to build secure, auditable AI agents? Let's go! 🚀