Building Agents

This guide walks you through building an AI agent that can autonomously make purchases using AutopayOS. You'll learn how to request permissions, interact with merchants, and execute payments.

Overview

A shopping agent follows this flow:

Diagram
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Receive    │────▶│   Request    │────▶│    Shop &    │────▶│   Execute    │
│    Goal      │     │  Permission  │     │  Build Cart  │     │   Payment    │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘

Prerequisites

  • Node.js 18+ or Python 3.9+
  • AutopayOS API key
  • Understanding of DIDs and VCs (see Authentication)

Project setup

TypeScript

Bash
mkdir my-shopping-agent
cd my-shopping-agent
npm init -y
npm install @autopayos/sdk typescript ts-node

Create tsconfig.json:

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist"
  }
}

Python

Bash
mkdir my-shopping-agent
cd my-shopping-agent
python -m venv venv
source venv/bin/activate
pip install autopayos

Basic agent structure

<tabs> <tab title="TypeScript">
TypeScript
// agent.ts
import { AutopayosClient } from '@autopayos/sdk';

interface ShoppingGoal {
  description: string;
  maxBudget: number;
  merchant?: string;
}

class ShoppingAgent {
  private client: AutopayosClient;
  private agentDid: string;
  private principalDid: string;

  constructor(
    apiKey: string,
    agentDid: string,
    principalDid: string
  ) {
    this.client = new AutopayosClient({
      baseUrl: 'https://api.autopayos.com',
      apiKey,
    });
    this.agentDid = agentDid;
    this.principalDid = principalDid;
  }

  async shop(goal: ShoppingGoal): Promise<void> {
    console.log(`🛒 Shopping for: ${goal.description}`);
    console.log(`💰 Budget: $${goal.maxBudget}`);

    // Step 1: Request permission
    const permission = await this.requestPermission(goal);
    if (!permission.allowed) {
      throw new Error(`Permission denied: ${permission.reasonCodes}`);
    }

    // Step 2: Find products and build cart
    const cart = await this.buildCart(goal);

    // Step 3: Verify cart
    const verification = await this.verifyCart(permission.intentVc, cart);
    if (!verification.allowed) {
      throw new Error(`Cart rejected: ${verification.reasonCodes}`);
    }

    // Step 4: Execute payment
    const payment = await this.executePayment(verification.approvalToken);

    console.log('Purchase complete!');
    console.log(`Order ID: ${payment.pmVc.credentialSubject.mandateId}`);
  }

  private async requestPermission(goal: ShoppingGoal) {
    console.log('Requesting permission...');
    
    return await this.client.requestPermission({
      agentDid: this.agentDid,
      principalDid: this.principalDid,
      amount: goal.maxBudget,
      currency: 'USD',
      merchantDomain: goal.merchant,
    });
  }

  private async buildCart(goal: ShoppingGoal) {
    console.log('🔍 Finding products...');
    
    // In a real agent, you would:
    // 1. Browse merchant website
    // 2. Search for products matching the goal
    // 3. Compare prices and reviews
    // 4. Select optimal products
    
    // For demo, return a mock cart
    return {
      merchant: goal.merchant || 'amazon.com',
      items: [
        { name: 'USB-C Cable 6ft', price: 12.99, quantity: 2 },
        { name: 'Phone Stand', price: 9.99, quantity: 1 },
      ],
      total: 35.97,
      currency: 'USD',
    };
  }

  private async verifyCart(intentVc: any, cart: any) {
    console.log('Verifying cart...');
    
    // Convert cart to VC format
    const cartVc = {
      type: ['VerifiableCredential', 'CartMandate'],
      credentialSubject: cart,
    };

    return await this.client.verifyCart({
      intentVc,
      cartVc,
    });
  }

  private async executePayment(approvalToken: string) {
    console.log('Executing payment...');
    
    return await this.client.executePayment({
      approvalToken,
    });
  }
}

// Usage
async function main() {
  const agent = new ShoppingAgent(
    process.env.AUTOPAYOS_API_KEY!,
    'did:key:z6MkAgentDid...', // Your agent's DID
    'did:key:z6MkUserDid...',  // User who authorized the agent
  );

  await agent.shop({
    description: 'Buy USB cables for new laptop',
    maxBudget: 50.00,
    merchant: 'amazon.com',
  });
}

main().catch(console.error);
</tab> <tab title="Python">
Python
# agent.py
from autopayos import AutopayosClient
from dataclasses import dataclass
from typing import Optional, List, Dict, Any

@dataclass
class ShoppingGoal:
    description: str
    max_budget: float
    merchant: Optional[str] = None

@dataclass
class CartItem:
    name: str
    price: float
    quantity: int

class ShoppingAgent:
    def __init__(
        self,
        api_key: str,
        agent_did: str,
        principal_did: str
    ):
        self.client = AutopayosClient(
            base_url="https://api.autopayos.com",
            api_key=api_key
        )
        self.agent_did = agent_did
        self.principal_did = principal_did

    async def shop(self, goal: ShoppingGoal) -> Dict[str, Any]:
        print(f"🛒 Shopping for: {goal.description}")
        print(f"💰 Budget: ${goal.max_budget}")

        # Step 1: Request permission
        permission = await self.request_permission(goal)
        if not permission.allowed:
            raise Exception(f"Permission denied: {permission.reason_codes}")

        # Step 2: Find products and build cart
        cart = await self.build_cart(goal)

        # Step 3: Verify cart
        verification = await self.verify_cart(permission.intent_vc, cart)
        if not verification.allowed:
            raise Exception(f"Cart rejected: {verification.reason_codes}")

        # Step 4: Execute payment
        payment = await self.execute_payment(verification.approval_token)

        print("Purchase complete!")
        print(f"Order ID: {payment.pm_vc['credentialSubject']['mandateId']}")
        
        return payment

    async def request_permission(self, goal: ShoppingGoal):
        print("Requesting permission...")
        
        return await self.client.request_permission(
            agent_did=self.agent_did,
            principal_did=self.principal_did,
            amount=goal.max_budget,
            currency="USD",
            merchant_domain=goal.merchant
        )

    async def build_cart(self, goal: ShoppingGoal) -> Dict[str, Any]:
        print("🔍 Finding products...")
        
        # In a real agent, browse and search
        return {
            "merchant": goal.merchant or "amazon.com",
            "items": [
                {"name": "USB-C Cable 6ft", "price": 12.99, "quantity": 2},
                {"name": "Phone Stand", "price": 9.99, "quantity": 1}
            ],
            "total": 35.97,
            "currency": "USD"
        }

    async def verify_cart(self, intent_vc, cart):
        print("Verifying cart...")
        
        cart_vc = {
            "type": ["VerifiableCredential", "CartMandate"],
            "credentialSubject": cart
        }

        return await self.client.verify_cart(
            intent_vc=intent_vc,
            cart_vc=cart_vc
        )

    async def execute_payment(self, approval_token: str):
        print("Executing payment...")
        
        return await self.client.execute_payment(
            approval_token=approval_token
        )

# Usage
import asyncio
import os

async def main():
    agent = ShoppingAgent(
        api_key=os.environ["AUTOPAYOS_API_KEY"],
        agent_did="did:key:z6MkAgentDid...",
        principal_did="did:key:z6MkUserDid..."
    )

    await agent.shop(ShoppingGoal(
        description="Buy USB cables for new laptop",
        max_budget=50.00,
        merchant="amazon.com"
    ))

asyncio.run(main())
</tab> </tabs>

Adding LLM decision making

Enhance your agent with LLM-powered product selection:

TypeScript
import OpenAI from 'openai';

class SmartShoppingAgent extends ShoppingAgent {
  private openai: OpenAI;

  constructor(apiKey: string, openaiKey: string, agentDid: string, principalDid: string) {
    super(apiKey, agentDid, principalDid);
    this.openai = new OpenAI({ apiKey: openaiKey });
  }

  private async selectProducts(goal: ShoppingGoal, products: Product[]): Promise<Product[]> {
    const response = await this.openai.chat.completions.create({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: `You are a shopping assistant. Select products that best match the user's goal within budget. Return a JSON array of product IDs.`
        },
        {
          role: 'user',
          content: `
Goal: ${goal.description}
Budget: $${goal.maxBudget}

Available products:
${JSON.stringify(products, null, 2)}

Select the best products and return their IDs as a JSON array.
          `
        }
      ],
      response_format: { type: 'json_object' },
    });

    const selection = JSON.parse(response.choices[0].message.content!);
    return products.filter(p => selection.productIds.includes(p.id));
  }
}

Handling Human Presence

When Human Presence (HP) is required, prompt the user:

TypeScript
async shop(goal: ShoppingGoal): Promise<void> {
  const permission = await this.requestPermission(goal);

  // Check if HP is required
  if (permission.hpRequired) {
    console.log('🔐 Human verification required');
    
    // Get HP proof from user (WebAuthn)
    const hpProof = await this.getHumanPresenceProof(permission.hpChallenge);
    
    // Include HP proof in cart verification
    const verification = await this.client.verifyCart({
      intentVc: permission.intentVc,
      cartVc: cart,
      hpProof: hpProof,
    });
  }
}

private async getHumanPresenceProof(challenge: HpChallenge) {
  // In a browser context:
  const credential = await navigator.credentials.get({
    publicKey: {
      challenge: Buffer.from(challenge.challengeId),
      userVerification: 'required',
    },
  });

  return {
    type: 'WebAuthn',
    challengeId: challenge.challengeId,
    credential: credential,
  };
}

Error handling

Build robust error handling:

TypeScript
async shop(goal: ShoppingGoal): Promise<void> {
  try {
    // ... shopping logic
  } catch (error) {
    await this.handleError(error, goal);
  }
}

private async handleError(error: any, goal: ShoppingGoal) {
  const code = error.code || error.message;

  switch (code) {
    case 'AMOUNT_OVER_CAP':
      console.log('Budget exceeds policy limit. Trying with lower amount...');
      await this.shop({ ...goal, maxBudget: goal.maxBudget * 0.5 });
      break;

    case 'MERCHANT_NOT_ALLOWED':
      console.log('Merchant not allowed. Trying alternative...');
      const alternatives = await this.findAlternativeMerchants(goal);
      if (alternatives.length > 0) {
        await this.shop({ ...goal, merchant: alternatives[0] });
      }
      break;

    case 'VELOCITY_EXCEEDED':
      console.log('⏳ Too many transactions. Will retry later.');
      // Schedule retry
      break;

    case 'APPROVAL_TOKEN_EXPIRED':
      console.log('🔄 Token expired. Restarting flow...');
      await this.shop(goal);
      break;

    default:
      console.error('Unhandled error:', error);
      throw error;
  }
}

Registering your agent

Register your agent with AutopayOS:

TypeScript
async function registerAgent() {
  const client = new AutopayosClient({
    baseUrl: 'https://api.autopayos.com',
    apiKey: process.env.AUTOPAYOS_API_KEY,
  });

  const agent = await client.registerAgent({
    name: 'My Shopping Assistant',
    description: 'AI-powered shopping agent for daily purchases',
    capabilities: ['browse', 'purchase', 'compare'],
    publicKey: myPublicKey,  // Ed25519 public key
  });

  console.log('Agent DID:', agent.did);
  console.log('Status:', agent.status);

  return agent;
}

Agent lifecycle

Diagram
┌──────────────┐
│   PENDING    │  Registration submitted
└──────┬───────┘
       │
       ▼
┌──────────────┐
│    ACTIVE    │  Ready to transact
└──────┬───────┘
       │
       ├──────────────────────────────────┐
       │                                  │
       ▼                                  ▼
┌──────────────┐                   ┌──────────────┐
│   REVOKED    │                   │   SUSPENDED  │
│              │                   │              │
│ Permanently  │                   │ Temporarily  │
│ disabled     │                   │ disabled     │
└──────────────┘                   └──────────────┘

Monitoring agent activity

Query your agent's transaction history:

TypeScript
const activity = await client.getAgentActivity(agentDid, {
  from: '2025-12-01',
  to: '2025-12-17',
});

console.log('Total transactions:', activity.count);
console.log('Total spent:', activity.totalAmount);
console.log('Success rate:', activity.successRate);

for (const txn of activity.transactions) {
  console.log(`${txn.merchant}: $${txn.amount} - ${txn.status}`);
}

Testing your agent

Use sandbox mode for testing:

TypeScript
const client = new AutopayosClient({
  baseUrl: 'https://sandbox-api.autopayos.com',
  apiKey: 'ak_test_...',
});

// Use test DIDs
const testAgentDid = 'did:key:z6MkTest123...';
const testUserDid = 'did:key:z6MkTestUser...';

// Test different scenarios
await testPurchaseSuccess();
await testPurchaseDenied();
await testHumanPresenceRequired();

Best practices

1. Request minimum permissions

Only ask for what you need:

TypeScript
// Good: Specific request
await client.requestPermission({
  amount: 25.00,
  merchantDomain: 'amazon.com',
  // ...
});

// Bad: Overly broad
await client.requestPermission({
  amount: 10000.00,
  // No merchant restriction
  // ...
});

2. Handle all error cases

TypeScript
const permission = await client.requestPermission({ ... });

if (!permission.allowed) {
  // Log for debugging
  console.log('Denied with codes:', permission.reasonCodes);
  
  // Inform user appropriately
  return {
    success: false,
    message: this.getUserFriendlyMessage(permission.reasonCodes),
  };
}

3. Respect rate limits

TypeScript
import { RateLimiter } from 'limiter';

const limiter = new RateLimiter({
  tokensPerInterval: 10,
  interval: 'minute',
});

async function makeRequest() {
  await limiter.removeTokens(1);
  return await client.requestPermission({ ... });
}

4. Log evidence IDs

Always log transaction IDs for audit:

TypeScript
const payment = await client.executePayment({ approvalToken });

console.log('Transaction completed', {
  mandateId: payment.pmVc.credentialSubject.mandateId,
  amount: payment.pmVc.credentialSubject.amount,
  timestamp: new Date().toISOString(),
});

// Store for later reference
await db.transactions.create({
  mandateId: payment.pmVc.credentialSubject.mandateId,
  // ...
});

Example: Complete agent

See the full example in our GitHub repo:

Bash
git clone https://github.com/autopayos/examples
cd examples/simple-agent
npm install
npm run dev

Next steps