Back to BlogIntegration

Platform Integration Guide: Creating and Managing User Passports

Complete guide for platforms integrating with APort to create and manage passports for their users and AI agents. Includes API key setup, passport creation, assurance levels, and limit management with code examples.

19 min read
by APort Team

TL;DR

  • Get API Key: Log in to APort Dashboard and navigate to Settings > API Keys (/settings/?tab=api-keys)
  • Generate an Org Key (optional): POST /api/orgs/{id}/orgkey so your backend can call org-scoped endpoints with Authorization: Bearer + x-org-key-id
  • Create User Passport:
    • Option A: POST /api/passports/create (requires valid APort user/org ID as owner_id)
    • Option B: POST /api/orgs/{id}/issue (delegated issuance for platform users without APort accounts; supports pending_owner and assurance field)


Overview

This guide walks through the complete integration flow for platforms that want to:

  1. Sign up for APort and get an API key
  2. Create passports for their users (users don't need to log in to APort)
  3. Set trust/assurance levels from the platform using assurance providers
  4. Create AI agent passports owned by platform users
  5. Update passport limits dynamically
Use Case: Your platform has users who create AI agents. You want to manage their passports programmatically without requiring users to have APort accounts.


Step 1: Get Your API Key

  1. Sign up or log in to APort Dashboard
  2. Navigate to Settings > API Keys (/settings/?tab=api-keys)
  3. Click "Create API Key"
  4. Select the required scopes: ["issue", "update", "read", "list_agents"]
  5. Save the API key immediately—it's only shown once!

Via API (Alternative)

If you prefer to create API keys programmatically, you can use the API endpoint: Endpoint: POST /api/keys

💡 Try it live: Test this endpoint in our Interactive API Documentation
Request:

{
  "owner_id": "ap_org_your_platform_id",
  "owner_type": "org",
  "scopes": ["issue", "update", "read", "list_agents"],
  "name": "Platform Production Key"
}

Required Scopes:

  • issue: Create new passports
  • update: Update existing passports
  • read: Read passport data
  • list_agents: List passports for an owner
Response:

{
  "success": true,
  "data": {
    "key_id": "apk_abc123def456",
    "key": "apk_xyz789uvw012...",  // ⚠️ Show only once!
    "owner_id": "ap_org_your_platform_id",
    "owner_type": "org",
    "scopes": ["issue", "update", "read", "list_agents"],
    "created_at": "2025-01-15T10:30:00Z",
    "name": "Platform Production Key"
  }
}

⚠️ Important: Save the key value immediately—it's only shown once! Code Example (Python 3):

import requests

def create_api_key(api_base_url: str, auth_token: str, org_id: str):
    """Create an API key for your organization."""
    response = requests.post(
        f"{api_base_url}/api/keys",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {auth_token}"  # Your user JWT token
        },
        json={
            "owner_id": org_id,
            "owner_type": "org",
            "scopes": ["issue", "update", "read", "list_agents"],
            "name": "Platform Production Key"
        }
    )
    
    response.raise_for_status()
    result = response.json()
    
    # Store the key securely!
    api_key = result["data"]["key"]
    print(f"API Key created: {result['data']['key_id']}")
    print(f"⚠️ Save this key now: {api_key}")
    
    return result["data"]
💡 More Examples: For additional SDK examples in Node.js, Python, and other languages, see the APort SDKs and Middlewares repository.

Step 1B: Generate an Org Key (for org-scoped automation)

API keys authenticate you as an admin, but org-only endpoints (/api/org/update, /api/org/status, etc.) expect an org key. An org key is an HMAC credential scoped to a single org and is presented via two headers:

Authorization: Bearer <org-key-secret>
x-org-key-id: orgkey_abc123

Endpoint: POST /api/orgs/{id}/orgkey

💡 Try it live: Use our Swagger UI, authorize with your JWT/admin token, and generate a key. The secret is displayed only once.
Response:

{
  "ok": true,
  "data": {
    "org_key_id": "orgkey_s3cure123",
    "secret": "tls_s3cret_that_you_store_securely",
    "org_id": "ap_org_your_platform_id"
  }
}

Code Example (Python 3):

import requests

def create_org_key(api_base_url: str, jwt: str, org_id: str):
    """Create an org key for org-scoped endpoints."""
    response = requests.post(
        f"{api_base_url}/api/orgs/{org_id}/orgkey",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {jwt}"  # logged-in org admin or API key with manage_keys scope
        }
    )
    
    response.raise_for_status()
    result = response.json()
    
    # Store both values securely:
    # - org_key_id (non-secret identifier)
    # - secret (used as Bearer token)
    return result["data"]

You will use this pair of headers whenever you hit org-key-protected endpoints later in this guide.


Step 1C: Configure Assurance Providers (Required for KYC/KYB)

Before you can issue passports with assurance levels, your organization must configure assurance providers. This allows your org to act as a KYC/KYB provider. Endpoint: PATCH /api/admin/organizations/{id}/assurance-provider Requirements:

  • You must be an organization admin
  • Your organization must have can_issue_for_others permission enabled
Request:

{
  "assuranceProvider": [
    {
      "type": "kyb",
      "assurance_level": "L4KYC",
      "proof": {
        "standard": "standard_proof_format"
      }
    },
    {
      "type": "kyb",
      "assurance_level": "L4FIN",
      "proof": {
        "standard": "financial_proof_format"
      }
    }
  ]
}

Code Example (Python 3):

import requests

def configure_assurance_providers(api_base_url: str, admin_token: str, org_id: str):
    """Configure assurance providers for your organization."""
    response = requests.patch(
        f"{api_base_url}/api/admin/organizations/{org_id}/assurance-provider",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {admin_token}"
        },
        json={
            "assuranceProvider": [
                {
                    "type": "kyb",
                    "assurance_level": "L4KYC",
                    "proof": {
                        "standard": "standard_proof_format"
                    }
                }
            ]
        }
    )
    
    response.raise_for_status()
    return response.json()

Step 2: Create a Passport for Your Platform User

Option A: Using Standard Passport Creation

Endpoint: POST /api/passports/create

💡 Try it live: Test this endpoint in our Interactive API Documentation
⚠️ Important Limitation: The owner_id field must be a valid APort user or organization ID (e.g., ap_user_abc123 or ap_org_xyz789). It cannot be your platform's internal user identifier. For Platform Integration: If you need to create passports for users who don't have APort accounts, see Option B: Using Organization Issuance below. Request:

{
  "name": "John Doe's Passport",
  "role": "agent",
  "description": "Primary passport for user John Doe on our platform",
  "owner_id": "ap_user_john_doe",
  "controller_type": "person",
  "contact": "john.doe@example.com",
  "capabilities": [
    {
      "id": "data.export",
      "params": {
        "max_rows": 10000
      }
    },
    {
      "id": "messaging.send"
    }
  ],
  "limits": {
    "max_export_rows": 10000,
    "msgs_per_day": 1000,
    "allow_pii": false
  },
  "regions": ["US", "EU"],
  "status": "active"
}

Code Example (Python 3):

import requests

def create_user_passport(
    api_base_url: str,
    api_key: str,
    aport_user_id: str,  # Must be a valid APort user ID (ap_user_xxx)
    user_email: str
):
    """Create a passport for a user with a valid APort account."""
    response = requests.post(
        f"{api_base_url}/api/passports/create",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        json={
            "name": f"{aport_user_id}'s Passport",
            "role": "agent",
            "description": f"Primary passport for user {aport_user_id}",
            "owner_id": aport_user_id,  # Must be a valid APort user/org ID
            "controller_type": "person",
            "contact": user_email,
            "capabilities": [
                {
                    "id": "data.export",
                    "params": {"max_rows": 10000}
                },
                {
                    "id": "messaging.send"
                }
            ],
            "limits": {
                "max_export_rows": 10000,
                "msgs_per_day": 1000,
                "allow_pii": False
            },
            "regions": ["US", "EU"],
            "status": "active"
        }
    )
    
    response.raise_for_status()
    result = response.json()
    print(f"Passport created: {result['data']['agent_id']}")
    return result["data"]

If your platform users don't have APort accounts, use the Organization Issuance endpoint. This allows your organization to issue passports on behalf of users with KYC/KYB assurance levels. Endpoint: POST /api/orgs/{id}/issue

💡 Try it live: Test this endpoint in our Interactive API Documentation
Requirements:
  • Your organization must have can_issue_for_others permission enabled
  • Your organization must have configured assurance providers (see Step 1C)
  • You must be an organization admin
Key Features:
  • Creates passports with your org as issued_by while automatically provisioning an APort user when you include pending_owner.email (we deterministically map the email to ap_user_xxx)
  • Set assurance level via assurance field (type, assurance_level, proof) - no direct assurance_level setting
  • Passport can be claimed via email link
  • Claim email is sent automatically if pending_owner.email is provided
Request Example:

{
  "name": "Platform User's Agent",
  "role": "agent",
  "description": "AI agent for platform user",
  "regions": ["US", "EU"],
  "contact": "user@example.com",
  "pending_owner": {
    "email": "user@example.com",
    "display_name": "Jane Platform"
  },
  "assurance": {
    "type": "kyb",
    "assurance_level": "L4KYC",
    "proof": {
      "verification_id": "ver_123",
      "verified_at": "2025-01-15T10:00:00Z"
    }
  },
  "capabilities": [
    {
      "id": "data.export",
      "params": { "max_rows": 10000 }
    }
  ],
  "limits": {
    "max_export_rows": 10000,
    "msgs_per_day": 1000
  },
  "status": "active"
}

Important Notes:

  • The assurance field must match one of your organization's configured assurance providers
  • The assurance level is set from the assurance field, not directly
  • The passport will have claimed: false until the user claims it via email
  • The user account is automatically created when the passport is claimed
Code Example (Python 3):

import requests

def create_passport_via_org_issue(
    api_base_url: str,
    api_key: str,
    org_id: str,  # Your APort organization ID (ap_org_xxx)
    user_email: str,
    user_display_name: str,
    assurance_type: str = "kyb",
    assurance_level: str = "L4KYC",
    verification_proof: dict = None
):
    """Create a passport for a platform user via org issuance with assurance level."""
    response = requests.post(
        f"{api_base_url}/api/orgs/{org_id}/issue",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        json={
            "name": f"{user_display_name}'s Passport",
            "role": "agent",
            "description": f"AI agent for platform user {user_email}",
            "regions": ["US", "EU"],
            "contact": user_email,
            "pending_owner": {
                "email": user_email,
                "display_name": user_display_name
            },
            "assurance": {
                "type": assurance_type,  # e.g., "kyb", "kyc", "financial"
                "assurance_level": assurance_level,  # e.g., "L4KYC", "L4FIN"
                "proof": verification_proof or {
                    "verification_id": f"ver_{user_email}",
                    "verified_at": "2025-01-15T10:00:00Z"
                }
            },
            "capabilities": [
                {
                    "id": "data.export",
                    "params": {"max_rows": 10000}
                }
            ],
            "limits": {
                "max_export_rows": 10000,
                "msgs_per_day": 1000
            },
            "status": "active"
        }
    )
    
    response.raise_for_status()
    result = response.json()
    print(f"Passport issued: {result['agent_id']}")
    print(f"Claimed: {result['claimed']}")  # Should be false until user claims
    print(f"Assurance Level: {result.get('assurance_level', 'N/A')}")
    return result

When to Use:

  • ✅ Your platform users don't have APort accounts and you want us to auto-provision them based on email
  • ✅ You want your org to be the issuer/creator
  • ✅ You want to set KYC/KYB assurance levels via the assurance field
  • ✅ You want users to claim their passports via email link
Claim Flow:
  1. Passport is created with claimed: false and assurance_level from the assurance field
  2. User receives claim email automatically
  3. User clicks link and claims passport
  4. Passport's assurance_level is preserved (not overwritten)
  5. User account is updated with the assurance level


Step 3: Create AI Agent Passports

When a user on your platform creates an AI agent, you have two options:

Why instances are recommended:

  • Inherits assurance level from the template automatically (user's KYC/KYB level)
  • Most fields can be customized on instance creation (limits, regions, status, contact, links)
  • Consistent configuration across all agent passports
  • Easier management - update the template to affect all instances
How it works:
  1. First, create a template (default) passport for the User (one-time setup we've done above)
  2. When a user creates an AI agent, create an instance from that template
  3. Customize the instance with user-specific overrides (limits, regions, etc.)
  4. The instance automatically inherits the template's assurance level, capabilities, and other settings
Endpoint: POST /api/passports/{template_id}/instances

💡 Try it live: Test this endpoint in our Interactive API Documentation
Note: The assurance_level can be set in agent_data.assurance_level to override the template's assurance level. If not provided, it inherits from the template (user's original passport). Request:

{
  "platform_id": "your_platform",
  "tenant_ref": "user_12345",
  "controller_id": "user_12345",
  "controller_type": "user",
  "overrides": {
    "limits": {
      "msgs_per_day": 5000,
      "max_export_rows": 10000
    },
    "regions": ["US", "EU"],
    "status": "active",
    "contact": "user@example.com"
  },
  "agent_data": {
    "name": "Customer Support Bot",
    "description": "AI agent for handling customer support inquiries"
  }
}

Code Example (Python 3):

import requests

def create_agent_instance(
    api_base_url: str,
    api_key: str,
    template_id: str,
    platform_user_id: str,
    agent_name: str,
    agent_description: str,
    custom_limits: dict = None
):
    """Create an agent passport instance from user's template passport.
    
    The instance automatically inherits the assurance level from the template
    (user's original passport with KYC/KYB level).
    """
    response = requests.post(
        f"{api_base_url}/api/passports/{template_id}/instances",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        json={
            "platform_id": "your_platform",
            "tenant_ref": platform_user_id,
            "controller_id": platform_user_id,
            "controller_type": "user",
            "overrides": {
                "limits": custom_limits or {
                    "msgs_per_day": 5000,
                    "max_export_rows": 10000
                },
                "status": "active"
            },
            "agent_data": {
                "name": agent_name,
                "description": agent_description
                # assurance_level will be inherited from template automatically
            }
        }
    )
    
    response.raise_for_status()
    result = response.json()
    print(f"Agent instance created: {result['data']['instance_id']}")
    print(f"Inherited assurance level: {result['data'].get('assurance_level', 'N/A')}")
    return result["data"]

Customizable Fields in overrides:

  • limits: Override any limit values
  • regions: Override allowed regions
  • status: Set instance status (draft, active, suspended, revoked)
  • contact: Override contact information
  • links: Override homepage, docs, repo links
Customizable Fields in agent_data:
  • name: Instance-specific name
  • description: Instance-specific description
  • capabilities: Override capabilities (as array of strings)
  • limits: Alternative way to set limits
  • regions: Alternative way to set regions
  • assurance_level: Override assurance level (inherits from template if not provided)

Option B: Create a New Passport

If you prefer full control or don't have a template, you can create a new passport directly. Endpoint: POST /api/passports/create Code Example (Python 3):

import requests

def create_agent_passport(
    api_base_url: str,
    api_key: str,
    platform_user_id: str,
    agent_name: str,
    agent_description: str
):
    """Create a new agent passport directly (not recommended - use instances instead)."""
    response = requests.post(
        f"{api_base_url}/api/passports/create",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        json={
            "name": agent_name,
            "role": "agent",
            "description": agent_description,
            "owner_id": platform_user_id,
            "controller_type": "person",
            "contact": "support@yourplatform.com",
            "capabilities": [
                {
                    "id": "messaging.send",
                    "params": {"max_per_day": 5000}
                }
            ],
            "limits": {
                "msgs_per_day": 5000,
                "max_export_rows": 5000
            },
            "regions": ["US"],
            "status": "active"
            # Note: assurance_level would need to be set from owner's level
        }
    )
    
    response.raise_for_status()
    return response.json()["data"]

Recommendation: Use Option A (instances) for better consistency and automatic assurance level inheritance. Use Option B (new passport) only if you need completely independent configurations per agent.


Step 4: Update Passports

Endpoint: PUT /api/passports/{agent_id}

💡 Try it live: Test this endpoint in our Interactive API Documentation
You can update various fields of both the main user passport and agent passports using the same endpoint. The update endpoint supports partial updates—you only need to include the fields you want to change. Updatable Fields:
  • limits: Update operational limits (max_export_rows, msgs_per_day, refund amounts, etc.)
  • capabilities: Add, remove, or modify capabilities
  • regions: Update allowed geographic regions
  • status: Change passport status (draft, active, suspended, revoked)
  • name: Update passport name
  • description: Update passport description
  • contact: Update contact information
  • links: Update homepage, docs, repo links
  • framework: Update AI framework classification
  • categories: Update category classifications
  • logo_url: Update logo URL
Request Examples:

// Update limits only
{
  "limits": {
    "max_export_rows": 20000,
    "msgs_per_day": 2000,
    "refund_amount_max_per_tx": 10000,
    "refund_amount_daily_cap": 100000
  }
}

// Update capabilities
{
  "capabilities": [
    {
      "id": "data.export",
      "params": { "max_rows": 50000 }
    },
    {
      "id": "messaging.send",
      "params": { "max_per_day": 10000 }
    },
    {
      "id": "finance.payment.refund"
    }
  ]
}

// Update multiple fields
{
  "limits": {
    "msgs_per_day": 5000,
    "max_export_rows": 20000
  },
  "capabilities": [
    {
      "id": "messaging.send"
    }
  ],
  "regions": ["US", "EU", "CA"],
  "status": "active"
}

Code Example (Python 3):

import requests

def update_passport(
    api_base_url: str,
    api_key: str,
    agent_id: str,
    updates: dict
):
    """Update passport fields (partial updates supported)."""
    response = requests.put(
        f"{api_base_url}/api/passports/{agent_id}",
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        json=updates
    )
    
    response.raise_for_status()
    result = response.json()
    print(f"Passport updated: {result['data']['agent_id']}")
    if result.get("changes"):
        print(f"Changes: {result['changes']}")
    return result["data"]

# Example 1: Update limits only
update_passport(api_base_url, api_key, user_passport_id, {
    "limits": {
        "max_export_rows": 20000,
        "msgs_per_day": 2000
    }
})

# Example 2: Update capabilities
update_passport(api_base_url, api_key, agent_passport_id, {
    "capabilities": [
        {
            "id": "data.export",
            "params": {"max_rows": 50000}
        },
        {
            "id": "messaging.send",
            "params": {"max_per_day": 10000}
        }
    ]
})

# Example 3: Update multiple fields
update_passport(api_base_url, api_key, agent_passport_id, {
    "limits": {
        "msgs_per_day": 5000,
        "max_export_rows": 20000
    },
    "capabilities": [
        {"id": "messaging.send"},
        {"id": "finance.payment.refund"}
    ],
    "regions": ["US", "EU", "CA"],
    "status": "active"
})

# Example 4: Suspend a passport
update_passport(api_base_url, api_key, agent_passport_id, {
    "status": "suspended"
})

Step 5: Automate Org-Level Actions with Org Keys

With the org key created in Step 1B, you can invoke sensitive org endpoints without a user session. These endpoints require both headers:

Authorization: Bearer <org-key-secret>
x-org-key-id: orgkey_abc123

Suspend or Resume an Agent Passport

Endpoint: POST /api/org/update Code Example (Python 3):

import requests

def suspend_agent_passport(
    api_base_url: str,
    org_key_secret: str,
    org_key_id: str,
    agent_id: str
):
    """Suspend an agent passport using org key."""
    response = requests.post(
        f"{api_base_url}/api/org/update",
        headers={
            "Authorization": f"Bearer {org_key_secret}",
            "x-org-key-id": org_key_id,
            "Content-Type": "application/json"
        },
        json={
            "agent_id": agent_id,
            "status": "suspended",
            "webhook_url": "https://hooks.example.com/aport",
            "email": "contact@example.com"
        }
    )
    
    response.raise_for_status()
    return response.json()

  • Only allows status transitions between active and suspended
  • Updates contact/email/webhook metadata
  • Enforces that the agent belongs to the org linked to the key

Toggle Your Org's Status

Endpoint: POST /api/org/status Code Example (Python 3):

import requests

def toggle_org_status(
    api_base_url: str,
    org_key_secret: str,
    org_key_id: str,
    status: str
):
    """Toggle organization status using org key."""
    response = requests.post(
        f"{api_base_url}/api/org/status",
        headers={
            "Authorization": f"Bearer {org_key_secret}",
            "x-org-key-id": org_key_id,
            "Content-Type": "application/json"
        },
        json={"status": status}
    )
    
    response.raise_for_status()
    return response.json()

Use this to quickly pause or resume your entire org (incident response, maintenance windows, etc.).

🔐 Rotation tip: Store org_key_id in config and the secret in your secrets manager. When you rotate the secret, update the manager only—the header stays the same.

Complete Integration Example

Here's a complete example showing the full flow: Code Example (Python 3):

import requests
from typing import Optional, Dict, Any

class APortPlatformIntegration:
    """Complete integration class for APort platform integration."""
    
    def __init__(self, api_base_url: str, api_key: str):
        self.api_base_url = api_base_url
        self.api_key = api_key
    
    def create_user_passport_via_org_issue(
        self,
        org_id: str,
        user_email: str,
        user_display_name: str,
        assurance_type: str = "kyb",
        assurance_level: str = "L4KYC"
    ) -> Dict[str, Any]:
        """Step 1: Create passport for new platform user via org issuance."""
        response = requests.post(
            f"{self.api_base_url}/api/orgs/{org_id}/issue",
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self.api_key}"
            },
            json={
                "name": f"{user_display_name}'s Passport",
                "role": "agent",
                "description": f"Primary passport for {user_display_name}",
                "regions": ["US", "EU"],
                "contact": user_email,
                "pending_owner": {
                    "email": user_email,
                    "display_name": user_display_name
                },
                "assurance": {
                    "type": assurance_type,
                    "assurance_level": assurance_level,
                    "proof": {
                        "verification_id": f"ver_{user_email}",
                        "verified_at": "2025-01-15T10:00:00Z"
                    }
                },
                "capabilities": [
                    {"id": "data.export", "params": {"max_rows": 10000}},
                    {"id": "messaging.send"}
                ],
                "limits": {
                    "max_export_rows": 10000,
                    "msgs_per_day": 1000,
                    "allow_pii": False
                },
                "status": "active"
            }
        )
        
        response.raise_for_status()
        result = response.json()
        print(f"✅ User passport created: {result['agent_id']}")
        print(f"   Assurance Level: {result.get('assurance_level', 'N/A')}")
        print(f"   Claimed: {result.get('claimed', False)}")
        return result
    
    def create_agent_instance(
        self,
        template_id: str,
        platform_user_id: str,
        agent_name: str,
        agent_description: str,
        custom_limits: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Step 2: Create agent passport instance from user's template (inherits assurance level)."""
        response = requests.post(
            f"{self.api_base_url}/api/passports/{template_id}/instances",
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self.api_key}"
            },
            json={
                "platform_id": "your_platform",
                "tenant_ref": platform_user_id,
                "controller_id": platform_user_id,
                "controller_type": "user",
                "overrides": {
                    "limits": custom_limits or {
                        "msgs_per_day": 5000,
                        "max_export_rows": 10000
                    },
                    "status": "active"
                },
                "agent_data": {
                    "name": agent_name,
                    "description": agent_description
                    # assurance_level inherited from template automatically
                }
            }
        )
        
        response.raise_for_status()
        result = response.json()
        print(f"✅ Agent instance created: {result['data']['instance_id']}")
        print(f"   Inherited Assurance Level: {result['data'].get('assurance_level', 'N/A')}")
        return result["data"]
    
    def update_passport(
        self,
        agent_id: str,
        updates: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Step 3: Update passport (limits, capabilities, status, etc.)."""
        response = requests.put(
            f"{self.api_base_url}/api/passports/{agent_id}",
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self.api_key}"
            },
            json=updates
        )
        
        response.raise_for_status()
        result = response.json()
        print(f"✅ Passport updated: {result['data']['agent_id']}")
        return result["data"]

# Usage Example
if __name__ == "__main__":
    aport = APortPlatformIntegration(
        api_base_url="https://api.aport.io",
        api_key="your_api_key_here"
    )
    
    org_id = "ap_org_your_platform_id"
    
    # Step 1: Onboard new user with KYC/KYB assurance
    user_passport = aport.create_user_passport_via_org_issue(
        org_id=org_id,
        user_email="user@example.com",
        user_display_name="John Doe",
        assurance_type="kyb",
        assurance_level="L4KYC"
    )
    template_id = user_passport["agent_id"]
    
    # Step 2: User creates an AI agent (instance inherits L4KYC from template)
    agent_passport = aport.create_agent_instance(
        template_id=template_id,
        platform_user_id="user_12345",
        agent_name="Customer Support Bot",
        agent_description="AI agent for handling customer support",
        custom_limits={"msgs_per_day": 5000, "max_export_rows": 10000}
    )
    
    # Step 3: User upgrades - increase limits and add capabilities
    aport.update_passport(agent_passport["instance_id"], {
        "limits": {
            "max_export_rows": 50000,
            "msgs_per_day": 10000
        },
        "capabilities": [
            {"id": "data.export", "params": {"max_rows": 50000}},
            {"id": "messaging.send", "params": {"max_per_day": 10000}}
        ]
    })

Endpoint Summary

Endpoint Method Purpose Required Scopes
/api/keys POST Create API key manage_keys
/api/orgs/{id}/orgkey POST Create org key manage_keys
/api/admin/organizations/{id}/assurance-provider PATCH Configure assurance providers Admin
/api/passports/create POST Create passport issue
/api/orgs/{id}/issue POST Issue passport with assurance issue
/api/passports/{agent_id} PUT Update passport update
/api/passports/{agent_id} GET Get passport read
/api/passports GET List passports list_agents
/api/passports/{template_id}/instances POST Create instance issue

Error Handling

All endpoints return standard error responses:

{
  "success": false,
  "error": "Error message",
  "requestId": "req_abc123..."
}

Common HTTP Status Codes:

  • 200 OK: Success
  • 201 Created: Resource created
  • 400 Bad Request: Invalid request data
  • 401 Unauthorized: Missing or invalid API key
  • 403 Forbidden: Insufficient permissions (missing scope)
  • 404 Not Found: Resource not found
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Server error
Code Example (Python 3):

import requests
from requests.exceptions import HTTPError

def handle_aport_request(func):
    """Decorator to handle APort API errors."""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except HTTPError as e:
            response = e.response
            if response.status_code == 401:
                raise Exception("Invalid API key. Please check your credentials.")
            elif response.status_code == 403:
                raise Exception("Insufficient permissions. Check your API key scopes.")
            elif response.status_code == 429:
                raise Exception("Rate limit exceeded. Please retry later.")
            else:
                error_data = response.json() if response.content else {}
                raise Exception(error_data.get("error", f"Request failed: {response.status_text}"))
    return wrapper

Best Practices

  1. Store API Keys Securely: Never commit API keys to version control. Use environment variables or secret management services.
  2. Use Appropriate Scopes: Only request the scopes you need. Don't request manage_keys if you only need to create passports.
  3. Handle Rate Limits: APort enforces rate limits (typically 60 RPM for create/update, 100 RPM for read). Implement exponential backoff for retries.
  4. Configure Assurance Providers First: Before issuing passports with assurance levels, ensure your organization has configured assurance providers.
  5. Use Instance Creation: Prefer creating instances from templates to automatically inherit assurance levels and maintain consistency.
  6. Monitor Audit IDs: Every create/update operation returns an audit_id. Store these for compliance and debugging.
  7. Set Appropriate Limits: Start with conservative limits and increase based on actual usage patterns.
  8. Use Idempotency Keys: For critical operations, include an Idempotency-Key header to prevent duplicate operations.

SDKs and Middleware

For additional examples, SDKs, and middleware in various languages, see:

  • APort SDKs and Middlewares Repository
    • Node.js SDK: @aporthq/sdk-node
    • Express.js Middleware: @aporthq/middleware-express
    • Python SDK: aporthq-sdk-python
    • FastAPI Middleware: agent-passport-middleware-fastapi


Next Steps

  • Read Passports: Use GET /api/passports/{agent_id} to retrieve passport data
  • List Passports: Use GET /api/passports?owner_id={user_id} to list all passports for a user
  • Verify Policies: Use /api/verify/policy/{pack_id} to verify agent actions
  • Set Up Webhooks: Configure webhooks to receive notifications about passport changes

Support


Ready to integrate? Start by creating your API key, configuring assurance providers, and then follow the steps above. If you run into any issues, check the error responses for detailed messages, or reach out to our support team.