🚀 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
- API Overview
- Authentication
- Core Concepts
- API Reference
- Middleware Integration
- MCP Support
- Policy Packs
- SDKs & Examples
- Best Practices
- Troubleshooting
🚀 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
- Capabilities:
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
- Capabilities:
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
- Basic Usage:
examples/javascript/basic-usage.js - MCP Enforcement:
examples/javascript/mcp-enforcement.js - Express Middleware:
middleware/express/
Python
- Basic Usage:
examples/python/basic_usage.py - MCP Enforcement:
examples/python/mcp_enforcement.py - FastAPI Middleware:
middleware/fastapi/
cURL Examples
- Basic Operations:
examples/curl/basic-usage.sh
✅ 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'sx-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 withretry-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
- Documentation: https://aport.io/docs
- API Reference: https://aport.io/api/openapi.json
- Examples: https://github.com/agent-passport/examples
- Issues: https://github.com/agent-passport/registry/issues
🎯 What's Next?
- Try the examples in the
examples/directory - Integrate middleware for automatic verification
- Set up policy enforcement for audit and governance workflows
- Configure MCP allowlists for enhanced security
- Monitor your agents with webhooks and metrics
Ready to build secure, auditable AI agents? Let's go! 🚀