# Managing AI Agents Source: https://docs.nevermined.app/docs/api-reference/cli/agents Complete guide to registering and managing AI agents with the Nevermined CLI # Managing AI Agents Complete guide to registering and managing AI agents with the Nevermined CLI. ## Overview AI agents are services that users can access by purchasing payment plans. Agents can be: * **AI assistants**: Chat-based conversational agents * **API services**: REST APIs with metered access * **Data pipelines**: Processing services with usage tracking * **Tools**: Utility services with pay-per-use billing ## Listing Agents View all registered agents: ```bash theme={null} # Table output (default) nvm agents list # JSON output for scripting nvm agents list --format json ``` Example output: ``` AI Agents ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Agent ID Name Plans ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ "987654321098765432" GPT-4 Assistant 3 "876543210987654321" Code Helper 2 "765432109876543210" Data Analyzer 1 ``` ## Getting Agent Details Retrieve detailed information about a specific agent: ```bash theme={null} nvm agents get-agent ``` Example: ```bash theme={null} nvm agents get-agent "987654321098765432"23 ``` Output includes: * Agent metadata (name, description, creator) * API endpoint configuration * Associated payment plans * Access control settings * Authentication requirements ## Registering Agents ### Basic Agent Registration Register a new AI agent with payment plans: ```bash theme={null} nvm agents register-agent \ --agent-metadata agent-metadata.json \ --agent-api "https://api.example.com/v1/agent" \ --payment-plans ""111111111111111111","222222222222222222" ``` **agent-metadata.json**: ```json theme={null} { "name": "My AI Assistant", "description": "GPT-4 powered conversational AI with specialized knowledge", "version": "1.0.0", "author": "Your Name", "tags": ["ai", "assistant", "gpt4"], "customData": { "capabilities": ["chat", "code-generation", "analysis"], "model": "gpt-4", "language": "en" } } ``` ### Agent with Service Configuration Register an agent with detailed service configuration: ```bash theme={null} nvm agents register-agent \ --agent-metadata metadata.json \ --agent-api "https://api.example.com" \ --payment-plans ""111111111111111111"" \ --service-config service-config.json ``` **service-config.json**: ```json theme={null} { "endpoints": { "chat": "/v1/chat", "completion": "/v1/completion", "embedding": "/v1/embedding" }, "authentication": { "type": "bearer", "headerName": "Authorization" }, "rateLimit": { "maxRequests": 100, "windowMs": 60000 }, "timeout": 30000 } ``` ## Updating Agents ### Update Agent Metadata Modify agent name, description, or other metadata: ```bash theme={null} nvm agents update-agent-metadata \ --agent-metadata updated-metadata.json ``` **updated-metadata.json**: ```json theme={null} { "name": "My AI Assistant Pro", "description": "Enhanced GPT-4 assistant with advanced capabilities", "version": "2.0.0", "tags": ["ai", "assistant", "gpt4", "premium"] } ``` ### Update Agent API Endpoint Change the agent's API endpoint: ```bash theme={null} nvm agents update-agent-endpoint \ --agent-api "https://api-v2.example.com/agent" ``` ### Add Payment Plans Associate additional payment plans with an agent: ```bash theme={null} nvm agents add-agent-plan \ --plan-id "444444444444444444" ``` ### Remove Payment Plans Disassociate a payment plan from an agent: ```bash theme={null} nvm agents remove-agent-plan \ --plan-id "555555555555555555" ``` ## Agent Files and Resources ### Upload Agent Files Upload files associated with your agent (models, configs, etc.): ```bash theme={null} nvm agents upload-agent-files \ --files "./model.bin,./config.json" ``` ### Download Agent Files Download files from an agent: ```bash theme={null} nvm agents download-agent-files \ --destination ./downloads ``` ## Advanced Agent Operations ### Create Agent Execution Create a new agent execution/session: ```bash theme={null} nvm agents create-agent-execution \ --execution-config execution.json ``` **execution.json**: ```json theme={null} { "sessionId": "unique-session-id", "parameters": { "temperature": 0.7, "maxTokens": 2000 }, "context": { "userId": "user123", "conversationId": "conv456" } } ``` ### Get Agent Execution Status Check the status of an agent execution: ```bash theme={null} nvm agents get-agent-execution-status ``` ### List Agent Executions View all executions for an agent: ```bash theme={null} nvm agents list-agent-executions ``` ## Agent Access Control ### Get Agent Access Config View who can access your agent: ```bash theme={null} nvm agents get-agent-access-config ``` ### Update Agent Access Config Modify access control settings: ```bash theme={null} nvm agents update-agent-access-config \ --access-config access.json ``` **access.json**: ```json theme={null} { "public": false, "allowedAddresses": [ "0x123...", "0x456..." ], "requiresPayment": true, "allowedPlans": [ ""111111111111111111"", ""222222222222222222" ] } ``` ## Integration Examples ### Example 1: Full Agent Setup Complete workflow to register and configure an AI agent: ```bash theme={null} #!/bin/bash # Complete agent setup script # 1. Create a payment plan first PLAN_ID=$(nvm plans register-credits-plan \ --plan-metadata plan.json \ --price-config price.json \ --credits-config credits.json \ --format json | jq -r '.planId') echo "Created plan: $PLAN_ID" # 2. Register the agent with the plan AGENT_ID=$(nvm agents register-agent \ --agent-metadata agent.json \ --agent-api "https://api.example.com/agent" \ --payment-plans "$PLAN_ID" \ --format json | jq -r '.agentId') echo "Registered agent: $AGENT_ID" # 3. Update agent metadata if needed nvm agents update-agent-metadata $AGENT_ID \ --agent-metadata updated-agent.json # 4. Verify agent is accessible nvm agents get-agent $AGENT_ID echo "Agent setup complete!" ``` ### Example 2: Multi-Plan Agent Register an agent with multiple pricing tiers: ```bash theme={null} # Create basic plan BASIC_PLAN=$(nvm plans register-credits-plan \ --plan-metadata basic-plan.json \ --price-config basic-price.json \ --credits-config basic-credits.json \ --format json | jq -r '.planId') # Create premium plan PREMIUM_PLAN=$(nvm plans register-credits-plan \ --plan-metadata premium-plan.json \ --price-config premium-price.json \ --credits-config premium-credits.json \ --format json | jq -r '.planId') # Register agent with both plans nvm agents register-agent \ --agent-metadata agent.json \ --agent-api "https://api.example.com" \ --payment-plans "$BASIC_PLAN,$PREMIUM_PLAN" ``` ### Example 3: Agent Monitoring Monitor agent usage and executions: ```bash theme={null} #!/bin/bash # Agent monitoring script AGENT_ID="987654321098765432"23" # Get agent details echo "Agent Details:" nvm agents get-agent $AGENT_ID # List recent executions echo -e "\nRecent Executions:" nvm agents list-agent-executions $AGENT_ID # Check access configuration echo -e "\nAccess Configuration:" nvm agents get-agent-access-config $AGENT_ID ``` ## JSON Output for Automation Use `--format json` to integrate with other tools: ```bash theme={null} # Get agent data AGENT=$(nvm agents get-agent "987654321098765432"23 --format json) # Extract fields NAME=$(echo $AGENT | jq -r '.name') API=$(echo $AGENT | jq -r '.apiEndpoint') PLANS=$(echo $AGENT | jq -r '.plans | join(",")') echo "Agent: $NAME" echo "API: $API" echo "Plans: $PLANS" # Check if agent has specific plan HAS_PLAN=$(echo $AGENT | jq --arg pid ""111111111111111111"" \ '.plans | contains([$pid])') if [ "$HAS_PLAN" = "true" ]; then echo "Agent has the required plan" fi ``` ## Best Practices ### 1. Use Descriptive Metadata Provide comprehensive information about your agent: ```json theme={null} { "name": "GPT-4 Code Assistant", "description": "AI-powered code generation and debugging assistant with support for 20+ programming languages", "version": "1.2.0", "author": "YourCompany", "tags": ["code", "ai", "programming", "debugging"], "customData": { "supportedLanguages": ["python", "javascript", "typescript", "go", "rust"], "features": ["code-generation", "debugging", "refactoring", "documentation"], "model": "gpt-4-turbo", "maxContextLength": 128000 } } ``` ### 2. Implement Proper API Endpoints Ensure your agent API follows best practices: * Use HTTPS for all endpoints * Implement rate limiting * Return proper HTTP status codes * Include error handling * Log requests for debugging ### 3. Version Your Agents Update version numbers when making changes: ```bash theme={null} # Update agent with new version nvm agents update-agent-metadata $AGENT_ID \ --agent-metadata updated-metadata.json # Create new payment plan for new version if needed nvm plans register-credits-plan \ --plan-metadata v2-plan.json \ --price-config v2-price.json \ --credits-config v2-credits.json ``` ### 4. Test Before Production Always test agents in staging environment: ```bash theme={null} # Register in staging nvm --profile staging agents register-agent \ --agent-metadata agent.json \ --agent-api "https://staging-api.example.com" \ --payment-plans "$STAGING_PLAN_ID" # Test the agent nvm --profile staging agents get-agent $STAGING_AGENT_ID # Once verified, deploy to production nvm --profile production agents register-agent \ --agent-metadata agent.json \ --agent-api "https://api.example.com" \ --payment-plans "$PROD_PLAN_ID" ``` ### 5. Organize Multiple Agents Keep agent configurations organized: ``` agents/ ├── code-assistant/ │ ├── metadata.json │ ├── service-config.json │ └── plans/ │ ├── basic-plan.json │ └── premium-plan.json ├── data-analyzer/ │ ├── metadata.json │ ├── service-config.json │ └── plans/ │ └── enterprise-plan.json └── chat-bot/ ├── metadata.json └── plans/ └── starter-plan.json ``` ## Common Issues ### "Agent endpoint not reachable" Ensure your agent API endpoint is: * Publicly accessible * Using HTTPS * Responding to health checks * Not blocking Nevermined's IP ranges ### "Payment plan not found" Verify the payment plan exists and you're using the correct plan ID: ```bash theme={null} # List all your plans nvm plans list # Verify specific plan nvm plans get-plan ``` ### "Insufficient permissions" Ensure you're the agent owner or have proper permissions: ```bash theme={null} # Check agent details nvm agents get-agent # Verify you're using the correct profile nvm config show ``` ## Next Steps * [Purchases](./purchases.md) - Users ordering your agent plans * [Querying](./querying.md) - Accessing agents with X402 tokens * [Plans](./plans.md) - Creating payment plans for your agents # Getting Started Source: https://docs.nevermined.app/docs/api-reference/cli/getting-started Complete guide to installing and configuring the Nevermined CLI # Getting Started Complete guide to installing and configuring the Nevermined CLI. ## Prerequisites Before installing the CLI, ensure you have: * **Node.js** >= 18.0.0 * **npm** or **yarn** package manager * A Nevermined account with API key ## Getting Your API Key 1. Visit [nevermined.app](https://nevermined.app) and sign in 2. Navigate to your account settings 3. Generate a new API key from the "API Keys" section 4. Save your API key securely - you'll need it for CLI configuration The API key format is: `live:eyJxxxxaaaa` ## Installation ### Option 1: Global Installation (Recommended) Install the CLI globally to use the `nvm` command from anywhere: ```bash theme={null} npm install -g @nevermined-io/cli ``` Verify installation: ```bash theme={null} nvm --version ``` ### Option 2: Using npx (No Installation) Run the CLI without installing: ```bash theme={null} npx @nevermined-io/cli --help ``` This is useful for one-off commands or trying the CLI before installing. ### Option 3: From Source For development or contributing: ```bash theme={null} # Clone the repository git clone https://github.com/nevermined-io/payments cd payments/cli # Install dependencies yarn install # Build the project yarn build:manifest # Run the CLI ./bin/run.js --help ``` ## Configuration ### Interactive Setup The easiest way to configure the CLI is using the interactive setup: ```bash theme={null} nvm config init ``` This will prompt you for: * **NVM API Key**: Your API key from nevermined.app * **Environment**: Choose from: * `sandbox` - Development and testing (recommended for learning) * `live` - Production environment Example: ```bash theme={null} $ nvm config init ? Enter your NVM API Key: live:eyJxxxxaaaabbbbbbbb ? Select environment: sandbox ✅ Configuration saved to /home/user/.config/nvm/config.json ``` ### Configuration File The CLI stores configuration in `~/.config/nvm/config.json`: ```json theme={null} { "profiles": { "default": { "nvmApiKey": "live:eyJxxxxaaaabbbbbbbb", "environment": "live" } }, "activeProfile": "default" } ``` ### Multiple Profiles Create multiple profiles for different environments or accounts: ```bash theme={null} # Initialize with default profile nvm config init # Create a production profile nvm config set profiles.production.nvmApiKey nvm-yyyyyyyy... nvm config set profiles.production.environment live # Switch active profile nvm config set activeProfile production ``` Use a specific profile for a command: ```bash theme={null} nvm --profile production plans list ``` ### Environment Variables Override configuration with environment variables: ```bash theme={null} # Set API key export NVM_API_KEY=sandbox:eyJxxxxaaaabbbbbbbb # Set environment export NVM_ENVIRONMENT=sandbox # Run commands nvm plans list ``` This is useful for: * CI/CD pipelines * Temporary configuration changes * Scripting ### View Configuration Display your current configuration: ```bash theme={null} nvm config show ``` Output: ``` Current Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Active Profile: default Environment: sandbox API Key: live:eyJxxxxaaaabbbbbbbb (truncated) ``` ## Verify Setup Test your configuration by listing available plans: ```bash theme={null} nvm plans list ``` If configured correctly, you should see a table of available payment plans. ## Environment Guide Choose the right environment for your use case: | Environment | Use Case | Blockchain | Payments | | ----------- | ------------------------------ | ------------ | ------------- | | `sandbox` | Development, learning, testing | Test network | Test credits | | `live` | Production | Mainnet | Real payments | **Recommendation**: Start with `sandbox` for development and testing. ## Common Issues ### "Command not found: nvm" After global installation, if `nvm` command is not found: 1. Verify installation: `npm list -g @nevermined-io/cli` 2. Check your PATH includes npm global bin directory 3. Restart your terminal 4. Try using the full path: `npx @nevermined-io/cli` ### "API Key not found" If you get an API key error: ```bash theme={null} # Initialize configuration nvm config init # Or set environment variable export NVM_API_KEY=your-api-key-here ``` ### Permission Errors If you get permission errors during global installation: ```bash theme={null} # Use npx instead npx @nevermined-io/cli --help # Or install without sudo using nvm/volta ``` ## Next Steps Now that you've installed and configured the CLI, explore: * [Plans](./plans.md) - Create and manage payment plans * [Agents](./agents.md) - Register AI agents * [Purchases](./purchases.md) - Order plans and make payments * [Querying](./querying.md) - Query agents with access tokens ## Getting Help Get help for any command: ```bash theme={null} # General help nvm --help # Topic help nvm plans --help # Command help nvm plans get-plan --help ``` For support: * Documentation: [https://nevermined.ai/docs](https://nevermined.ai/docs) * Issues: [https://github.com/nevermined-io/payments/issues](https://github.com/nevermined-io/payments/issues) * Discord: [https://discord.gg/nevermined](https://discord.gg/nevermined) # Other Commands Source: https://docs.nevermined.app/docs/api-reference/cli/other-commands Reference guide for additional CLI commands including configuration, facilitator operations, organizations, and utilities # Other Commands Reference guide for additional CLI commands including configuration, facilitator operations, organizations, and utilities. ## Configuration Commands ### Initialize Configuration Set up your CLI configuration interactively: ```bash theme={null} nvm config init ``` Interactive prompts: * NVM API Key * Environment (sandbox, live) * Profile name (optional) ### View Configuration Display your current configuration: ```bash theme={null} nvm config show ``` Output: ``` Current Configuration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Active Profile: default Environment: sandbox API Key: sandbox:eyJxxxxaaaa...bbbbbbbb (truncated) Config File: /home/user/.config/nvm/config.json ``` ### Set Configuration Values Update specific configuration values: ```bash theme={null} # Set API key nvm config set nvmApiKey sandbox:eyJxxxxaaaa...bbbbbbbb # Set environment nvm config set environment sandbox # Set active profile nvm config set activeProfile production # Create new profile nvm config set profiles.production.nvmApiKey live:eyJyyyybbbb... nvm config set profiles.production.environment live ``` ### Configuration File Structure The config file at `~/.config/nvm/config.json`: ```json theme={null} { "profiles": { "default": { "nvmApiKey": "sandbox:eyJxxxxaaaa...bbbbbbbb", "environment": "sandbox" }, "staging": { "nvmApiKey": "sandbox-staging-key", "environment": "sandbox" }, "production": { "nvmApiKey": "live:eyJyyyybbbb...", "environment": "live" } }, "activeProfile": "default" } ``` ## Facilitator Commands Facilitator commands are used by agent owners to verify and settle permissions (credit burning). ### Verify Permissions Verify that a request has valid permissions before processing: ```bash theme={null} nvm facilitator verify-permissions \ --params verify.json ``` **verify.json**: ```json theme={null} { "planId": ""123456789012345678"", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "creditsRequired": 5 } ``` Output: ``` Permission Verification ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Status: Valid Plan ID: "123456789012345678" Subscriber: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb Credits Available: 100 Credits Required: 5 Can Proceed: true Signature Valid: true ``` ### Settle Permissions Burn credits after processing a request: ```bash theme={null} nvm facilitator settle-permissions \ --params settle.json ``` **settle.json**: ```json theme={null} { "planId": ""123456789012345678"", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "creditsBurned": 5, "executionId": "exec-20240315-001", "metadata": { "requestType": "chat", "duration": 1234, "tokensUsed": 500 } } ``` Output: ``` Credits Settled ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan ID: "123456789012345678" Credits Burned: 5 Previous Balance: 100 New Balance: 95 Transaction Hash: 0x1234567890abcdef... Execution ID: exec-20240315-001 Status: Confirmed ``` ### Batch Verification Verify multiple requests at once: ```bash theme={null} #!/bin/bash # Batch permission verification REQUESTS=("request1.json" "request2.json" "request3.json") for REQUEST in "${REQUESTS[@]}"; do echo "Verifying $REQUEST..." RESULT=$(nvm facilitator verify-permissions \ --params $REQUEST \ --format json) CAN_PROCEED=$(echo $RESULT | jq -r '.canProceed') if [ "$CAN_PROCEED" = "true" ]; then echo "✅ Valid - Processing request" # Process request here else echo "❌ Invalid - Rejecting request" fi done ``` ## Organizations Commands Manage organization members and settings. ### Create Organization Member Add a new member to your organization: ```bash theme={null} nvm organizations create-member \ --member-data member.json ``` **member.json**: ```json theme={null} { "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "role": "developer", "permissions": ["read", "write"], "metadata": { "name": "John Doe", "email": "john@example.com", "department": "Engineering" } } ``` Output: ``` Member Created ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Member ID: member-123 Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb Role: developer Permissions: read, write Status: Active ``` ### List Organization Members View all members in your organization: ```bash theme={null} nvm organizations get-members ``` Output: ``` Organization Members ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Name Address Role Status ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ John Doe 0x742d35... developer Active Jane Smith 0x8f3a21... admin Active Bob Wilson 0x1c5e92... viewer Active ``` ### Update Member Role Modify a member's role or permissions: ```bash theme={null} nvm organizations update-member \ --member-data updated-member.json ``` **updated-member.json**: ```json theme={null} { "role": "admin", "permissions": ["read", "write", "delete", "admin"] } ``` ### Remove Member Remove a member from the organization: ```bash theme={null} nvm organizations remove-member ``` ## X402 Token Commands ### Get X402 Access Token Generate an access token for a plan: ```bash theme={null} nvm x402token get-x402-access-token ``` Example: ```bash theme={null} nvm x402token get-x402-access-token "123456789012345678" ``` Output: ``` X402 Access Token ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Plan ID: "123456789012345678" Issued: 2024-03-15 10:30:00 UTC Expires: 2024-03-15 11:30:00 UTC Credits: 100 ``` ### Save Token Save token for later use: ```bash theme={null} # Save to file nvm x402token get-x402-access-token "123456789012345678" \ --format json | jq -r '.token' > ~/.nvm/token.txt # Save to environment variable export X402_TOKEN=$(nvm x402token get-x402-access-token "123456789012345678" \ --format json | jq -r '.token') ``` ## Global Flags All commands support these global flags: ### Format Flag Control output format: ```bash theme={null} # Table output (default) nvm plans list # JSON output nvm plans list --format json # Quiet output (minimal) nvm plans list --format quiet ``` ### Profile Flag Use a specific configuration profile: ```bash theme={null} # Use production profile nvm --profile production plans list # Use staging profile nvm --profile staging agents list ``` ### Verbose Flag Enable verbose output with detailed logging: ```bash theme={null} nvm plans order-plan "123456789012345678" --verbose ``` Output includes: * Request/response details * API calls made * Stack traces for errors * Debug information ## Utility Commands ### Version Information Check CLI version: ```bash theme={null} nvm --version ``` ### Help Get help for commands: ```bash theme={null} # General help nvm --help # Topic help nvm plans --help nvm agents --help nvm config --help # Command help nvm plans get-plan --help nvm agents register-agent --help ``` ## Scripting Examples ### Environment Switcher Switch between environments easily: ```bash theme={null} #!/bin/bash # env-switch.sh - Switch CLI environment case $1 in staging) nvm config set activeProfile staging nvm config set environment sandbox echo "Switched to staging environment" ;; production) nvm config set activeProfile production nvm config set environment live echo "Switched to production environment" ;; sandbox) nvm config set activeProfile sandbox nvm config set environment sandbox echo "Switched to sandbox environment" ;; *) echo "Usage: $0 {staging|production|sandbox}" exit 1 ;; esac nvm config show ``` Usage: ```bash theme={null} ./env-switch.sh staging ./env-switch.sh production ``` ### Backup Configuration Backup your CLI configuration: ```bash theme={null} #!/bin/bash # backup-config.sh CONFIG_FILE=~/.config/nvm/config.json BACKUP_DIR=~/.config/nvm/backups BACKUP_FILE=$BACKUP_DIR/config-$(date +%Y%m%d-%H%M%S).json mkdir -p $BACKUP_DIR cp $CONFIG_FILE $BACKUP_FILE echo "Configuration backed up to: $BACKUP_FILE" ``` ### Restore Configuration Restore from backup: ```bash theme={null} #!/bin/bash # restore-config.sh BACKUP_DIR=~/.config/nvm/backups CONFIG_FILE=~/.config/nvm/config.json # List available backups echo "Available backups:" ls -1 $BACKUP_DIR # Prompt for backup to restore read -p "Enter backup filename: " BACKUP_NAME if [ -f "$BACKUP_DIR/$BACKUP_NAME" ]; then cp "$BACKUP_DIR/$BACKUP_NAME" $CONFIG_FILE echo "Configuration restored from: $BACKUP_NAME" nvm config show else echo "Backup file not found" exit 1 fi ``` ### Multi-Profile Operations Run commands across multiple profiles: ```bash theme={null} #!/bin/bash # multi-profile.sh - Run command across all profiles PROFILES=("staging" "production" "sandbox") COMMAND="$@" for PROFILE in "${PROFILES[@]}"; do echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Profile: $PROFILE" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" nvm --profile $PROFILE $COMMAND echo " done ``` Usage: ```bash theme={null} ./multi-profile.sh plans list ./multi-profile.sh agents list ``` ## Integration Examples ### CI/CD Pipeline Use CLI in CI/CD: ```yaml theme={null} # .github/workflows/deploy-agent.yml name: Deploy Agent on: push: tags: - 'v*' jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install CLI run: npm install -g @nevermined-io/cli - name: Configure CLI run: | nvm config set nvmApiKey ${{ secrets.NVM_API_KEY }} nvm config set environment live - name: Register Agent run: | nvm agents register-agent \ --agent-metadata agent.json \ --agent-api https://api.example.com \ --payment-plans ${{ secrets.PLAN_ID }} - name: Verify Agent run: | AGENT_ID=$(cat agent-id.txt) nvm agents get-agent $AGENT_ID ``` ### Monitoring Script Monitor your resources: ```bash theme={null} #!/bin/bash # monitor.sh - Monitor plans, agents, and balances echo "Nevermined Resources Monitor" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Configuration echo "Current Configuration:" nvm config show echo " # Plans echo "Active Plans:" nvm plans list echo " # Agents echo "Registered Agents:" nvm agents list echo " # Balances echo "Plan Balances:" PLANS=$(nvm plans list --format json | jq -r '.[].id') for PLAN in $PLANS; do echo " $PLAN:" nvm plans get-plan-balance $PLAN | grep "Credits" done ``` ## Best Practices ### 1. Use Profiles for Environments Separate configurations for different environments: ```bash theme={null} # Development nvm --profile dev plans list # Production nvm --profile prod plans list ``` ### 2. Secure Your API Keys Never commit API keys to version control: ```bash theme={null} # Use environment variables export NVM_API_KEY=sandbox:eyJxxxxaaaa...bbbbbbbb # Or secure config files chmod 600 ~/.config/nvm/config.json ``` ### 3. Automate Repetitive Tasks Create scripts for common operations: ```bash theme={null} # Daily backup 0 0 * * * /path/to/backup-config.sh # Weekly report 0 9 * * 1 /path/to/monitor.sh | mail -s "Weekly Report" you@example.com ``` ### 4. Use JSON Output for Scripting Always use `--format json` in scripts: ```bash theme={null} PLAN_DATA=$(nvm plans get-plan "123456789012345678" --format json) PLAN_NAME=$(echo $PLAN_DATA | jq -r '.name') ``` ## Common Issues ### "Config file not found" Initialize configuration: ```bash theme={null} nvm config init ``` ### "Invalid profile" Check available profiles: ```bash theme={null} cat ~/.config/nvm/config.json | jq '.profiles | keys' ``` ### "Permission denied" Fix config file permissions: ```bash theme={null} chmod 600 ~/.config/nvm/config.json ``` ## Next Steps * [Getting Started](./getting-started.md) - Installation and setup * [Plans](./plans.md) - Create and manage plans * [Agents](./agents.md) - Register and manage agents * [Purchases](./purchases.md) - Order plans and manage credits * [Querying](./querying.md) - Query agents with X402 tokens # Managing Payment Plans Source: https://docs.nevermined.app/docs/api-reference/cli/plans Complete guide to creating and managing payment plans with the Nevermined CLI # Managing Payment Plans Complete guide to creating and managing payment plans with the Nevermined CLI. ## Overview Payment plans define how users can access your AI agents and services. Plans can be: * **Credits-based**: Pay per API call or credit consumed * **Time-based**: Subscription access for a duration * **Service-based**: Access to specific services ## Listing Plans View all available payment plans: ```bash theme={null} # Table output (default) nvm plans list # JSON output for scripting nvm plans list --format json # Quiet output (IDs only) nvm plans list --format quiet ``` Example output: ``` Payment Plans ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan ID Name Type Price ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ "123456789012345678" Basic Plan Credits $10.00 "234567890123456789" Pro Plan Credits $50.00 "345678901234567890" Enterprise Time $100.00 ``` ## Getting Plan Details Retrieve detailed information about a specific plan: ```bash theme={null} nvm plans get-plan ``` Example: ```bash theme={null} nvm plans get-plan "123456789012345678" ``` Output includes: * Plan metadata (name, description, creator) * Pricing configuration * Credits configuration * Service details * Payment token information ## Checking Plan Balance Check your credit balance for a plan: ```bash theme={null} # Your balance nvm plans get-plan-balance # Check balance for specific address nvm plans get-plan-balance --account-address 0x1234... ``` Example: ```bash theme={null} nvm plans get-plan-balance "123456789012345678" ``` Output: ``` Plan Balance ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Credits Remaining: 1000 Plan ID: "123456789012345678" Account: 0xYourAddress... ``` ## Creating Plans ### Credits Plan Create a pay-per-use plan with credits: ```bash theme={null} nvm plans register-credits-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` **plan-metadata.json**: ```json theme={null} { "name": "My AI Agent - Basic Plan", "description": "100 credits for API access", "tags": ["ai", "assistant", "basic"], "customData": { "category": "AI Assistant" } } ``` **price-config.json**: ```json theme={null} { "tokenAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "price": 1000000, "amountOfCredits": 100 } ``` **credits-config.json**: ```json theme={null} { "subscriptionType": "credits", "accessType": "credits", "creditType": "api_calls", "minCreditsRequired": 1, "minCreditsToCharge": 1, "maxCreditsToCharge": 10 } ``` ### Time-Based Plan Create a subscription plan with time-limited access: ```bash theme={null} nvm plans register-time-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --time-config time-config.json ``` **time-config.json**: ```json theme={null} { "subscriptionType": "time", "accessType": "time", "duration": 2592000 } ``` Duration is in seconds (2592000 = 30 days). ### Service Plan Create a plan for accessing specific AI services: ```bash theme={null} nvm plans register-service-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --service-config service-config.json ``` **service-config.json**: ```json theme={null} { "subscriptionType": "service", "accessType": "service", "serviceHost": "https://api.example.com", "nftTransfer": false, "nftHolder": false } ``` ## Updating Plans ### Update Plan Metadata Modify plan name, description, or tags: ```bash theme={null} nvm plans update-plan-metadata \ --plan-metadata updated-metadata.json ``` **updated-metadata.json**: ```json theme={null} { "name": "My AI Agent - Premium Plan", "description": "Upgraded with more features", "tags": ["ai", "assistant", "premium"] } ``` ### Update Plan Price Change plan pricing: ```bash theme={null} nvm plans update-plan-price \ --price-config new-price.json ``` **new-price.json**: ```json theme={null} { "tokenAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "price": 2000000, "amountOfCredits": 200 } ``` ## Plan Files and Transfers ### Download Plan Files Download files associated with a plan (if it includes static resources): ```bash theme={null} nvm plans download-plan-files --destination ./downloads ``` ### Transfer Plan Ownership Transfer plan ownership to another address: ```bash theme={null} nvm plans transfer-plan-ownership \ --new-owner 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb ``` ## Advanced Plan Operations ### Get Plan Dependencies List other plans or services this plan depends on: ```bash theme={null} nvm plans get-plan-dependencies ``` ### Register File Plan Create a plan with downloadable files: ```bash theme={null} nvm plans register-file-plan \ --plan-metadata metadata.json \ --price-config price.json \ --file-urls "https://example.com/file1.pdf,https://example.com/file2.zip" ``` ### Mint Credits Add credits to an existing plan (plan owner only): ```bash theme={null} nvm plans mint-credits \ --receiver-address 0x123... \ --amount 1000 ``` ### Burn Credits Remove credits from a plan: ```bash theme={null} nvm plans burn-credits \ --amount 100 ``` ## JSON Output for Scripting Use `--format json` for machine-readable output: ```bash theme={null} # Get plan details as JSON PLAN_DATA=$(nvm plans get-plan "123456789012345678" --format json) # Extract specific field with jq PLAN_NAME=$(echo $PLAN_DATA | jq -r '.name') PLAN_PRICE=$(echo $PLAN_DATA | jq -r '.price') echo "Plan: $PLAN_NAME costs $PLAN_PRICE" ``` ## Examples ### Example 1: Create a Basic AI Agent Plan ```bash theme={null} # 1. Create plan metadata file cat > plan.json << EOF { "name": "ChatBot Basic", "description": "100 API calls for $10", "tags": ["chatbot", "ai"] } EOF # 2. Create price configuration cat > price.json << EOF { "tokenAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "price": 10000000, "amountOfCredits": 100 } EOF # 3. Create credits configuration cat > credits.json << EOF { "subscriptionType": "credits", "accessType": "credits", "creditType": "api_calls", "minCreditsRequired": 1, "minCreditsToCharge": 1, "maxCreditsToCharge": 5 } EOF # 4. Register the plan nvm plans register-credits-plan \ --plan-metadata plan.json \ --price-config price.json \ --credits-config credits.json ``` ### Example 2: List All Plans and Get First Plan Details ```bash theme={null} # Get all plans as JSON PLANS=$(nvm plans list --format json) # Extract first plan ID FIRST_PLAN=$(echo $PLANS | jq -r '.[0].id') # Get detailed info nvm plans get-plan $FIRST_PLAN ``` ### Example 3: Monitor Plan Balance ```bash theme={null} #!/bin/bash # Script to monitor plan balance PLAN_ID="123456789012345678" MIN_CREDITS=10 BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "$MIN_CREDITS" ]; then echo "⚠️ Low balance: $BALANCE credits remaining" echo "Consider ordering more credits" else echo "✅ Balance healthy: $BALANCE credits" fi ``` ## Best Practices ### 1. Use Descriptive Metadata Make your plans discoverable with clear names and descriptions: ```json theme={null} { "name": "GPT-4 API Access - Starter", "description": "1000 GPT-4 API calls per month with 24/7 support", "tags": ["gpt4", "ai", "api", "starter"] } ``` ### 2. Set Appropriate Credit Limits Configure min/max credits to prevent abuse: ```json theme={null} { "minCreditsRequired": 1, "minCreditsToCharge": 1, "maxCreditsToCharge": 10 } ``` ### 3. Store Configuration Files Keep plan configurations in version control: ``` plans/ ├── basic-plan/ │ ├── metadata.json │ ├── price.json │ └── credits.json ├── pro-plan/ │ ├── metadata.json │ ├── price.json │ └── credits.json └── enterprise-plan/ ├── metadata.json ├── price.json └── time.json ``` ### 4. Test in Staging First Always test new plans in sandbox before production: ```bash theme={null} # Test in staging nvm --profile staging plans register-credits-plan \ --plan-metadata metadata.json \ --price-config price.json \ --credits-config credits.json # Verify it works nvm --profile staging plans get-plan # Then deploy to production nvm --profile production plans register-credits-plan \ --plan-metadata metadata.json \ --price-config price.json \ --credits-config credits.json ``` ## Common Issues ### "Insufficient balance" When registering plans, ensure you have enough credits or tokens in your account. ### "Invalid token address" Verify the payment token address matches your environment: * Staging: Use test token addresses * Production: Use mainnet token addresses ### "Plan not found" Ensure you're using the correct environment and plan ID: ```bash theme={null} # Check which environment you're using nvm config show # Try the correct profile nvm --profile staging plans get-plan ``` ## Next Steps * [Agents](./agents.md) - Register agents with your plans * [Purchases](./purchases.md) - Order plans and make payments * [Querying](./querying.md) - Query agents with access tokens # Making Purchases Source: https://docs.nevermined.app/docs/api-reference/cli/purchases Complete guide to ordering payment plans and managing subscriptions with the Nevermined CLI # Making Purchases Complete guide to ordering payment plans and managing subscriptions with the Nevermined CLI. ## Overview Purchasing a payment plan gives you access to AI agents and services. Once you order a plan, you receive credits or time-based access that you can use to query agents. ## Ordering a Plan ### Basic Plan Order Purchase a payment plan: ```bash theme={null} nvm plans order-plan ``` Example: ```bash theme={null} nvm plans order-plan "123456789012345678" ``` Output: ``` Plan Order Success ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan ID: "123456789012345678" Credits Purchased: 100 Transaction Hash: 0x1234567890abcdef... Status: Confirmed ✨ Next steps: 1. Get your X402 access token: nvm x402token get-x402-access-token "123456789012345678" 2. Use the token to query the agent 3. Check your balance: nvm plans get-plan-balance "123456789012345678" ``` ### Order with Specific Payment Method Specify payment method or options: ```bash theme={null} nvm plans order-plan \ --payment-method "credit-card" \ --payment-data payment.json ``` **payment.json**: ```json theme={null} { "tokenAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "amount": 1000000, "billingAddress": { "country": "US", "zip": "12345" } } ``` ## Checking Your Credits ### View Plan Balance Check how many credits you have remaining: ```bash theme={null} # Your balance nvm plans get-plan-balance # Balance for specific address nvm plans get-plan-balance --account-address 0x123... ``` Example output: ``` Plan Balance ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan ID: "123456789012345678" Plan Name: Basic Plan Credits Remaining: 75 Credits Used: 25 Total Credits: 100 Last Updated: 2024-03-15 10:30:00 UTC ``` ### Monitor Multiple Plans Check balances for all your plans: ```bash theme={null} #!/bin/bash # Check all plan balances # Get all plans you've ordered (from transaction history or saved list) PLANS=(""111111111111111111"" ""222222222222222222"" ""333333333333333333"") for PLAN in "${PLANS[@]}"; do echo "Checking balance for $PLAN..." nvm plans get-plan-balance $PLAN echo " done ``` ## Managing Subscriptions ### View Active Subscriptions List all your active subscriptions: ```bash theme={null} nvm plans list --filter active ``` ### Subscription Details Get detailed information about a specific subscription: ```bash theme={null} nvm plans get-plan ``` Output includes: * Subscription status (active, expired, cancelled) * Expiration date (for time-based plans) * Remaining credits (for credits-based plans) * Auto-renewal settings * Payment history ## Refilling Credits ### Purchase Additional Credits Add more credits to an existing plan: ```bash theme={null} nvm plans order-plan ``` This adds credits to your existing balance rather than creating a new subscription. ### Bulk Credit Purchase Order multiple plans at once: ```bash theme={null} #!/bin/bash # Purchase credits for multiple plans PLANS=(""111111111111111111"" ""222222222222222222"") for PLAN in "${PLANS[@]}"; do echo "Ordering $PLAN..." nvm plans order-plan $PLAN done ``` ## Transaction History ### View Purchase History See your past purchases: ```bash theme={null} nvm plans list-purchases ``` Example output: ``` Purchase History ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Date Plan ID Credits Amount Status ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2024-03-15 10:30 "123456789012345678" 100 $10.00 Confirmed 2024-03-10 14:20 "234567890123456789" 500 $50.00 Confirmed 2024-03-05 09:15 "345678901234567890" 1000 $90.00 Confirmed ``` ### Export Transaction Data Export purchase history as JSON: ```bash theme={null} nvm plans list-purchases --format json > purchases.json ``` Use with analytics tools: ```bash theme={null} # Get total spending TOTAL=$(nvm plans list-purchases --format json | jq '[.[] | .amount] | add') echo "Total spent: \$$TOTAL" # Count purchases by plan nvm plans list-purchases --format json | jq 'group_by(.planId) | map({plan: .[0].planId, count: length})' ``` ## Credit Usage Analytics ### Track Credit Consumption Monitor how your credits are being used: ```bash theme={null} nvm plans get-credit-usage ``` Example output: ``` Credit Usage Report ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan: Basic Plan ("123456789012345678") Period: Last 30 days Total Credits Used: 250 Average per Day: 8.3 Peak Day: 35 (March 10) Usage by Service: - Agent Queries: 200 credits (80%) - File Downloads: 30 credits (12%) - API Calls: 20 credits (8%) ``` ### Set Usage Alerts Create a script to alert when credits run low: ```bash theme={null} #!/bin/bash # Credit monitoring script PLAN_ID="123456789012345678" MIN_CREDITS=10 EMAIL="your@email.com" BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "$MIN_CREDITS" ]; then echo "⚠️ LOW CREDITS ALERT" echo "Plan: $PLAN_ID" echo "Remaining: $BALANCE credits" echo "Threshold: $MIN_CREDITS credits" # Send email or notification # mail -s "Low Credits Alert" $EMAIL <<< "Only $BALANCE credits remaining" # Optionally auto-refill # nvm plans order-plan $PLAN_ID fi ``` ## Automated Purchases ### Auto-Refill Script Automatically refill credits when low: ```bash theme={null} #!/bin/bash # Auto-refill credits script # Run as cron job: 0 */6 * * * /path/to/auto-refill.sh PLAN_ID="123456789012345678" MIN_CREDITS=20 REFILL_AMOUNT=100 BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "$MIN_CREDITS" ]; then echo "$(date): Credits low ($BALANCE), initiating refill..." # Order more credits RESULT=$(nvm plans order-plan $PLAN_ID --format json) if [ $? -eq 0 ]; then echo "$(date): Successfully ordered $REFILL_AMOUNT credits" NEW_BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') echo "$(date): New balance: $NEW_BALANCE credits" else echo "$(date): ERROR - Failed to order credits" echo "$RESULT" fi else echo "$(date): Balance healthy: $BALANCE credits" fi ``` ### Subscription Renewal For time-based plans, set up auto-renewal: ```bash theme={null} #!/bin/bash # Check and renew expiring subscriptions PLANS=(""111111111111111111"" ""222222222222222222"") DAYS_BEFORE_EXPIRY=7 for PLAN in "${PLANS[@]}"; do DETAILS=$(nvm plans get-plan $PLAN --format json) EXPIRY=$(echo $DETAILS | jq -r '.expiryDate') # Calculate days until expiry DAYS_LEFT=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 )) if [ "$DAYS_LEFT" -le "$DAYS_BEFORE_EXPIRY" ]; then echo "Plan $PLAN expires in $DAYS_LEFT days. Renewing..." nvm plans order-plan $PLAN else echo "Plan $PLAN: $DAYS_LEFT days remaining" fi done ``` ## Payment Methods ### Crypto Payments Pay with cryptocurrency (default): ```bash theme={null} nvm plans order-plan ``` Supported tokens: * USDC * ETH * Custom ERC-20 tokens ### Credit Card Payments Pay with credit card through payment gateway: ```bash theme={null} nvm plans order-plan \ --payment-method "card" \ --card-data card.json ``` **card.json** (processed securely): ```json theme={null} { "gateway": "stripe", "successUrl": "https://yourapp.com/success", "cancelUrl": "https://yourapp.com/cancel" } ``` ## Budgeting and Cost Control ### Set Monthly Budget Track spending against budget: ```bash theme={null} #!/bin/bash # Monthly budget tracker MONTHLY_BUDGET=100 CURRENT_MONTH=$(date +%Y-%m) # Get all purchases for current month PURCHASES=$(nvm plans list-purchases --format json | jq --arg month "$CURRENT_MONTH" '[.[] | select(.date | startswith($month))]') TOTAL_SPENT=$(echo $PURCHASES | jq '[.[] | .amount] | add') echo "Monthly Budget: \$$MONTHLY_BUDGET" echo "Spent This Month: \$$TOTAL_SPENT" REMAINING=$(echo "$MONTHLY_BUDGET - $TOTAL_SPENT" | bc) echo "Remaining: \$$REMAINING" if (( $(echo "$TOTAL_SPENT > $MONTHLY_BUDGET" | bc -l) )); then echo "⚠️ Budget exceeded!" fi ``` ### Cost Optimization Analyze which plans provide best value: ```bash theme={null} #!/bin/bash # Calculate cost per credit for each plan PLANS=$(nvm plans list --format json) echo "Plan Cost Analysis" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo $PLANS | jq -r '.[] | "\(.name): $\(.price / .credits | . * 100 | round / 100) per credit (Total: $\(.price) for \(.credits) credits)"' ``` ## Examples ### Example 1: Complete Purchase Flow ```bash theme={null} #!/bin/bash # Complete purchase workflow # 1. Browse available plans echo "Available Plans:" nvm plans list # 2. Get details about a specific plan PLAN_ID="123456789012345678" nvm plans get-plan $PLAN_ID # 3. Check current balance echo "Current balance:" nvm plans get-plan-balance $PLAN_ID # 4. Purchase the plan echo "Purchasing plan..." nvm plans order-plan $PLAN_ID # 5. Verify purchase echo "New balance:" nvm plans get-plan-balance $PLAN_ID # 6. Get access token for using the service echo "Getting access token..." nvm x402token get-x402-access-token $PLAN_ID ``` ### Example 2: Multi-Tier Purchase ```bash theme={null} #!/bin/bash # Purchase different tiers based on usage needs # Light user: Basic plan nvm plans order-plan "111111111111111111" # Power user: Pro plan nvm plans order-plan "222222222222222222" # Enterprise: Custom plan nvm plans order-plan "333333333333333333" # Check all balances echo "Current Balances:" nvm plans get-plan-balance "111111111111111111" nvm plans get-plan-balance "222222222222222222" nvm plans get-plan-balance "333333333333333333" ``` ### Example 3: Team Purchase Management ```bash theme={null} #!/bin/bash # Manage purchases for a team TEAM_PLANS=( ""111111111111111111" ""222222222222222222" ""333333333333333333" ) # Purchase for entire team for PLAN in "${TEAM_PLANS[@]}"; do echo "Ordering $PLAN for team..." nvm plans order-plan $PLAN done # Generate team usage report echo "Team Usage Report" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" for PLAN in "${TEAM_PLANS[@]}"; do BALANCE=$(nvm plans get-plan-balance $PLAN --format json) echo $BALANCE | jq '{plan: .planId, remaining: .balance, used: .used}' done ``` ## Best Practices ### 1. Monitor Credit Levels Set up monitoring to avoid service interruptions: ```bash theme={null} # Daily cron job 0 9 * * * /path/to/check-credits.sh ``` ### 2. Plan Ahead Purchase credits before you need them to avoid delays. ### 3. Use Automation Implement auto-refill to ensure continuous service: ```bash theme={null} # Check every 6 hours 0 */6 * * * /path/to/auto-refill.sh ``` ### 4. Track Spending Regularly review purchase history: ```bash theme={null} # Weekly spending report nvm plans list-purchases --format json | \ jq 'group_by(.date | split("T")[0]) | map({date: .[0].date, total: map(.amount) | add})' ``` ### 5. Optimize Costs Choose plans that match your usage patterns: ```bash theme={null} # Compare cost efficiency nvm plans list --format json | \ jq 'map({name, costPerCredit: (.price / .credits)}) | sort_by(.costPerCredit)' ``` ## Common Issues ### "Insufficient funds" Ensure your wallet has enough balance: ```bash theme={null} # Check wallet balance nvm wallet get-balance # Fund your wallet first ``` ### "Plan not available" The plan may be sold out or no longer active: ```bash theme={null} # Verify plan status nvm plans get-plan # Look for alternative plans nvm plans list ``` ### "Transaction failed" Network issues or gas price problems: ```bash theme={null} # Retry the purchase nvm plans order-plan # Check transaction status nvm plans get-transaction-status ``` ## Next Steps * [Querying](./querying.md) - Use your credits to query agents * [Plans](./plans.md) - Learn about different plan types * [Agents](./agents.md) - Discover available agents # Querying Agents Source: https://docs.nevermined.app/docs/api-reference/cli/querying Complete guide to accessing AI agents using X402 access tokens with the Nevermined CLI # Querying Agents Complete guide to accessing AI agents using X402 access tokens with the Nevermined CLI. ## Overview After purchasing a payment plan, you can query AI agents using X402 access tokens. The X402 protocol enables pay-per-use access with automatic credit deduction. ## Getting an Access Token ### Generate X402 Token Get an access token for a purchased plan: ```bash theme={null} nvm x402token get-x402-access-token ``` Example: ```bash theme={null} nvm x402token get-x402-access-token "123456789012345678" ``` Output: ``` X402 Access Token ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Plan ID: "123456789012345678" Expires: 2024-03-15 10:30:00 UTC Credits: 100 Use this token in the X-402 header: curl -H "X-402: eyJhbGci..." https://agent-api.example.com ``` ### Token Format The token is a JWT containing: * Plan ID * Subscriber address * Credits information * Signature for verification ### Save Token for Reuse Store the token for multiple requests: ```bash theme={null} # Get token and save to file TOKEN=$(nvm x402token get-x402-access-token "123456789012345678" --format json | jq -r '.token') echo $TOKEN > ~/.nvm-token # Use token from file curl -H "X-402: $(cat ~/.nvm-token)" https://agent-api.example.com/query ``` ## Using X402 Tokens ### HTTP Requests with curl Query an agent using curl: ```bash theme={null} # Get access token TOKEN=$(nvm x402token get-x402-access-token "123456789012345678" --format json | jq -r '.token') # Make request with X-402 header curl -X POST https://agent-api.example.com/v1/chat \ -H "Content-Type: application/json" \ -H "X-402: $TOKEN" \ -d '{ "message": "Hello, AI assistant!", "temperature": 0.7 }' ``` ### With JavaScript/TypeScript ```javascript theme={null} // Get token from CLI const { execSync } = require('child_process') const planId = '"123456789012345678"' const tokenCmd = `nvm x402token get-x402-access-token ${planId} --format json` const result = JSON.parse(execSync(tokenCmd).toString()) const token = result.token // Make API request const response = await fetch('https://agent-api.example.com/v1/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-402': token }, body: JSON.stringify({ message: 'Hello, AI assistant!', temperature: 0.7 }) }) const data = await response.json() console.log(data) ``` ### With Python ```python theme={null} import subprocess import json import requests # Get token from CLI plan_id = '"123456789012345678"' cmd = f'nvm x402token get-x402-access-token {plan_id} --format json' result = subprocess.run(cmd.split(), capture_output=True, text=True) token_data = json.loads(result.stdout) token = token_data['token'] # Make API request response = requests.post( 'https://agent-api.example.com/v1/chat', headers={ 'Content-Type': 'application/json', 'X-402': token }, json={ 'message': 'Hello, AI assistant!', 'temperature': 0.7 } ) print(response.json()) ``` ## Verifying Requests ### Manual Verification Verify that a request is valid before processing (for agent owners): ```bash theme={null} nvm facilitator verify-permissions \ --params verify.json ``` **verify.json**: ```json theme={null} { "planId": ""123456789012345678"", "token": "eyJhbGci...", "creditsRequired": 5 } ``` Output: ``` Permission Verification ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Status: Valid Plan ID: "123456789012345678" Subscriber: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb Credits Available: 100 Credits Required: 5 Can Proceed: true ``` ### Automatic Verification (SDK) For agent implementations, use the SDK for automatic verification: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) // Verify incoming request const isValid = await payments.facilitator.verifyPermissions({ planId: '"123456789012345678"', token: req.headers['x-402'], creditsRequired: 5 }) if (!isValid) { return res.status(402).json({ error: 'Payment required' }) } ``` ## Settling Credits ### Burn Credits After Use After processing a request, burn the credits (agent owners): ```bash theme={null} nvm facilitator settle-permissions \ --params settle.json ``` **settle.json**: ```json theme={null} { "planId": ""123456789012345678"", "token": "eyJhbGci...", "creditsBurned": 5, "executionId": "exec-123" } ``` Output: ``` Credits Settled ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Plan ID: "123456789012345678" Credits Burned: 5 Remaining Balance: 95 Transaction Hash: 0x1234567890abcdef... Status: Confirmed ``` ### Automatic Settlement (SDK) Integrate settlement into your agent: ```typescript theme={null} // After processing request await payments.facilitator.settlePermissions({ planId: '"123456789012345678"', token: req.headers['x-402'], creditsBurned: 5, executionId: requestId }) ``` ## Complete Query Workflow ### End-to-End Example Complete flow from purchase to query: ```bash theme={null} #!/bin/bash # Complete agent query workflow PLAN_ID="123456789012345678" AGENT_API="https://agent-api.example.com/v1/chat" # 1. Purchase plan if needed echo "Checking balance..." BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "10" ]; then echo "Low balance, purchasing plan..." nvm plans order-plan $PLAN_ID fi # 2. Get X402 access token echo "Getting access token..." TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') # 3. Query the agent echo "Querying agent..." RESPONSE=$(curl -s -X POST $AGENT_API \ -H "Content-Type: application/json" \ -H "X-402: $TOKEN" \ -d '{ "message": "Explain quantum computing in simple terms", "temperature": 0.7 }') echo "Response:" echo $RESPONSE | jq '.' # 4. Check updated balance echo "Updated balance:" nvm plans get-plan-balance $PLAN_ID ``` ## Advanced Usage ### Batch Queries Process multiple queries efficiently: ```bash theme={null} #!/bin/bash # Batch query script PLAN_ID="123456789012345678" AGENT_API="https://agent-api.example.com/v1/chat" # Get token once TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') # Array of questions QUESTIONS=( "What is machine learning?" "Explain neural networks" "What is deep learning?" ) # Query each for QUESTION in "${QUESTIONS[@]}"; do echo "Query: $QUESTION" RESPONSE=$(curl -s -X POST $AGENT_API \ -H "Content-Type: application/json" \ -H "X-402: $TOKEN" \ -d "{\"message\": \"$QUESTION\"}") echo "Answer: $(echo $RESPONSE | jq -r '.answer')" echo "Credits used: $(echo $RESPONSE | jq -r '.creditsUsed')" echo "---" done # Check final balance nvm plans get-plan-balance $PLAN_ID ``` ### Rate-Limited Queries Respect rate limits: ```bash theme={null} #!/bin/bash # Rate-limited query script PLAN_ID="123456789012345678" AGENT_API="https://agent-api.example.com/v1/chat" RATE_LIMIT=10 # requests per minute DELAY=$(echo "60 / $RATE_LIMIT" | bc -l) TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') for i in {1..50}; do echo "Request $i..." curl -s -X POST $AGENT_API \ -H "Content-Type: application/json" \ -H "X-402: $TOKEN" \ -d "{\"message\": \"Query $i\"}" # Respect rate limit sleep $DELAY done ``` ### Error Handling Handle common errors gracefully: ```bash theme={null} #!/bin/bash # Robust query script with error handling query_agent() { local plan_id=$1 local message=$2 local max_retries=3 local retry_count=0 while [ $retry_count -lt $max_retries ]; do # Get fresh token TOKEN=$(nvm x402token get-x402-access-token $plan_id --format json 2>&1) if [ $? -ne 0 ]; then echo "Error getting token: $TOKEN" return 1 fi TOKEN=$(echo $TOKEN | jq -r '.token') # Make request RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ https://agent-api.example.com/v1/chat \ -H "Content-Type: application/json" \ -H "X-402: $TOKEN" \ -d "{\"message\": \"$message\"}") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | head -n-1) case $HTTP_CODE in 200) echo "$BODY" return 0 ;; 402) echo "Insufficient credits. Purchasing more..." nvm plans order-plan $plan_id ;; 429) echo "Rate limit exceeded. Waiting..." sleep 60 ;; *) echo "Error $HTTP_CODE: $BODY" ;; esac retry_count=$((retry_count + 1)) sleep 2 done echo "Max retries exceeded" return 1 } # Usage query_agent ""123456789012345678"" "Hello, agent!" ``` ## Monitoring Usage ### Track Credit Consumption Monitor how credits are used per query: ```bash theme={null} #!/bin/bash # Credit usage tracking PLAN_ID="123456789012345678" LOG_FILE="credit-usage.log" # Get initial balance INITIAL=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') # Make query TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') RESPONSE=$(curl -s -X POST https://agent-api.example.com/v1/chat \ -H "X-402: $TOKEN" \ -d '{"message": "test query"}') # Get new balance FINAL=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') # Calculate usage USED=$((INITIAL - FINAL)) # Log echo "$(date)|Query: test query|Credits used: $USED|Remaining: $FINAL" >> $LOG_FILE echo "Credits used: $USED" echo "Remaining: $FINAL" ``` ### Usage Reports Generate usage reports: ```bash theme={null} #!/bin/bash # Generate usage report from logs echo "Credit Usage Report" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Total credits used TOTAL_USED=$(awk -F'|' '{sum+=$3} END {print sum}' credit-usage.log | cut -d: -f2) echo "Total Credits Used: $TOTAL_USED" # Average per query NUM_QUERIES=$(wc -l < credit-usage.log) AVG=$(echo "$TOTAL_USED / $NUM_QUERIES" | bc -l | xargs printf "%.2f") echo "Average per Query: $AVG" # Queries by date echo " echo "Queries by Date:" awk -F'|' '{print substr($1, 1, 10)}' credit-usage.log | sort | uniq -c ``` ## Best Practices ### 1. Token Refresh Tokens may expire, always get fresh tokens for new sessions: ```bash theme={null} # Don't reuse old tokens TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') ``` ### 2. Credit Management Monitor balance before queries: ```bash theme={null} BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "10" ]; then echo "Low credits, refilling..." nvm plans order-plan $PLAN_ID fi ``` ### 3. Error Recovery Implement retries with exponential backoff: ```bash theme={null} retry_with_backoff() { local max_attempts=5 local timeout=1 local attempt=1 while [ $attempt -le $max_attempts ]; do if "$@"; then return 0 fi echo "Attempt $attempt failed. Retrying in ${timeout}s..." sleep $timeout timeout=$((timeout * 2)) attempt=$((attempt + 1)) done return 1 } ``` ### 4. Secure Token Storage Never commit tokens to version control: ```bash theme={null} # Use environment variables export X402_TOKEN=$(nvm x402token get-x402-access-token $PLAN_ID --format json | jq -r '.token') # Or secure file storage chmod 600 ~/.nvm-token ``` ### 5. Rate Limiting Respect API rate limits to avoid 429 errors: ```bash theme={null} # Add delays between requests sleep 0.1 # 100ms between requests ``` ## Common Issues ### "Invalid token" Token may be expired or malformed: ```bash theme={null} # Get a fresh token nvm x402token get-x402-access-token $PLAN_ID ``` ### "Insufficient credits" Your balance is too low: ```bash theme={null} # Check balance nvm plans get-plan-balance $PLAN_ID # Purchase more nvm plans order-plan $PLAN_ID ``` ### "402 Payment Required" The agent requires payment but token is invalid or missing: ```bash theme={null} # Ensure X-402 header is included curl -H "X-402: $TOKEN" https://agent-api.example.com ``` ## Next Steps * [Other Commands](./other-commands.md) - Additional CLI features * [Purchases](./purchases.md) - Managing your credits * [Agents](./agents.md) - Discover more agents # Pre-requisites Source: https://docs.nevermined.app/docs/api-reference/introduction Get your Nevermined API Key to use the Payment libraries and REST API To interact with Nevermined programmatically, you need a **Nevermined API Key**. This key is required for both: * **Payment Libraries** (TypeScript SDK and Python SDK) * **REST API** (direct HTTP calls) ## Get Your API Key Go to [nevermined.app](https://nevermined.app) and sign in with your account. Go to **Settings > API Keys** in your dashboard. Click **Generate** and copy your new API key. It starts with `nvm:`. ```bash theme={null} export NVM_API_KEY="nvm:your-api-key-here" ``` Keep your API key secure. Do not commit it to version control or share it publicly. For a complete walkthrough, see our [5-Minute Setup Guide](/docs/integrate/quickstart/5-minute-setup). ## Choose Your Environment Nevermined offers two environments: | Environment | Purpose | Network | | ----------- | ----------------------- | ------------ | | **Sandbox** | Testing and development | Base Sepolia | | **Live** | Production | Base Mainnet | ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY, environment: 'sandbox' // or 'live' }) ``` ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox' # or 'live' ) ) ``` | Environment | Base URL | | ----------- | ------------------------------------------- | | **Sandbox** | `https://api.sandbox.nevermined.app/api/v1` | | **Live** | `https://api.live.nevermined.app/api/v1` | ```bash theme={null} curl -X GET "https://api.sandbox.nevermined.app/api/v1/protocol/agents" \ -H "Authorization: Bearer $NVM_API_KEY" ``` ## Next Steps Complete integration walkthrough. Make direct HTTP calls to the Nevermined API. Install and use the TypeScript Payment library. Install and use the Python Payment library. # Add Plan to Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/add-plan-to-agent POST /protocol/agents/{agentId}/plan/{planId} Associates an existing payment plan with an agent. # Get Access Token Source: https://docs.nevermined.app/docs/api-reference/protocol/get-access-token GET /protocol/token/{planId}/{agentId} Generates access credentials for agent-plan interaction. # Get Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/get-agent GET /protocol/agents/{agentId} Retrieves agent configuration and associated plans. # Get Agent Plans Source: https://docs.nevermined.app/docs/api-reference/protocol/get-agent-plans GET /protocol/agents/{agentId}/plans Lists all payment plans associated with an agent. # Get Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/get-plan GET /protocol/plans/{planId} Retrieves details about a specific payment plan. # Get Plan Agents Source: https://docs.nevermined.app/docs/api-reference/protocol/get-plan-agents GET /protocol/plans/{planId}/agents Lists all agents associated with a payment plan. # Get Plan Balance Source: https://docs.nevermined.app/docs/api-reference/protocol/get-plan-balance GET /protocol/plans/{planId}/balance/{holderAddress} Retrieves credit balance for a specific account on a plan. # Initialize Agent Request Source: https://docs.nevermined.app/docs/api-reference/protocol/initialize-agent-request POST /protocol/agents/initialize/{agentId} Validates access and initializes an agent request session. # List Agents Source: https://docs.nevermined.app/docs/api-reference/protocol/list-agents GET /protocol/agents Get paginated list of agents for the current user. # List Plans Source: https://docs.nevermined.app/docs/api-reference/protocol/list-plans GET /protocol/plans Get paginated list of payment plans for the current user. # Mint Credits Source: https://docs.nevermined.app/docs/api-reference/protocol/mint-credits POST /protocol/plans/mint Adds credits to a plan account. Builder-only operation. # Mint Expirable Credits Source: https://docs.nevermined.app/docs/api-reference/protocol/mint-expirable-credits POST /protocol/plans/mintExpirable Adds time-limited credits to a plan account. # Order Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/order-plan POST /protocol/plans/{planId}/order Purchases a payment plan. Credits are issued immediately. # Redeem Credits Source: https://docs.nevermined.app/docs/api-reference/protocol/redeem-credits POST /protocol/plans/redeem Consumes credits from a plan account after processing a request. # Register Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/register-agent POST /protocol/agents Creates a new AI agent with API configuration. # Register Agent and Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/register-agent-and-plan POST /protocol/agents/plans Creates both an agent and associated payment plan in a single request. # Register Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/register-plan POST /protocol/plans Creates a new payment plan with pricing and credit configuration. # Remove Plan from Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/remove-plan-from-agent DELETE /protocol/agents/{agentId}/plan/{planId} Disassociates a payment plan from an agent. # Activate/Deactivate Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/toggle-agent-listing PUT /protocol/agents/{agentId}/list/{activate} Controls the agent's public listing status. # Activate/Deactivate Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/toggle-plan-listing PUT /protocol/plans/{planId}/list/{activate} Controls the plan's public listing status. # Track Agent Sub Task Source: https://docs.nevermined.app/docs/api-reference/protocol/track-agent-sub-task POST /protocol/agent-sub-tasks Records task completion and credit consumption for complex agent workflows. # Update Agent Source: https://docs.nevermined.app/docs/api-reference/protocol/update-agent PUT /protocol/agents/{agentId} Modifies agent configuration and metadata. # Update Plan Source: https://docs.nevermined.app/docs/api-reference/protocol/update-plan PUT /protocol/plans/{planId} Modifies plan metadata. # A2A Integration Source: https://docs.nevermined.app/docs/api-reference/python/a2a-module Implement Agent-to-Agent protocol with payments This guide explains how to integrate the Nevermined Payments Python SDK with A2A (Agent-to-Agent) protocol servers. ## Overview A2A (Agent-to-Agent) is a protocol that enables AI agents to communicate with each other using JSON-RPC. The Nevermined SDK provides A2A integration to: * Build A2A servers with payment validation * Automatically verify x402 tokens on incoming requests * Handle credit redemption for agent tasks ## Payment Flow ```mermaid theme={null} sequenceDiagram participant Consumer as Consumer Agent participant Provider as Provider Agent participant NVM as Nevermined Consumer->>Provider: GET /.well-known/agent.json Provider-->>Consumer: Agent Card (planIds, agentId) Consumer->>NVM: order_plan(plan_id) NVM-->>Consumer: Order confirmed Consumer->>NVM: get_x402_access_token(plan_id, agent_id) NVM-->>Consumer: Access token Consumer->>Provider: POST / message/send (Bearer token) Provider->>NVM: verify_permissions(token) NVM-->>Provider: Valid Provider->>Provider: Execute agent logic Provider->>NVM: settle_permissions(token, credits) NVM-->>Provider: Credits burned Provider-->>Consumer: JSON-RPC response ``` ## Building Agent Cards ### With a Single Plan ```python theme={null} from payments_py.a2a import build_payment_agent_card agent_card = build_payment_agent_card( base_card={ "name": "Code Review Agent", "description": "Automated code review powered by AI", "url": "https://localhost:8080", "version": "1.0.0", "capabilities": { "streaming": False, "pushNotifications": False, }, }, payment_metadata={ "paymentType": "fixed", "credits": 1, "agentId": "your-agent-id", "planId": "your-plan-id", "costDescription": "1 credit per review", }, ) ``` ### With Multiple Plans When your agent supports multiple plans (e.g. a basic and a premium tier), use `planIds` instead of `planId`: ```python theme={null} agent_card = build_payment_agent_card( base_card={ "name": "Code Review Agent", "url": "https://localhost:8080", "version": "1.0.0", "capabilities": {}, }, payment_metadata={ "paymentType": "dynamic", "credits": 5, "agentId": "your-agent-id", "planIds": ["plan-basic", "plan-premium"], "costDescription": "1-5 credits depending on review depth", }, ) ``` Provide either `planId` or `planIds`, not both. `planIds` must be a non-empty list. ### Agent Card Structure The agent card includes a payment extension: ```json theme={null} { "name": "Code Review Agent", "url": "https://localhost:8080", "version": "1.0.0", "capabilities": { "extensions": [ { "uri": "urn:nevermined:payment", "params": { "paymentType": "dynamic", "credits": 5, "agentId": "your-agent-id", "planIds": ["plan-basic", "plan-premium"], "costDescription": "1-5 credits depending on review depth" } } ] } } ``` ## Using the `@a2a_requires_payment` Decorator The simplest way to create a payment-protected A2A agent: ```python theme={null} from payments_py import Payments from payments_py.common.types import PaymentOptions from payments_py.a2a import AgentResponse, a2a_requires_payment, build_payment_agent_card payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) agent_card = build_payment_agent_card( base_card={ "name": "My Agent", "url": "http://localhost:8080", "version": "1.0.0", "capabilities": {}, }, payment_metadata={ "paymentType": "dynamic", "credits": 3, "agentId": "your-agent-id", "planIds": ["plan-1", "plan-2"], }, ) @a2a_requires_payment( payments=payments, agent_card=agent_card, default_credits=1, ) async def my_agent(context) -> AgentResponse: text = context.get_user_input() return AgentResponse(text=f"Echo: {text}", credits_used=1) # Start serving (blocking) my_agent.serve(port=8080) ``` The decorator handles: * Payment middleware (verify/settle) automatically * Publishing task status events with `creditsUsed` metadata * Credit burning on task completion ### `AgentResponse` | Field | Type | Description | | -------------- | -------------- | -------------------------------------------------- | | `text` | `str` | The agent's text response | | `credits_used` | `int \| None` | Credits consumed (falls back to `default_credits`) | | `metadata` | `dict \| None` | Extra metadata for the final event | ## Starting an A2A Server (Advanced) For more control, use `PaymentsA2AServer.start()` directly: ```python theme={null} from payments_py.a2a.server import PaymentsA2AServer from a2a.server.agent_execution import AgentExecutor from a2a.server.events.event_queue import EventQueue class MyExecutor(AgentExecutor): async def execute(self, context, event_queue: EventQueue): # Your agent logic — publish events to event_queue ... async def cancel(self, context, event_queue: EventQueue): ... result = PaymentsA2AServer.start( agent_card=agent_card, executor=MyExecutor(), payments_service=payments, port=8080, base_path="/", expose_agent_card=True, async_execution=False, ) # Run the server import asyncio asyncio.run(result.server.serve()) ``` ## Server Configuration Options | Option | Type | Required | Description | | ------------------- | --------------- | -------- | ------------------------------------- | | `agent_card` | `AgentCard` | Yes | A2A agent card with payment extension | | `executor` | `AgentExecutor` | Yes | Your agent implementation | | `payments_service` | `Payments` | Yes | Payments instance | | `port` | `int` | No | Server port (default: 8080) | | `task_store` | `TaskStore` | No | Task storage implementation | | `base_path` | `str` | No | Base URL path (default: "/") | | `expose_agent_card` | `bool` | No | Expose /.well-known/agent.json | | `hooks` | `dict` | No | Request lifecycle hooks | | `async_execution` | `bool` | No | Enable async task execution | ## Request Validation The A2A server automatically validates payments on every POST request: 1. Extracts the access token from the `payment-signature` header 2. Reads `planId` or `planIds` from the agent card's payment extension 3. Verifies permissions via `build_payment_required_for_plans()` 4. Rejects requests with 402 if validation fails, including a base64-encoded `payment-required` header When multiple plans are configured, the 402 response includes all plans in `accepts[]`, allowing the client to choose which plan to purchase. ``` HTTP/1.1 402 Payment Required payment-required: eyJ4NDAy... (base64-encoded X402PaymentRequired) {"error": {"code": -32001, "message": "Missing bearer token."}} ``` ## Client Usage ### Discovering Plans from the Agent Card Consumers can fetch the agent card to discover available plans: ```python theme={null} import httpx async with httpx.AsyncClient() as client: resp = await client.get("http://agent-url/.well-known/agent.json") card = resp.json() extensions = card["capabilities"]["extensions"] payment_ext = next(e for e in extensions if e["uri"] == "urn:nevermined:payment") plan_ids = payment_ext["params"].get("planIds") or [payment_ext["params"]["planId"]] agent_id = payment_ext["params"]["agentId"] ``` ### Ordering a Plan and Sending Messages ```python theme={null} from payments_py import Payments from payments_py.common.types import PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) # Order (purchase) the plan payments.plans.order_plan(plan_ids[0]) # Get x402 access token token_resp = payments.x402.get_x402_access_token( plan_id=plan_ids[0], agent_id=agent_id, ) access_token = token_resp["accessToken"] # Send A2A JSON-RPC message async with httpx.AsyncClient() as client: resp = await client.post( "http://agent-url/", json={ "jsonrpc": "2.0", "id": 1, "method": "message/send", "params": { "message": { "messageId": "msg-1", "role": "user", "parts": [{"kind": "text", "text": "Review this code"}], } }, }, headers={"payment-signature": access_token}, ) result = resp.json() ``` ## Hooks Add custom logic at request lifecycle points: ```python theme={null} async def before_request(method, params, request): print(f"Incoming request: {method}") async def after_request(method, response, request): print(f"Request completed: {method}") async def on_error(method, error, request): print(f"Request failed: {method} - {error}") result = PaymentsA2AServer.start( agent_card=agent_card, executor=executor, payments_service=payments, hooks={ "beforeRequest": before_request, "afterRequest": after_request, "onError": on_error, }, ) ``` ## Error Handling | Error Code | HTTP Status | Description | | ---------- | ----------- | -------------------------- | | -32001 | 402 | Missing Bearer token | | -32001 | 402 | Payment validation failed | | -32001 | 402 | Agent ID missing from card | | -32001 | 402 | Plan ID missing from card | ## Next Steps Deep dive into x402 payment protocol Manual validation patterns # Agents Source: https://docs.nevermined.app/docs/api-reference/python/agents-module Register and manage AI agents with the Python SDK This guide covers how to register and manage AI agents using the Nevermined Payments Python SDK. ## Overview AI Agents are services that users can access through payment plans. The Agents API allows you to: * Register new agents with associated payment plans * Register agents and plans together in a single operation * Update agent metadata and endpoints * Manage plan associations ## Agents API Access the Agents API through `payments.agents`: ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) # Access agents API agents_api = payments.agents ``` ## Register Agents ### Basic Agent Registration Register an agent and associate it with existing payment plans: ```python theme={null} from payments_py.common.types import AgentMetadata, AgentAPIAttributes # Define agent metadata agent_metadata = AgentMetadata( name="My AI Assistant", description="An intelligent assistant that helps with various tasks", tags=["ai", "assistant", "productivity"], author="Your Company" ) # Define API configuration agent_api = AgentAPIAttributes( endpoints=[ {"POST": "https://your-api.com/api/v1/agents/:agentId/tasks"}, {"GET": "https://your-api.com/api/v1/agents/:agentId/tasks/:taskId"} ], agent_definition_url="https://your-api.com/api/v1/openapi.json" ) # List of plan IDs that grant access to this agent payment_plans = [plan_id_1, plan_id_2] # Register the agent result = payments.agents.register_agent( agent_metadata=agent_metadata, agent_api=agent_api, payment_plans=payment_plans ) print(f"Agent ID: {result['agentId']}") ``` ### Register Agent and Plan Together Create both an agent and a payment plan in a single operation: ```python theme={null} from payments_py.common.types import AgentMetadata, AgentAPIAttributes, PlanMetadata from payments_py.plans import get_erc20_price_config, get_fixed_credits_config # Agent configuration agent_metadata = AgentMetadata( name="AI Code Reviewer", description="Automated code review assistant", tags=["code", "review", "ai"] ) agent_api = AgentAPIAttributes( endpoints=[{"POST": "https://your-api.com/api/v1/review"}], agent_definition_url="https://your-api.com/openapi.json" ) # Plan configuration plan_metadata = PlanMetadata( name="Code Review Plan", description="100 code reviews" ) price_config = get_erc20_price_config( amount=50, token_address="0xYourTokenAddress", receiver=payments.account_address ) credits_config = get_fixed_credits_config(credits_granted=100) # Register both in one call result = payments.agents.register_agent_and_plan( agent_metadata=agent_metadata, agent_api=agent_api, plan_metadata=plan_metadata, price_config=price_config, credits_config=credits_config, access_limit="credits" # or "time" ) print(f"Agent ID: {result['agentId']}") print(f"Plan ID: {result['planId']}") print(f"Transaction: {result['txHash']}") ``` ## Agent Configuration ### AgentMetadata Fields | Field | Type | Required | Description | | ----------------- | ----------- | -------- | ------------------- | | `name` | `str` | Yes | Agent display name | | `description` | `str` | No | Agent description | | `author` | `str` | No | Author/company name | | `tags` | `list[str]` | No | Categorization tags | | `license` | `str` | No | License information | | `sample_link` | `str` | No | Link to demo/sample | | `api_description` | `str` | No | API documentation | ### AgentAPIAttributes Fields | Field | Type | Required | Description | | ---------------------- | ------------ | -------- | ------------------------------- | | `endpoints` | `list[dict]` | Yes | List of endpoint configurations | | `agent_definition_url` | `str` | Yes | URL to OpenAPI/MCP/A2A spec | | `open_endpoints` | `list[str]` | No | Endpoints without auth | | `auth_type` | `AuthType` | No | Authentication type | ### Endpoint Configuration Endpoints are defined as dictionaries with HTTP verb as key and URL as value: ```python theme={null} agent_api = AgentAPIAttributes( endpoints=[ {"POST": "https://api.example.com/agents/:agentId/tasks"}, {"GET": "https://api.example.com/agents/:agentId/tasks/:taskId"}, {"DELETE": "https://api.example.com/agents/:agentId/tasks/:taskId"} ], agent_definition_url="https://api.example.com/openapi.json" ) ``` The `:agentId` and `:taskId` placeholders will be replaced with actual values during request validation. ## Retrieve Agents ### Get a Single Agent ```python theme={null} agent = payments.agents.get_agent(agent_id="your-agent-id") print(f"Agent name: {agent['name']}") print(f"Agent endpoints: {agent['endpoints']}") ``` ### Get Plans for an Agent ```python theme={null} from payments_py.common.types import PaginationOptions plans = payments.agents.get_agent_plans( agent_id="your-agent-id", pagination=PaginationOptions(page=1, offset=10) ) print(f"Associated plans: {plans}") ``` ## Update Agents ### Update Agent Metadata ```python theme={null} updated_metadata = AgentMetadata( name="Updated Agent Name", description="Updated description with new features", tags=["ai", "updated", "v2"] ) updated_api = AgentAPIAttributes( endpoints=[{"POST": "https://new-api.com/v2/tasks"}], agent_definition_url="https://new-api.com/v2/openapi.json" ) result = payments.agents.update_agent_metadata( agent_id="your-agent-id", agent_metadata=updated_metadata, agent_api=updated_api ) print(f"Update successful: {result}") ``` ## Manage Plan Associations ### Add a Plan to an Agent ```python theme={null} result = payments.agents.add_plan_to_agent( plan_id="plan-to-add", agent_id="your-agent-id" ) print(f"Plan added: {result}") ``` ### Remove a Plan from an Agent ```python theme={null} result = payments.agents.remove_plan_from_agent( plan_id="plan-to-remove", agent_id="your-agent-id" ) print(f"Plan removed: {result}") ``` ## Complete Example ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.common.types import AgentMetadata, AgentAPIAttributes, PlanMetadata from payments_py.plans import ( get_erc20_price_config, get_fixed_credits_config, get_free_price_config ) # Initialize payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) ERC20_TOKEN = "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" builder_address = payments.account_address # 1. Create a payment plan first plan_result = payments.plans.register_credits_plan( plan_metadata=PlanMetadata(name="AI Agent Plan"), price_config=get_erc20_price_config(20, ERC20_TOKEN, builder_address), credits_config=get_fixed_credits_config(100) ) plan_id = plan_result['planId'] # 2. Register an agent with the plan agent_result = payments.agents.register_agent( agent_metadata=AgentMetadata( name="My First AI Agent", description="A demo AI agent", tags=["demo", "ai"] ), agent_api=AgentAPIAttributes( endpoints=[{"POST": "https://api.example.com/agents/:agentId/tasks"}], agent_definition_url="https://api.example.com/openapi.json" ), payment_plans=[plan_id] ) agent_id = agent_result['agentId'] print(f"Created agent: {agent_id}") # 3. Or create both together combo_result = payments.agents.register_agent_and_plan( agent_metadata=AgentMetadata(name="Combo Agent"), agent_api=AgentAPIAttributes( endpoints=[{"POST": "https://api.example.com/combo"}], agent_definition_url="https://api.example.com/openapi.json" ), plan_metadata=PlanMetadata(name="Combo Plan"), price_config=get_free_price_config(), credits_config=get_fixed_credits_config(10) ) print(f"Combo Agent: {combo_result['agentId']}, Plan: {combo_result['planId']}") # 4. Retrieve agent details agent = payments.agents.get_agent(agent_id) print(f"Agent details: {agent}") # 5. Get associated plans plans = payments.agents.get_agent_plans(agent_id) print(f"Agent plans: {plans}") # 6. Update agent metadata payments.agents.update_agent_metadata( agent_id=agent_id, agent_metadata=AgentMetadata(name="Updated Agent Name"), agent_api=AgentAPIAttributes( endpoints=[{"POST": "https://api.example.com/v2/agents/:agentId/tasks"}], agent_definition_url="https://api.example.com/v2/openapi.json" ) ) ``` ## Next Steps Publish files and datasets Learn how to access agents # Payments and Balance Source: https://docs.nevermined.app/docs/api-reference/python/balance-module Order plans and manage credit balances This guide covers how to order payment plans and manage balances using the Nevermined Payments Python SDK. ## Overview As a subscriber, you need to order payment plans to gain access to AI agents and resources. This guide explains: * How to check plan balances * How to order plans (crypto payments) * How to mint and burn credits (for plan owners) ## Check Plan Balance Before ordering or using a plan, check the current balance: ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) # Check balance for current user balance = payments.plans.get_plan_balance(plan_id="your-plan-id") print(f"Plan ID: {balance.plan_id}") print(f"Plan Name: {balance.plan_name}") print(f"Plan Type: {balance.plan_type}") print(f"Balance: {balance.balance}") print(f"Is Subscriber: {balance.is_subscriber}") print(f"Price per Credit: {balance.price_per_credit}") ``` ### Check Balance for Another User ```python theme={null} balance = payments.plans.get_plan_balance( plan_id="your-plan-id", account_address="0xOtherUserAddress" ) ``` ### Balance Response Fields | Field | Type | Description | | ------------------ | ------- | ----------------------- | | `plan_id` | `str` | Plan identifier | | `plan_name` | `str` | Plan display name | | `plan_type` | `str` | "credits" or "time" | | `balance` | `int` | Current credit balance | | `is_subscriber` | `bool` | Whether user has access | | `holder_address` | `str` | Wallet address checked | | `price_per_credit` | `float` | Cost per credit | ## Order Plans ### Order with Crypto Order a plan using cryptocurrency (ERC20 or native tokens): ```python theme={null} # Order a plan (requires sufficient token balance) result = payments.plans.order_plan(plan_id="your-plan-id") print(f"Success: {result['success']}") print(f"Order details: {result}") ``` For ERC20 token payments, ensure you have approved the Nevermined contract to spend your tokens before ordering. ### Order Fiat Plans For plans priced in fiat (USD), use Stripe checkout: ```python theme={null} # Get Stripe checkout session checkout = payments.plans.order_fiat_plan(plan_id="fiat-plan-id") print(f"Checkout URL: {checkout['checkoutUrl']}") print(f"Session ID: {checkout['sessionId']}") # Redirect user to checkout['checkoutUrl'] to complete payment ``` ## Mint Credits (Plan Owners) Plan owners can mint additional credits for subscribers: ```python theme={null} # Mint credits to a user result = payments.plans.mint_plan_credits( plan_id="your-plan-id", credits_amount=50, credits_receiver="0xSubscriberAddress" ) print(f"Minted: {result}") ``` ### Mint Expirable Credits For time-limited plans, mint credits with an expiration: ```python theme={null} result = payments.plans.mint_plan_expirable( plan_id="your-plan-id", credits_amount=100, credits_receiver="0xSubscriberAddress", credits_duration=86400 # 1 day in seconds ) ``` ## Burn Credits (Plan Owners) Plan owners can burn credits from the plan: ```python theme={null} result = payments.plans.burn_credits( plan_id="your-plan-id", credits_amount="10" ) print(f"Burned: {result}") ``` ## Redeem Credits Agents can redeem credits from subscribers for completed work: ```python theme={null} result = payments.plans.redeem_credits( agent_request_id="request-123", plan_id="plan-id", redeem_from="0xSubscriberAddress", credits_amount_to_redeem="5" ) ``` ## Complete Example ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.common.types import PlanMetadata from payments_py.plans import get_erc20_price_config, get_fixed_credits_config # Initialize as builder (plan owner) payments_builder = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:builder-key", environment="sandbox") ) # Initialize as subscriber payments_subscriber = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) ERC20_TOKEN = "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" builder_address = payments_builder.account_address # 1. Builder creates a plan plan_result = payments_builder.plans.register_credits_plan( plan_metadata=PlanMetadata(name="Premium Plan"), price_config=get_erc20_price_config(20, ERC20_TOKEN, builder_address), credits_config=get_fixed_credits_config(100) ) plan_id = plan_result['planId'] print(f"Created plan: {plan_id}") # 2. Subscriber checks initial balance (should be 0 or not subscribed) initial_balance = payments_subscriber.plans.get_plan_balance(plan_id) print(f"Initial balance: {initial_balance.balance}") print(f"Is subscriber: {initial_balance.is_subscriber}") # 3. Subscriber orders the plan order_result = payments_subscriber.plans.order_plan(plan_id) print(f"Order success: {order_result['success']}") # 4. Subscriber checks balance after ordering final_balance = payments_subscriber.plans.get_plan_balance(plan_id) print(f"Final balance: {final_balance.balance}") print(f"Is subscriber: {final_balance.is_subscriber}") # 5. Builder can mint additional credits if final_balance.is_subscriber: mint_result = payments_builder.plans.mint_plan_credits( plan_id=plan_id, credits_amount=50, credits_receiver=payments_subscriber.account_address ) print(f"Minted 50 additional credits") # Check updated balance updated_balance = payments_subscriber.plans.get_plan_balance(plan_id) print(f"Updated balance: {updated_balance.balance}") ``` ## Workflow Summary ``` ┌──────────────────────────────────────────────────────────┐ │ BUILDER │ │ 1. Create Plan (register_credits_plan) │ │ 2. Register Agent with Plan │ │ 3. Optionally mint credits to users │ └──────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ SUBSCRIBER │ │ 1. Check Balance (get_plan_balance) │ │ 2. Order Plan (order_plan) │ │ 3. Get Access Token (get_x402_access_token) │ │ 4. Query Agent (with access token) │ └──────────────────────────────────────────────────────────┘ ``` ## Next Steps Get access tokens and make requests How agents validate requests # Installation Source: https://docs.nevermined.app/docs/api-reference/python/installation Install and configure the payments-py Python SDK This guide covers how to install the Nevermined Payments Python SDK. ## Overview The Nevermined Payments Python SDK (`payments-py`) is a Python library that provides tools for integrating AI agent monetization and access control into your applications. It supports: * Payment plans (credits-based and time-based) * AI agent registration and management * X402 payment protocol * MCP (Model Context Protocol) integration * A2A (Agent-to-Agent) protocol support ## Prerequisites Before installing the SDK, ensure you have: * **Python 3.10 or higher** - The SDK requires Python 3.10+ * **pip or Poetry** - Package manager for installation * **Nevermined API Key** - Obtain from the [Nevermined App](https://nevermined.app) ## Installation Steps ### Using pip ```bash theme={null} pip install payments-py ``` ### Using Poetry ```bash theme={null} poetry add payments-py ``` ### With Optional Dependencies For FastAPI/x402 middleware support: ```bash theme={null} # Using pip pip install payments-py[fastapi] # Using Poetry poetry add payments-py -E fastapi ``` ## Verify Installation After installation, verify the SDK is working: ```python theme={null} from payments_py import Payments, PaymentOptions # Check version import payments_py print(f"payments-py installed successfully") ``` ## Environment Setup Create a `.env` file or set environment variables: ```bash theme={null} # Required NVM_API_KEY=nvm:your-api-key-here # Optional (for custom environments) NVM_BACKEND_URL=https://api.sandbox.nevermined.app NVM_PROXY_URL=https://proxy.sandbox.nevermined.app ``` ## Next Steps Once installed, proceed to [Initializing the Library](/docs/api-reference/python/payments-class) to configure the SDK. # MCP Integration Source: https://docs.nevermined.app/docs/api-reference/python/mcp-module Build MCP servers with integrated payments This guide explains how to integrate the Nevermined Payments Python SDK with MCP (Model Context Protocol) servers. ## Overview MCP (Model Context Protocol) enables AI applications to interact with external tools, resources, and prompts. The Nevermined SDK provides built-in MCP integration to: * Protect tools, resources, and prompts with paywalls * Handle OAuth 2.1 authentication * Manage credit consumption per operation ## MCP Integration API Access the MCP integration through `payments.mcp`: ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) # MCP integration is available as: mcp = payments.mcp ``` ## Simplified API (Recommended) The simplified API handles server setup automatically: ### Register a Tool ```python theme={null} async def hello_handler(args, context=None): """Handle the hello tool request.""" name = args.get("name", "World") return { "content": [{"type": "text", "text": f"Hello, {name}!"}] } # Register the tool payments.mcp.register_tool( name="hello_world", config={ "description": "Says hello to someone", "inputSchema": { "type": "object", "properties": { "name": {"type": "string", "description": "Name to greet"} } } }, handler=hello_handler, options={"credits": 1} # Cost: 1 credit per call ) ``` ### Register a Resource ```python theme={null} async def config_handler(uri, variables, context=None): """Handle the configuration resource request.""" return { "contents": [{ "uri": str(uri), "mimeType": "application/json", "text": '{"version": "1.0.0", "feature_flags": {"beta": true}}' }] } payments.mcp.register_resource( uri="data://config", config={ "name": "Configuration", "description": "Application configuration", "mimeType": "application/json" }, handler=config_handler, options={"credits": 2} # Cost: 2 credits per access ) ``` ### Register a Prompt ```python theme={null} async def greeting_handler(args, context=None): """Handle the greeting prompt request.""" style = args.get("style", "formal") return { "messages": [{ "role": "user", "content": { "type": "text", "text": f"Please greet me in a {style} way." } }] } payments.mcp.register_prompt( name="greeting", config={ "name": "Greeting", "description": "Generates a greeting" }, handler=greeting_handler, options={"credits": 1} ) ``` ### Start the Server ```python theme={null} import asyncio async def main(): # Register handlers first payments.mcp.register_tool("hello", {...}, hello_handler) # Start the MCP server result = await payments.mcp.start({ "port": 5001, "agentId": "your-agent-id", "serverName": "my-mcp-server", "version": "1.0.0", "description": "My MCP server with Nevermined payments" }) print(f"Server running at: {result['info']['baseUrl']}") print(f"Tools: {result['info']['tools']}") # Server runs until stopped # To stop: await payments.mcp.stop() asyncio.run(main()) ``` ## Advanced API For more control, use the advanced API: ### Configure and Protect Handlers ```python theme={null} # Configure shared options payments.mcp.configure({ "agentId": "your-agent-id", "serverName": "my-mcp-server" }) # Wrap a handler with paywall async def my_handler(args): return {"result": "processed"} protected_handler = payments.mcp.with_paywall( handler=my_handler, options={ "kind": "tool", "name": "my_tool", "credits": 1 } ) ``` ### Attach to Existing Server ```python theme={null} from mcp.server import MCPServer # Create your own MCP server server = MCPServer() # Attach payments integration registrar = payments.mcp.attach(server) # Register protected handlers registrar.register_tool( name="hello", config={"description": "Hello tool"}, handler=hello_handler, options={"credits": 1} ) registrar.register_resource( name="config", template="data://{path}", config={"name": "Config"}, handler=config_handler, options={"credits": 2} ) ``` ## Complete Example ```python theme={null} import asyncio from payments_py import Payments, PaymentOptions # Initialize payments payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) # Define handlers async def analyze_code(args, context=None): """Analyze code for issues.""" code = args.get("code", "") language = args.get("language", "python") # Your analysis logic here issues = analyze(code, language) return { "content": [{ "type": "text", "text": f"Found {len(issues)} issues in {language} code." }] } async def get_docs(uri, variables, context=None): """Return documentation.""" topic = variables.get("topic", "general") return { "contents": [{ "uri": str(uri), "mimeType": "text/markdown", "text": f"# Documentation for {topic}\n\nContent here..." }] } async def code_review_prompt(args, context=None): """Generate code review prompt.""" return { "messages": [{ "role": "user", "content": { "type": "text", "text": "Please review the following code for best practices..." } }] } # Register handlers payments.mcp.register_tool( "analyze_code", { "description": "Analyzes code for potential issues", "inputSchema": { "type": "object", "properties": { "code": {"type": "string"}, "language": {"type": "string", "default": "python"} }, "required": ["code"] } }, analyze_code, {"credits": 5} # 5 credits per analysis ) payments.mcp.register_resource( "docs://{topic}", { "name": "Documentation", "description": "Technical documentation", "mimeType": "text/markdown" }, get_docs, {"credits": 1} ) payments.mcp.register_prompt( "code_review", { "name": "Code Review", "description": "Generates a code review prompt" }, code_review_prompt, {"credits": 2} ) # Start server async def main(): result = await payments.mcp.start({ "port": 5001, "agentId": "agent-123", "serverName": "code-assistant-mcp", "version": "1.0.0" }) print(f"MCP Server running at {result['info']['baseUrl']}") print(f"Tools: {result['info']['tools']}") print(f"Resources: {result['info']['resources']}") print(f"Prompts: {result['info']['prompts']}") # Keep running try: while True: await asyncio.sleep(1) except KeyboardInterrupt: await payments.mcp.stop() asyncio.run(main()) ``` ## Server Configuration | Option | Type | Required | Description | | ------------- | ----- | -------- | ----------------------------- | | `port` | `int` | Yes | Server port | | `agentId` | `str` | Yes | Nevermined agent DID | | `serverName` | `str` | Yes | Human-readable name | | `baseUrl` | `str` | No | Base URL (default: localhost) | | `version` | `str` | No | Server version | | `description` | `str` | No | Server description | ## Handler Options | Option | Type | Description | | --------------- | ------------------- | -------------------------------------------------------------------- | | `credits` | `int` or `callable` | Credits to consume per call | | `planId` | `str` | Optional override for the plan ID (otherwise inferred from token) | | `maxAmount` | `int` | Max credits to verify during authentication (default: `1`) | | `onRedeemError` | `str` | `"ignore"` (default) or `"propagate"` to raise on redemption failure | ## Response Metadata (`_meta`) After each paywall-protected call, the SDK injects a `_meta` field into the response following the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic). This field is always present regardless of whether credit redemption succeeded or failed: ```python theme={null} # Successful redemption { "content": [{"type": "text", "text": "result"}], "_meta": { "success": True, "txHash": "0xabc...", "creditsRedeemed": "5", "planId": "plan-123", "subscriberAddress": "0x123..." } } # Failed redemption { "content": [{"type": "text", "text": "result"}], "_meta": { "success": False, "creditsRedeemed": "0", "planId": "plan-123", "subscriberAddress": "0x123...", "errorReason": "Insufficient credits" } } ``` | Field | Type | Description | | ------------------- | --------------- | --------------------------------------------- | | `success` | `bool` | Whether credit redemption succeeded | | `txHash` | `str` or `None` | Blockchain transaction hash (only on success) | | `creditsRedeemed` | `str` | Number of credits burned (`"0"` on failure) | | `planId` | `str` | Plan used for the operation | | `subscriberAddress` | `str` | Subscriber's wallet address | | `errorReason` | `str` | Error message (only on failure) | ## Endpoints The MCP server exposes: * `/.well-known/oauth-authorization-server` - OAuth 2.1 discovery * `/.well-known/oauth-protected-resource` - Resource metadata * `/register` - Client registration * `/mcp` - MCP protocol endpoint (POST/GET/DELETE) * `/health` - Health check ## Next Steps Agent-to-Agent protocol Payment protocol details # Initializing the Library Source: https://docs.nevermined.app/docs/api-reference/python/payments-class Initialize and configure the Payments class for Python This guide explains how to import, configure, and initialize the Nevermined Payments Python SDK. ## Get the NVM API Key Before using the SDK, you need a Nevermined API Key: 1. Go to the [Nevermined App](https://nevermined.app) 2. Sign in or create an account 3. Navigate to **Settings** > **API Keys** 4. Generate a new API key 5. Copy the key (format: `nvm:xxxxxxxx...`) Never commit your API key to version control. Use environment variables or a secrets manager. ## Import and Initialize ### Basic Initialization ```python theme={null} from payments_py import Payments, PaymentOptions # Initialize with API key and environment payments = Payments.get_instance( PaymentOptions( nvm_api_key="nvm:your-api-key-here", environment="sandbox" ) ) # Verify initialization print(f"Connected to: {payments.environment.backend}") print(f"Account: {payments.account_address}") ``` ### Using Environment Variables ```python theme={null} import os from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.getenv("NVM_API_KEY"), environment=os.getenv("NVM_ENVIRONMENT", "sandbox") ) ) ``` ## Configuration Options The `PaymentOptions` class accepts the following parameters: | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------ | | `nvm_api_key` | `str` | Yes | Your Nevermined API key | | `environment` | `str` | Yes | Environment name (see below) | | `app_id` | `str` | No | Application identifier | | `version` | `str` | No | Application version | | `headers` | `dict` | No | Additional HTTP headers | | `return_url` | `str` | No | Return URL (browser mode only) | ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions( nvm_api_key="nvm:your-api-key", environment="sandbox", app_id="my-app", version="1.0.0", headers={"X-Custom-Header": "value"} ) ) ``` ## Environments ### Sandbox Environment (Testing) Use `sandbox` for development and testing: ```python theme={null} payments = Payments.get_instance( PaymentOptions( nvm_api_key="nvm:your-api-key", environment="sandbox" ) ) ``` * Backend: `https://api.sandbox.nevermined.app` * Proxy: `https://proxy.sandbox.nevermined.app` * Uses test tokens and test networks ### Live Environment (Production) Use `live` for production: ```python theme={null} payments = Payments.get_instance( PaymentOptions( nvm_api_key="nvm:your-api-key", environment="live" ) ) ``` * Backend: `https://api.live.nevermined.app` * Proxy: `https://proxy.live.nevermined.app` * Uses real tokens and mainnet networks ### Custom Environment For self-hosted or development setups: ```python theme={null} import os # Set environment variables first os.environ["NVM_BACKEND_URL"] = "http://localhost:3001" os.environ["NVM_PROXY_URL"] = "http://localhost:443" payments = Payments.get_instance( PaymentOptions( nvm_api_key="nvm:your-api-key", environment="custom" ) ) ``` ### Available Environments | Environment | Description | | ----------- | ------------------------------------- | | `sandbox` | Sandbox (testing) | | `live` | Production mainnet | | `custom` | Custom URLs via environment variables | ## Accessing Sub-APIs The initialized `Payments` object provides access to specialized APIs: ```python theme={null} # Plans API - manage payment plans payments.plans.register_credits_plan(...) payments.plans.get_plan(plan_id) payments.plans.get_plan_balance(plan_id) # Agents API - manage AI agents payments.agents.register_agent(...) payments.agents.get_agent(agent_id) # Facilitator API - x402 verification/settlement payments.facilitator.verify_permissions(...) payments.facilitator.settle_permissions(...) # X402 Token API - generate access tokens payments.x402.get_x402_access_token(plan_id, agent_id) # MCP Integration payments.mcp.register_tool(...) await payments.mcp.start(config) # A2A Integration payments.a2a["start"](agent_card=card, executor=executor) ``` ## Error Handling The SDK raises `PaymentsError` for API errors: ```python theme={null} from payments_py.common.payments_error import PaymentsError try: result = payments.plans.get_plan("invalid-id") except PaymentsError as e: print(f"Error: {e.message}") print(f"Code: {e.code}") ``` ## Next Steps Now that you have initialized the SDK, proceed to: * [Payment Plans](/docs/api-reference/python/plans-module) - Create payment plans * [Agents](/docs/api-reference/python/agents-module) - Register AI agents # Payment Plans Source: https://docs.nevermined.app/docs/api-reference/python/plans-module Create and manage payment plans with the Python SDK This guide covers how to create and manage payment plans using the Nevermined Payments Python SDK. ## Overview Payment Plans define how users pay to access your AI agents and services. Nevermined supports two types of plans: * **Credits Plans**: Users purchase a fixed number of credits that are consumed with each request * **Time Plans**: Users purchase access for a specific duration (e.g., 1 day, 1 month) ## Plans API Access the Plans API through `payments.plans`: ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) # Access plans API plans_api = payments.plans ``` ## Types of Payment Plans ### Credits Plans Credits plans grant users a fixed number of credits. Each agent request consumes credits according to the plan configuration. ```python theme={null} from payments_py.common.types import PlanMetadata from payments_py.plans import get_erc20_price_config, get_fixed_credits_config # Plan metadata plan_metadata = PlanMetadata( name="Basic Plan", description="100 credits for AI agent access" ) # Price: 20 tokens (ERC20) price_config = get_erc20_price_config( amount=20, token_address="0xYourERC20TokenAddress", receiver="0xYourWalletAddress" ) # Credits: 100 credits, 1 credit per request credits_config = get_fixed_credits_config( credits_granted=100, credits_per_request=1 ) # Register the plan result = payments.plans.register_credits_plan( plan_metadata=plan_metadata, price_config=price_config, credits_config=credits_config ) print(f"Plan ID: {result['planId']}") ``` ### Time Plans Time plans grant users unlimited access for a specific duration: ```python theme={null} from payments_py.plans import get_expirable_duration_config, ONE_DAY_DURATION # Price configuration price_config = get_erc20_price_config( amount=50, token_address="0xYourERC20TokenAddress", receiver="0xYourWalletAddress" ) # Duration: 1 day credits_config = get_expirable_duration_config(ONE_DAY_DURATION) result = payments.plans.register_time_plan( plan_metadata=PlanMetadata(name="Daily Plan"), price_config=price_config, credits_config=credits_config ) ``` ### Trial Plans Trial plans can only be purchased once per user. Perfect for free trials: ```python theme={null} from payments_py.plans import get_free_price_config # Free trial with 10 credits price_config = get_free_price_config() credits_config = get_fixed_credits_config(credits_granted=10) result = payments.plans.register_credits_trial_plan( plan_metadata=PlanMetadata(name="Free Trial", description="10 free credits"), price_config=price_config, credits_config=credits_config ) ``` ```python theme={null} # Free trial with 1 day access credits_config = get_expirable_duration_config(ONE_DAY_DURATION) result = payments.plans.register_time_trial_plan( plan_metadata=PlanMetadata(name="1-Day Trial"), price_config=get_free_price_config(), credits_config=credits_config ) ``` ## Price Configuration ### ERC20 Token Price ```python theme={null} from payments_py.plans import get_erc20_price_config price_config = get_erc20_price_config( amount=100, # Amount in token units token_address="0xTokenAddress", # ERC20 token contract receiver="0xBuilderAddress" # Payment receiver ) ``` ### Native Token Price (ETH) ```python theme={null} from payments_py.plans import get_native_token_price_config price_config = get_native_token_price_config( amount=1000000000000000, # Amount in wei (0.001 ETH) receiver="0xBuilderAddress" ) ``` ### Fiat Price (USD) ```python theme={null} from payments_py.plans import get_fiat_price_config price_config = get_fiat_price_config( amount=999, # $9.99 in cents receiver="0xBuilderAddress" ) ``` ### Free Price ```python theme={null} from payments_py.plans import get_free_price_config price_config = get_free_price_config() ``` ## Credits Configuration ### Fixed Credits Each request consumes a fixed number of credits: ```python theme={null} from payments_py.plans import get_fixed_credits_config credits_config = get_fixed_credits_config( credits_granted=100, # Total credits granted credits_per_request=1 # Credits consumed per request ) ``` ### Dynamic Credits Each request can consume a variable number of credits: ```python theme={null} from payments_py.plans import get_dynamic_credits_config credits_config = get_dynamic_credits_config( credits_granted=100, min_credits_per_request=1, # Minimum credits per request max_credits_per_request=10 # Maximum credits per request ) ``` ### Duration-Based (Time Plans) ```python theme={null} from payments_py.plans import ( get_expirable_duration_config, get_non_expirable_duration_config, ONE_DAY_DURATION, ONE_WEEK_DURATION, ONE_MONTH_DURATION, ONE_YEAR_DURATION ) # Expirable (time-limited) credits_config = get_expirable_duration_config(ONE_MONTH_DURATION) # Non-expirable (permanent access) credits_config = get_non_expirable_duration_config() ``` ## Retrieve Plans ### Get a Single Plan ```python theme={null} plan = payments.plans.get_plan(plan_id="123456") print(f"Plan name: {plan['name']}") print(f"Plan type: {plan['planType']}") ``` ### Get Plan Balance Check the balance for a specific account: ```python theme={null} balance = payments.plans.get_plan_balance( plan_id="123456", account_address="0xUserAddress" # Optional, defaults to current user ) print(f"Balance: {balance.balance} credits") print(f"Is subscriber: {balance.is_subscriber}") ``` ### Get Agents Associated to a Plan ```python theme={null} from payments_py.common.types import PaginationOptions agents = payments.plans.get_agents_associated_to_plan( plan_id="123456", pagination=PaginationOptions(page=1, offset=10) ) print(f"Agents: {agents}") ``` ## Complete Example ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.common.types import PlanMetadata from payments_py.plans import ( get_erc20_price_config, get_fixed_credits_config, get_expirable_duration_config, get_free_price_config, ONE_DAY_DURATION ) # Initialize payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) builder_address = payments.account_address # ERC20 token address (use a test token in sandbox) ERC20_TOKEN = "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" # 1. Create a credits plan credits_plan = payments.plans.register_credits_plan( plan_metadata=PlanMetadata(name="Basic Credits Plan"), price_config=get_erc20_price_config(20, ERC20_TOKEN, builder_address), credits_config=get_fixed_credits_config(100) ) print(f"Credits Plan: {credits_plan['planId']}") # 2. Create a time plan time_plan = payments.plans.register_time_plan( plan_metadata=PlanMetadata(name="Monthly Plan"), price_config=get_erc20_price_config(50, ERC20_TOKEN, builder_address), credits_config=get_expirable_duration_config(ONE_DAY_DURATION) ) print(f"Time Plan: {time_plan['planId']}") # 3. Create a free trial trial_plan = payments.plans.register_credits_trial_plan( plan_metadata=PlanMetadata(name="Free Trial"), price_config=get_free_price_config(), credits_config=get_fixed_credits_config(10) ) print(f"Trial Plan: {trial_plan['planId']}") # 4. Retrieve plan details plan_details = payments.plans.get_plan(credits_plan['planId']) print(f"Plan details: {plan_details}") ``` ## Next Steps Register AI agents and associate them with plans Order plans and manage balances # Querying an Agent Source: https://docs.nevermined.app/docs/api-reference/python/requests-module Query agents and process requests with the Python SDK This guide explains how to get x402 access tokens and make authenticated requests to AI agents. ## Overview To query an AI agent, subscribers need to: 1. Have an active subscription to a plan associated with the agent 2. Generate an x402 access token 3. Include the token in requests to the agent ## Get X402 Access Token The x402 access token authorizes requests to agents and enables credit verification/settlement. ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) # Generate access token result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id" # Optional but recommended ) access_token = result['accessToken'] print(f"Access Token: {access_token[:50]}...") ``` ### Token Generation Parameters | Parameter | Type | Required | Description | | ------------------ | ----- | -------- | ----------------------------- | | `plan_id` | `str` | Yes | The payment plan ID | | `agent_id` | `str` | No | Target agent ID (recommended) | | `redemption_limit` | `int` | No | Max number of redemptions | | `order_limit` | `str` | No | Max spend in wei | | `expiration` | `str` | No | ISO 8601 expiration date | ### Advanced Token Options ```python theme={null} from datetime import datetime, timedelta # Token with limits and expiration result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", redemption_limit=10, # Max 10 requests order_limit="1000000000000000000", # Max 1 token in wei expiration=(datetime.now() + timedelta(hours=24)).isoformat() ) ``` ## Make Requests to Agents ### Using the x402 Payment Header Include the access token in the `payment-signature` header (per [x402 v2 HTTP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md)): ```python theme={null} import requests # Get access token token_result = payments.x402.get_x402_access_token( plan_id=plan_id, agent_id=agent_id ) access_token = token_result['accessToken'] # Make request to the agent response = requests.post( f"https://agent-api.example.com/agents/{agent_id}/tasks", headers={ "payment-signature": access_token, "Content-Type": "application/json" }, json={ "task": "Analyze this data", "data": {"key": "value"} } ) if response.status_code == 200: result = response.json() print(f"Agent response: {result}") elif response.status_code == 402: print("Payment required - check plan balance") else: print(f"Error: {response.status_code}") ``` ## Decode Access Token You can decode the token to inspect its contents: ```python theme={null} from payments_py.x402.token import decode_access_token decoded = decode_access_token(access_token) if decoded: payload = decoded.get('payload', {}) authorization = payload.get('authorization', {}) print(f"Subscriber: {authorization.get('from')}") print(f"Plan ID: {authorization.get('planId')}") print(f"Agent ID: {authorization.get('agentId')}") ``` ## Complete Example ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.common.types import PlanMetadata, AgentMetadata, AgentAPIAttributes from payments_py.plans import get_free_price_config, get_fixed_credits_config import requests # Initialize as builder builder = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:builder-key", environment="sandbox") ) # Initialize as subscriber subscriber = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) # 1. Builder creates plan and agent plan_result = builder.plans.register_credits_plan( plan_metadata=PlanMetadata(name="API Access Plan"), price_config=get_free_price_config(), # Free for demo credits_config=get_fixed_credits_config(100) ) plan_id = plan_result['planId'] agent_result = builder.agents.register_agent( agent_metadata=AgentMetadata(name="Demo Agent"), agent_api=AgentAPIAttributes( endpoints=[{"POST": "https://api.example.com/tasks"}], agent_definition_url="https://api.example.com/openapi.json" ), payment_plans=[plan_id] ) agent_id = agent_result['agentId'] # 2. Subscriber orders the plan subscriber.plans.order_plan(plan_id) # 3. Subscriber generates access token token_result = subscriber.x402.get_x402_access_token( plan_id=plan_id, agent_id=agent_id ) access_token = token_result['accessToken'] # 4. Subscriber makes request to agent response = requests.post( "https://api.example.com/tasks", headers={ "payment-signature": access_token, "Content-Type": "application/json" }, json={"prompt": "Hello, agent!"} ) print(f"Status: {response.status_code}") print(f"Response: {response.json()}") # 5. Check updated balance balance = subscriber.plans.get_plan_balance(plan_id) print(f"Remaining credits: {balance.balance}") ``` ## Error Handling ### 402 Payment Required ```python theme={null} response = requests.post(agent_endpoint, headers=headers, json=payload) if response.status_code == 402: error = response.json() print(f"Payment required: {error}") # Check balance balance = payments.plans.get_plan_balance(plan_id) if balance.balance <= 0: print("No credits remaining - order more credits") elif not balance.is_subscriber: print("Not subscribed - order the plan first") ``` ### Token Expired or Invalid ```python theme={null} try: response = requests.post(agent_endpoint, headers=headers, json=payload) response.raise_for_status() except requests.HTTPError as e: if e.response.status_code == 401: print("Token expired or invalid - generate a new token") # Regenerate token new_token = payments.x402.get_x402_access_token(plan_id, agent_id) ``` ## Request Flow Diagram ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Subscriber │ │ Nevermined │ │ Agent │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ get_x402_access_token() │ │ ─────────────────► │ │ │ │ │ │ ◄───────────────── │ │ │ accessToken │ │ │ │ │ │ POST /tasks (payment-signature header) │ │ ────────────────────────────────────────► │ │ │ │ │ verify_permissions│ │ │ ◄────────────────── │ │ │ ─────────────────► │ │ │ │ │ │ settle_permissions│ │ │ ◄────────────────── │ │ │ ─────────────────► │ │ │ │ │ ◄────────────────────────────────────── │ Response │ │ │ │ ``` ## Next Steps How agents validate incoming requests Deep dive into x402 verification and settlement # Publishing Static Resources Source: https://docs.nevermined.app/docs/api-reference/python/resources-module Publish and manage static resources with access control This guide explains how to publish static resources (files, datasets) using the Nevermined Payments Python SDK. ## Overview Static resources are files or datasets that users can access through payment plans. Unlike AI agents that process requests, static resources are downloadable content such as: * Documents (PDFs, reports) * Datasets (CSV, JSON files) * Media files (images, videos) * Software packages ## Register Static Resource Agents Static resources are registered as a special type of agent with file endpoints: ```python theme={null} from payments_py.common.types import AgentMetadata, AgentAPIAttributes # Resource metadata resource_metadata = AgentMetadata( name="Premium Dataset Collection", description="A curated collection of ML training datasets", tags=["dataset", "machine-learning", "premium"] ) # Static file endpoints resource_api = AgentAPIAttributes( endpoints=[ {"GET": "https://storage.example.com/datasets/training-data.csv"}, {"GET": "https://storage.example.com/datasets/validation-data.csv"}, {"GET": "https://storage.example.com/datasets/test-data.csv"} ], agent_definition_url="https://storage.example.com/datasets/manifest.json" ) # Register with a payment plan result = payments.agents.register_agent( agent_metadata=resource_metadata, agent_api=resource_api, payment_plans=[plan_id] ) print(f"Resource ID: {result['agentId']}") ``` ## Using Wildcards for Multiple Files When you have multiple files that follow a pattern, you can use wildcard URLs: ```python theme={null} # Single wildcard for dynamic file names resource_api = AgentAPIAttributes( endpoints=[ # Matches: /files/report-001.pdf, /files/report-2024.pdf, etc. {"GET": "https://storage.example.com/files/report-*.pdf"}, # Matches: /data/dataset_v1.csv, /data/dataset_v2.csv, etc. {"GET": "https://storage.example.com/data/dataset_*.csv"} ], agent_definition_url="https://storage.example.com/manifest.json" ) ``` ### Wildcard Patterns | Pattern | Matches | | -------------- | ------------------------------- | | `*.pdf` | Any PDF file | | `report-*.pdf` | report-001.pdf, report-2024.pdf | | `data/*.csv` | All CSV files in data folder | | `v*/data.json` | v1/data.json, v2/data.json | ## Complete Example: Dataset Publishing ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.common.types import AgentMetadata, AgentAPIAttributes, PlanMetadata from payments_py.plans import get_erc20_price_config, get_fixed_credits_config # Initialize payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:your-key", environment="sandbox") ) ERC20_TOKEN = "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" builder_address = payments.account_address # 1. Create a plan for the dataset plan_result = payments.plans.register_credits_plan( plan_metadata=PlanMetadata( name="Dataset Access Plan", description="Access to premium ML datasets" ), price_config=get_erc20_price_config(100, ERC20_TOKEN, builder_address), credits_config=get_fixed_credits_config(10) # 10 downloads ) plan_id = plan_result['planId'] # 2. Register the dataset resource dataset_result = payments.agents.register_agent( agent_metadata=AgentMetadata( name="ML Training Datasets", description="High-quality labeled datasets for machine learning", tags=["dataset", "ml", "training", "labeled"] ), agent_api=AgentAPIAttributes( endpoints=[ {"GET": "https://storage.example.com/datasets/images/*.zip"}, {"GET": "https://storage.example.com/datasets/text/*.jsonl"}, {"GET": "https://storage.example.com/datasets/audio/*.tar.gz"} ], agent_definition_url="https://storage.example.com/datasets/catalog.json" ), payment_plans=[plan_id] ) print(f"Dataset Resource ID: {dataset_result['agentId']}") ``` ## Example: Document Repository ```python theme={null} # Publishing a document repository doc_result = payments.agents.register_agent( agent_metadata=AgentMetadata( name="Research Papers Collection", description="Exclusive research papers and technical reports", tags=["research", "papers", "technical"] ), agent_api=AgentAPIAttributes( endpoints=[ # Specific documents {"GET": "https://docs.example.com/papers/whitepaper-2024.pdf"}, {"GET": "https://docs.example.com/papers/research-findings.pdf"}, # Wildcard for quarterly reports {"GET": "https://docs.example.com/reports/q*-report.pdf"}, # All supplementary materials {"GET": "https://docs.example.com/supplementary/*.zip"} ], agent_definition_url="https://docs.example.com/index.json" ), payment_plans=[plan_id] ) ``` ## Example: Software Distribution ```python theme={null} # Publishing software packages software_result = payments.agents.register_agent( agent_metadata=AgentMetadata( name="Premium Software Tools", description="Professional development tools and utilities", tags=["software", "tools", "professional"] ), agent_api=AgentAPIAttributes( endpoints=[ # Version-specific downloads {"GET": "https://releases.example.com/tool/v*/tool-*.zip"}, # Platform-specific binaries {"GET": "https://releases.example.com/tool/latest/tool-linux-*.tar.gz"}, {"GET": "https://releases.example.com/tool/latest/tool-macos-*.dmg"}, {"GET": "https://releases.example.com/tool/latest/tool-windows-*.exe"}, # Documentation {"GET": "https://releases.example.com/tool/docs/*.pdf"} ], agent_definition_url="https://releases.example.com/tool/manifest.json" ), payment_plans=[plan_id] ) ``` ## Accessing Static Resources For details on how subscribers access static resources using x402 access tokens, see [Request Validation](/docs/api-reference/python/validation-module). ## Best Practices 1. **Use descriptive metadata**: Include clear names, descriptions, and tags for discoverability 2. **Organize files logically**: Use consistent URL patterns that work well with wildcards 3. **Version your resources**: Include version numbers in URLs for easy updates 4. **Provide a manifest**: Create a JSON manifest at the `agent_definition_url` listing all available files 5. **Set appropriate credits**: Consider the value and size of files when setting credit costs ## Next Steps Order plans and check balances Access resources with tokens # Request Validation Source: https://docs.nevermined.app/docs/api-reference/python/validation-module Validate incoming requests and access tokens This guide explains how AI agents can validate incoming requests using the Nevermined Payments Python SDK. ## Overview When an agent receives a request, it needs to: 1. Extract the x402 access token from the request 2. Verify the subscriber has valid permissions 3. Optionally settle (burn) credits after processing The SDK provides the Facilitator API for these operations. ## Receiving Requests Agents receive requests with the x402 access token in the `payment-signature` header (per [x402 v2 HTTP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md)): ```python theme={null} from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/tasks', methods=['POST']) def handle_task(): # Extract x402 token from payment-signature header x402_token = request.headers.get('payment-signature', '') if not x402_token: return jsonify({'error': 'Missing payment-signature header'}), 402 # Validate and process... ``` ## Validating Requests with Facilitator ### Build Payment Required Object First, build the payment requirement specification: ```python theme={null} from payments_py.x402.helpers import build_payment_required payment_required = build_payment_required( plan_id="your-plan-id", endpoint="https://your-api.com/tasks", agent_id="your-agent-id", http_verb="POST" ) ``` ### Verify Permissions Check if the subscriber has valid permissions without burning credits: ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) # Verify the request verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" # Optional: max credits to verify ) if verification.is_valid: print("Request is valid!") print(f"Subscriber: {verification.subscriber_address}") else: print(f"Invalid request: {verification.error}") ``` ### Settle Permissions After successfully processing a request, burn the credits: ```python theme={null} settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" # Credits to burn ) if settlement.success: print(f"Credits redeemed: {settlement.credits_redeemed}") print(f"Transaction: {settlement.tx_hash}") ``` ## Complete Example: Flask Agent ```python theme={null} from flask import Flask, request, jsonify from payments_py import Payments, PaymentOptions from payments_py.x402.helpers import build_payment_required from payments_py.common.payments_error import PaymentsError app = Flask(__name__) # Initialize payments payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) AGENT_ID = "your-agent-id" PLAN_ID = "your-plan-id" @app.route('/api/tasks', methods=['POST']) def create_task(): # 1. Extract x402 access token from payment-signature header access_token = request.headers.get('payment-signature', '') if not access_token: return jsonify({'error': 'Missing payment-signature header'}), 402 # 2. Build payment required object payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=request.url, agent_id=AGENT_ID, http_verb=request.method ) try: # 3. Verify permissions verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" ) if not verification.is_valid: return jsonify({ 'error': 'Payment verification failed', 'details': verification.error }), 402 # 4. Process the request task_data = request.json result = process_task(task_data) # 5. Settle (burn credits) after successful processing settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" ) return jsonify({ 'result': result, 'credits_used': settlement.credits_redeemed }) except PaymentsError as e: return jsonify({'error': str(e)}), 402 def process_task(data): # Your AI logic here return {"status": "completed", "output": "Task result"} if __name__ == '__main__': app.run(port=8080) ``` ## FastAPI Example with Manual Validation ```python theme={null} from fastapi import FastAPI, Request, HTTPException from payments_py import Payments, PaymentOptions from payments_py.x402.helpers import build_payment_required app = FastAPI() payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) AGENT_ID = "your-agent-id" PLAN_ID = "your-plan-id" async def validate_payment(request: Request) -> dict: """Validate payment and return verification result.""" # Extract x402 token from payment-signature header access_token = request.headers.get('payment-signature', '') if not access_token: raise HTTPException(status_code=402, detail="Missing payment-signature header") payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=str(request.url), agent_id=AGENT_ID, http_verb=request.method ) verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" ) if not verification.is_valid: raise HTTPException(status_code=402, detail="Payment verification failed") return { "access_token": access_token, "payment_required": payment_required, "verification": verification } @app.post("/api/tasks") async def create_task(request: Request, body: dict): # Validate payment payment_info = await validate_payment(request) # Process task result = {"output": f"Processed: {body}"} # Settle credits settlement = payments.facilitator.settle_permissions( payment_required=payment_info["payment_required"], x402_access_token=payment_info["access_token"], max_amount="1" ) return { "result": result, "credits_used": settlement.credits_redeemed } ``` ## Using x402 FastAPI Middleware For FastAPI applications, use the built-in x402 middleware: ```python theme={null} from fastapi import FastAPI from payments_py.x402.fastapi import X402Middleware app = FastAPI() # Add x402 middleware app.add_middleware( X402Middleware, nvm_api_key="nvm:agent-key", environment="sandbox", agent_id="your-agent-id", plan_id="your-plan-id" ) @app.post("/api/tasks") async def create_task(body: dict): # Middleware handles verification automatically # Just process the request return {"result": "Task completed"} ``` ## Verification Response The `verify_permissions` method returns: | Field | Type | Description | | -------------------- | ------ | --------------------------------- | | `is_valid` | `bool` | Whether the request is authorized | | `subscriber_address` | `str` | Subscriber's wallet address | | `plan_id` | `str` | Plan being used | | `balance` | `int` | Current credit balance | | `error` | `str` | Error message if invalid | ## Settlement Response The `settle_permissions` method returns: | Field | Type | Description | | ------------------- | ------ | ---------------------------- | | `success` | `bool` | Whether settlement succeeded | | `credits_redeemed` | `int` | Number of credits burned | | `tx_hash` | `str` | Blockchain transaction hash | | `remaining_balance` | `int` | Credits remaining | ## Best Practices 1. **Always verify before processing**: Don't process expensive operations without verification 2. **Handle errors gracefully**: Return 402 Payment Required with helpful error messages 3. **Settle after completion**: Only burn credits after successfully completing the request 4. **Log transactions**: Keep records of verification and settlement for debugging 5. **Use middleware for consistency**: Apply validation uniformly across all endpoints ## Next Steps Build MCP servers with payment validation Deep dive into x402 protocol # x402 Protocol Source: https://docs.nevermined.app/docs/api-reference/python/x402-module Use x402 protocol for payment verification and settlement This guide covers the x402 payment protocol for verifying permissions and settling payments. ## Overview x402 is a payment protocol that enables: * **Permission Generation**: Subscribers create access tokens for agents * **Permission Verification**: Agents verify tokens without burning credits * **Permission Settlement**: Agents burn credits after completing work The protocol is named after HTTP status code 402 (Payment Required). ## Generate Payment Permissions ### From Nevermined App The easiest way to generate permissions is through the [Nevermined App Permissions page](https://nevermined.app/permissions/agent-permissions): 1. Navigate to the permissions page 2. Select your plan and agent 3. Configure limits (optional) 4. Generate the access token ### From SDK ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) # Basic token generation result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id" ) access_token = result['accessToken'] # Advanced: with limits result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", redemption_limit=100, # Max 100 requests order_limit="1000000000000000000", # Max 1 token spend expiration="2025-12-31T23:59:59Z" # Expires end of year ) ``` ### Token Structure The x402 token is a base64-encoded JSON document: ```json theme={null} { "payload": { "authorization": { "from": "0xSubscriberAddress", "planId": "plan-123", "agentId": "agent-456" }, "sessionKey": { "address": "0xSessionKeyAddress", "permissions": ["order", "burn"], "limits": { "redemptionLimit": 100, "orderLimit": "1000000000000000000" } } }, "signature": "0x..." } ``` ## Verify Payment Permissions Verification checks if a subscriber has valid permissions without burning credits: ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.x402.helpers import build_payment_required payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) # Build the 402 Payment Required specification payment_required = build_payment_required( plan_id="your-plan-id", endpoint="https://your-api.com/endpoint", agent_id="your-agent-id", http_verb="POST" ) # Verify the token verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1" # Optional: max credits to verify ) if verification.is_valid: print(f"Valid! Subscriber: {verification.subscriber_address}") print(f"Balance: {verification.balance}") else: print(f"Invalid: {verification.error}") ``` ### Verification Response | Field | Type | Description | | -------------------- | ------ | --------------------------- | | `is_valid` | `bool` | Whether verification passed | | `subscriber_address` | `str` | Subscriber's wallet address | | `plan_id` | `str` | Plan being used | | `balance` | `int` | Current credit balance | | `error` | `str` | Error message if invalid | ## Settle Payment Permissions Settlement burns credits after successfully processing a request: ```python theme={null} # After processing the request successfully settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount="1", # Credits to burn agent_request_id="request-123" # Optional: for tracking ) if settlement.success: print(f"Settled! Credits burned: {settlement.credits_redeemed}") print(f"Transaction: {settlement.tx_hash}") print(f"Remaining: {settlement.remaining_balance}") else: print(f"Settlement failed: {settlement.error}") ``` ### Settlement Response | Field | Type | Description | | ------------------- | ------ | ---------------------------- | | `success` | `bool` | Whether settlement succeeded | | `credits_redeemed` | `int` | Credits that were burned | | `tx_hash` | `str` | Blockchain transaction hash | | `remaining_balance` | `int` | Credits remaining | ## Payment Required Object The `X402PaymentRequired` object specifies what payment is required: ```python theme={null} from payments_py.x402.types import X402PaymentRequired, X402Scheme payment_required = X402PaymentRequired( x402_version=2, accepts=[ X402Scheme( scheme="nvm:erc4337", network="eip155:84532", # Base Sepolia plan_id="your-plan-id" ) ], extensions={} ) ``` ### Using the Helper ```python theme={null} from payments_py.x402.helpers import build_payment_required # Simpler way to build payment required payment_required = build_payment_required( plan_id="your-plan-id", endpoint="https://api.example.com/tasks", agent_id="agent-123", http_verb="POST" ) ``` ## Complete Workflow Example ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.x402.helpers import build_payment_required from flask import Flask, request, jsonify app = Flask(__name__) # Agent's payments instance agent_payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) PLAN_ID = "your-plan-id" AGENT_ID = "your-agent-id" @app.route('/api/process', methods=['POST']) def process_request(): # 1. Extract x402 token from payment-signature header token = request.headers.get('payment-signature', '') if not token: return jsonify({'error': 'Missing payment-signature header'}), 402 # 2. Build payment requirement payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=request.url, agent_id=AGENT_ID, http_verb=request.method ) # 3. Verify (doesn't burn credits) verification = agent_payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=token, max_amount="1" ) if not verification.is_valid: return jsonify({ 'error': 'Payment required', 'details': verification.error, 'paymentRequired': payment_required.model_dump() }), 402 # 4. Process the request try: result = do_expensive_work(request.json) except Exception as e: # Don't settle on failure return jsonify({'error': str(e)}), 500 # 5. Settle (burn credits) on success settlement = agent_payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=token, max_amount="1" ) return jsonify({ 'result': result, 'creditsUsed': settlement.credits_redeemed, 'remainingBalance': settlement.remaining_balance }) def do_expensive_work(data): # Your processing logic return {'processed': True} if __name__ == '__main__': app.run(port=8080) ``` ## HTTP Flow ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Subscriber │ │ Agent │ │ Nevermined │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ GET /api/process │ │ │ (no token) │ │ │ ─────────────────────────────► │ │ │ │ │ │ ◄───────────────────────────── │ │ │ 402 Payment Required │ │ │ {paymentRequired: {...}} │ │ │ │ │ │ get_x402_access_token() │ │ │ ─────────────────────────────────────────────────────────────► │ │ │ │ │ ◄───────────────────────────────────────────────────────────── │ │ {accessToken: "..."} │ │ │ │ │ │ GET /api/process │ │ │ payment-signature: │ │ │ ─────────────────────────────► │ │ │ │ │ │ │ verify_permissions() │ │ │ ─────────────────────────────► │ │ │ │ │ │ ◄───────────────────────────── │ │ │ {isValid: true} │ │ │ │ │ │ [process request] │ │ │ │ │ │ settle_permissions() │ │ │ ─────────────────────────────► │ │ │ │ │ │ ◄───────────────────────────── │ │ │ {success: true} │ │ │ │ │ ◄───────────────────────────── │ │ │ 200 OK {result: ...} │ │ │ │ │ ``` ## Best Practices 1. **Always verify before processing**: Don't do expensive work without verification 2. **Only settle on success**: Don't burn credits if processing fails 3. **Use agent\_request\_id**: Include request IDs for tracking and debugging 4. **Handle 402 responses**: Return proper payment required responses with scheme info 5. **Cache verifications carefully**: Tokens can be used multiple times until limits are reached ## Error Codes | Error | Description | Resolution | | ---------------------- | ------------------ | -------------------- | | `invalid_token` | Token is malformed | Generate a new token | | `expired_token` | Token has expired | Generate a new token | | `insufficient_balance` | Not enough credits | Order more credits | | `invalid_plan` | Plan ID mismatch | Use correct plan ID | | `invalid_agent` | Agent ID mismatch | Use correct agent ID | ## Next Steps More validation patterns x402 with MCP servers # A2A Integration Source: https://docs.nevermined.app/docs/api-reference/typescript/a2a-integration Enable agent-to-agent communication with payment-protected streaming and task execution # A2A Integration The Agent-to-Agent (A2A) protocol integration enables AI agents to communicate with each other using payment-protected message streams. The Nevermined Payments Library provides complete A2A server functionality with automatic payment handling. ## Overview of A2A Integration The A2A integration provides: * **Agent Card**: Payment metadata extension for discovery * **Streaming Support**: Real-time task updates via Server-Sent Events * **Task Management**: Async execution with status tracking * **Credit Burning**: Automatic credit settlement after task completion * **EventBus Pattern**: Clean event-driven architecture for task execution ## Build Payment Agent Card The agent card is published at `/.well-known/agent.json` and includes payment metadata: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Build agent card with payment extension const agentCard = payments.a2a.buildPaymentAgentCard( { name: 'Weather AI Agent', description: 'Real-time weather forecasts and analysis', version: '1.0.0', protocolVersion: '0.3.0', url: 'http://localhost:6000/a2a/', capabilities: { streaming: true, pushNotifications: false, }, defaultInputModes: ['text'], defaultOutputModes: ['text'], skills: [], }, { agentId: process.env.NVM_AGENT_ID!, planId: process.env.NVM_PLAN_ID!, credits: 5, // Fixed credit cost paymentType: 'fixed', // 'fixed' or 'dynamic' costDescription: '5 credits per request (optional)', } ) ``` ## Payment Extension Structure The agent card includes a Nevermined payment extension in `capabilities.extensions`: ```json theme={null} { "capabilities": { "streaming": true, "extensions": [ { "uri": "urn:nevermined:payment", "required": false, "params": { "paymentType": "fixed", "credits": 5, "agentId": "did:nv:agent-123", "planId": "plan-456", "costDescription": "5 credits per request" } } ] } } ``` ## Implement Executor The executor contains your agent's business logic and emits events via the EventBus: ```typescript theme={null} const executor = { async execute(context, eventBus) { const taskId = context.task?.id // Emit working status eventBus.publish({ kind: 'status-update', taskId, status: { state: 'working' }, final: false, }) try { // Process the task const userMessage = context.history?.[0] const prompt = userMessage?.parts?.find(p => p.kind === 'text')?.text const result = await processPrompt(prompt) // Emit completion with credits used eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: { role: 'agent', parts: [{ kind: 'text', text: result }], }, }, final: true, metadata: { creditsUsed: 5 }, // Report credits consumed }) } catch (error) { // Emit failure eventBus.publish({ kind: 'status-update', taskId, status: { state: 'failed', message: { role: 'agent', parts: [{ kind: 'text', text: `Error: ${error.message}` }], }, }, final: true, }) } // Signal completion eventBus.finished() }, async cancelTask(taskId, eventBus) { // Handle task cancellation eventBus.publish({ kind: 'status-update', taskId, status: { state: 'canceled' }, final: true, }) eventBus.finished() }, } ``` ## EventBus Events The executor communicates via event publishing: ### Task Event ```typescript theme={null} eventBus.publish({ kind: 'task', id: taskId, contextId: contextId, status: { state: 'working' }, history: [userMessage], }) ``` ### Status Update Event ```typescript theme={null} eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', // or 'working', 'failed', 'canceled' message: { role: 'agent', parts: [{ kind: 'text', text: 'Result here' }], }, }, final: true, // Set true when task is done metadata: { creditsUsed: 5, // Required for credit settlement }, }) ``` ### Message Event (Streaming) ```typescript theme={null} eventBus.publish({ kind: 'message', messageId: generateId(), role: 'agent', parts: [{ kind: 'text', text: 'Partial result...' }], }) ``` ### Signal Completion ```typescript theme={null} eventBus.finished() // Must call when done ``` ## Start A2A Server Start the complete A2A server with payment integration: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Build agent card const agentCard = payments.a2a.buildPaymentAgentCard( { name: 'Weather Agent', description: 'Weather forecasts and analysis', version: '1.0.0', protocolVersion: '0.3.0', url: 'http://localhost:6000/a2a/', capabilities: { streaming: true, pushNotifications: false }, defaultInputModes: ['text'], defaultOutputModes: ['text'], skills: [], }, { agentId: process.env.NVM_AGENT_ID!, planId: process.env.NVM_PLAN_ID!, credits: 5, paymentType: 'fixed', } ) // Define executor const executor = { async execute(context, eventBus) { const taskId = context.task?.id eventBus.publish({ kind: 'status-update', taskId, status: { state: 'working' }, final: false, }) // Your logic here const result = await processTask(context) eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: { role: 'agent', parts: [{ kind: 'text', text: result }] }, }, final: true, metadata: { creditsUsed: 5 }, }) eventBus.finished() }, async cancelTask(taskId, eventBus) { // Handle cancellation }, } // Start server const { server, close } = await payments.a2a.start({ agentCard, executor, port: 6000, basePath: '/a2a/', exposeAgentCard: true, // Expose /.well-known/agent.json exposeDefaultRoutes: true, // Expose health and info routes }) console.log('A2A Server running on port 6000') // Graceful shutdown process.on('SIGINT', async () => { await close() console.log('Server stopped') process.exit(0) }) ``` ## Credit Reporting The executor reports credits used in the final status update: ### Fixed Credits ```typescript theme={null} eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: {...} }, final: true, metadata: { creditsUsed: 5 }, // Always 5 credits }) ``` ### Dynamic Credits Calculate credits based on actual usage: ```typescript theme={null} async execute(context, eventBus) { const taskId = context.task?.id // Execute task const result = await processTask(context) // Calculate credits based on result const creditsUsed = calculateCredits(result) eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: {...} }, final: true, metadata: { creditsUsed }, // Variable credits }) eventBus.finished() } function calculateCredits(result: string): number { // Example: charge based on response length return Math.ceil(result.length / 1000) // 1 credit per KB } ``` ## Streaming Example For long-running tasks, stream partial results: ```typescript theme={null} const executor = { async execute(context, eventBus) { const taskId = context.task?.id eventBus.publish({ kind: 'status-update', taskId, status: { state: 'working' }, final: false, }) // Stream partial results const chunks = await processInChunks(context) for (const chunk of chunks) { eventBus.publish({ kind: 'message', messageId: generateId(), role: 'agent', parts: [{ kind: 'text', text: chunk }], }) } // Final result eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: { role: 'agent', parts: [{ kind: 'text', text: 'Done!' }] }, }, final: true, metadata: { creditsUsed: 5 }, }) eventBus.finished() }, } ``` ## Complete Example: Weather A2A Agent ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Build agent card const agentCard = payments.a2a.buildPaymentAgentCard( { name: 'Weather Forecast Agent', description: 'Get weather forecasts for any location', version: '1.0.0', protocolVersion: '0.3.0', url: 'http://localhost:6000/a2a/', capabilities: { streaming: true, pushNotifications: false, }, defaultInputModes: ['text'], defaultOutputModes: ['text'], skills: [ { name: 'weather_forecast', description: 'Get weather forecast', }, ], }, { agentId: process.env.NVM_AGENT_ID!, planId: process.env.NVM_PLAN_ID!, credits: 3, paymentType: 'fixed', costDescription: '3 credits per forecast request', } ) // Implement executor const executor = { async execute(context, eventBus) { const taskId = context.task?.id // Extract user message const userMessage = context.history?.[0] const prompt = userMessage?.parts?.find(p => p.kind === 'text')?.text if (!prompt) { eventBus.publish({ kind: 'status-update', taskId, status: { state: 'failed', message: { role: 'agent', parts: [{ kind: 'text', text: 'No prompt provided' }], }, }, final: true, }) eventBus.finished() return } // Emit working status eventBus.publish({ kind: 'status-update', taskId, status: { state: 'working' }, final: false, }) try { // Process weather request const city = extractCity(prompt) const forecast = await getWeatherForecast(city) // Emit completion eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed', message: { role: 'agent', parts: [{ kind: 'text', text: formatForecast(forecast) }], }, }, final: true, metadata: { creditsUsed: 3 }, }) } catch (error) { eventBus.publish({ kind: 'status-update', taskId, status: { state: 'failed', message: { role: 'agent', parts: [{ kind: 'text', text: `Error: ${error.message}` }], }, }, final: true, }) } eventBus.finished() }, async cancelTask(taskId, eventBus) { eventBus.publish({ kind: 'status-update', taskId, status: { state: 'canceled' }, final: true, }) eventBus.finished() }, } // Start server const { server, close } = await payments.a2a.start({ agentCard, executor, port: 6000, basePath: '/a2a/', }) console.log('Weather A2A Agent running on http://localhost:6000/a2a/') console.log('Agent card: http://localhost:6000/a2a/.well-known/agent.json') // Graceful shutdown process.on('SIGINT', async () => { console.log('Shutting down...') await close() process.exit(0) }) // Helper functions function extractCity(prompt: string): string { // Simple extraction logic const match = prompt.match(/weather (?:in|for) (.+)/i) return match?.[1] || 'San Francisco' } async function getWeatherForecast(city: string) { // Mock weather API call return { city, temperature: 72, condition: 'Sunny', forecast: ['Mon: 70°F', 'Tue: 68°F', 'Wed: 73°F'], } } function formatForecast(forecast: any): string { return `Weather in ${forecast.city}: Current: ${forecast.temperature}°F, ${forecast.condition} Forecast: ${forecast.forecast.join(', ')}` } ``` ## Server Configuration Options ```typescript theme={null} const { server, close } = await payments.a2a.start({ agentCard, // Payment agent card (required) executor, // Task executor (required) port: 6000, // Server port (required) basePath: '/a2a/', // Base path (optional, default: '/') exposeAgentCard: true, // Expose /.well-known/agent.json (optional) exposeDefaultRoutes: true, // Expose /health, /info (optional) paymentsService: payments, // Custom payments instance (optional) handlerOptions: { asyncExecution: false, // Async task execution (optional) defaultBatch: false, // Batch credit settlement (optional) defaultMarginPercent: 5, // Add 5% margin to credits (optional) }, }) ``` ## Best Practices 1. **Always Report Credits**: Include `creditsUsed` in final status updates 2. **Handle Errors**: Emit 'failed' status on errors 3. **Signal Completion**: Always call `eventBus.finished()` 4. **Streaming for Long Tasks**: Use message events for real-time updates 5. **Graceful Shutdown**: Implement proper server cleanup 6. **Descriptive Agent Cards**: Provide clear descriptions and skills 7. **Version Control**: Include version in agent card metadata ## Related Documentation * [Agents](./agents) - Register your A2A agent in Nevermined * [Payment Plans](./payment-plans) - Configure pricing for your agent * [Validation of Requests](./validation-of-requests) - Understanding credit settlement *** **Source References**: * `RUN.md` (A2A Server section, lines 88-156) * `src/a2a/server.ts` (PaymentsA2AServer) * `src/a2a/agent-card.ts` (buildPaymentAgentCard) * `tests/integration/a2a/complete-message-send-flow.test.ts` (executor patterns) * `tests/e2e/helpers/a2a-setup-helpers.ts` (setup examples) # Agents Source: https://docs.nevermined.app/docs/api-reference/typescript/agents Register and manage AI agents with payment plans, metadata, and API endpoints # Agents The Agents API allows AI builders to register their AI agents with the Nevermined protocol, making them discoverable and accessible through payment plans. ## Overview of Agents API The Agents API (`payments.agents`) enables you to: * Register AI agents with metadata and API endpoints * Associate agents with one or more payment plans * Register agents and plans together in a single operation * Update agent metadata and configuration * Retrieve agent information * Manage plan associations (add/remove plans from agents) ## Agent Metadata Structure Agents require metadata for discovery and identification: ```typescript theme={null} import { AgentMetadata } from '@nevermined-io/payments' const agentMetadata: AgentMetadata = { name: 'Weather AI Agent', description: 'Provides real-time weather forecasts and climate data', tags: ['weather', 'forecast', 'climate'], dateCreated: new Date(), author: 'Your Company Name', // Additional metadata fields... } ``` ## Agent API Configuration Define how your agent's API endpoints can be accessed: ```typescript theme={null} import { AgentAPIAttributes } from '@nevermined-io/payments' const agentApi: AgentAPIAttributes = { endpoints: [ { POST: 'https://api.example.com/v1/agents/:agentId/tasks' }, { GET: 'https://api.example.com/v1/agents/:agentId/tasks/:taskId' }, ], openEndpoints: [], // Endpoints that don't require authentication authType: 'bearer', // Authentication type: 'none', 'basic', 'bearer', 'oauth' agentDefinitionUrl: 'https://api.example.com/openapi.json', // Optional OpenAPI spec } ``` ### Endpoint Patterns You can use path parameters in your endpoint definitions: ```typescript theme={null} const agentApi = { endpoints: [ { POST: 'https://api.example.com/agents/:agentId/query' }, { GET: 'https://api.example.com/agents/:agentId/status' }, { POST: 'https://api.example.com/agents/:agentId/tasks/:taskId/cancel' }, ], } ``` ## Register Agents ### Register Agent with Existing Plans If you've already registered payment plans, you can register an agent and associate it with those plans: ```typescript theme={null} import { Payments, AgentMetadata, AgentAPIAttributes } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) // Define agent metadata const agentMetadata: AgentMetadata = { name: 'Weather Forecast Agent', description: 'Get accurate weather forecasts for any location', tags: ['weather', 'forecast', 'api'], } // Configure API endpoints const agentApi: AgentAPIAttributes = { endpoints: [ { POST: 'https://weather-api.example.com/v1/agents/:agentId/forecast' }, { GET: 'https://weather-api.example.com/v1/agents/:agentId/history' }, ], openEndpoints: [], authType: 'bearer', agentDefinitionUrl: 'https://weather-api.example.com/openapi.json', } // Register agent with existing plans const { agentId } = await payments.agents.registerAgent( agentMetadata, agentApi, [planId1, planId2] // Array of plan IDs ) console.log(`Agent registered with ID: ${agentId}`) ``` ### Register Agent and Plan Together For convenience, you can register both an agent and its payment plan in a single operation: ```typescript theme={null} import { Payments, AgentMetadata, AgentAPIAttributes, PlanMetadata } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) const builderAddress = payments.getAccountAddress() const ERC20_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // Define plan const planMetadata: PlanMetadata = { name: 'Weather Agent Access Plan', description: 'Get 1000 credits to access weather data', } const priceConfig = payments.plans.getCryptoPriceConfig( 10_000_000n, builderAddress, ERC20_ADDRESS ) const creditsConfig = payments.plans.getFixedCreditsConfig(1000n, 1n) // Define agent const agentMetadata: AgentMetadata = { name: 'Weather Forecast Agent', description: 'Real-time weather forecasts powered by AI', tags: ['weather', 'ai', 'forecast'], } const agentApi: AgentAPIAttributes = { endpoints: [ { POST: 'https://weather-api.example.com/v1/agents/:agentId/tasks' }, ], agentDefinitionUrl: 'https://weather-api.example.com/openapi.json', } // Register both together const { agentId, planId } = await payments.agents.registerAgentAndPlan( agentMetadata, agentApi, planMetadata, priceConfig, creditsConfig ) console.log(`Agent ID: ${agentId}`) console.log(`Plan ID: ${planId}`) ``` ## Retrieve Agents ### Get a Specific Agent ```typescript theme={null} // Get agent details by ID const agent = await payments.agents.getAgent(agentId) console.log(`Name: ${agent.name}`) console.log(`Description: ${agent.description}`) console.log(`Endpoints: ${JSON.stringify(agent.endpoints)}`) ``` ### Get All Your Agents ```typescript theme={null} // Get all agents owned by your account const agents = await payments.agents.getAgents() agents.forEach(agent => { console.log(`${agent.name}: ${agent.agentId}`) }) ``` ### Get Plans for an Agent ```typescript theme={null} // Get all payment plans associated with an agent const plans = await payments.agents.getPlansForAgent(agentId) plans.forEach(plan => { console.log(`Plan: ${plan.name} (${plan.planId})`) }) ``` ## Update Agent Metadata You can update agent metadata and API configuration after registration: ```typescript theme={null} // Update agent information await payments.agents.updateAgent( agentId, { description: 'Updated description with new features', tags: ['weather', 'forecast', 'climate', 'ai-powered'], }, { endpoints: [ { POST: 'https://weather-api.example.com/v2/agents/:agentId/forecast' }, { GET: 'https://weather-api.example.com/v2/agents/:agentId/history' }, { GET: 'https://weather-api.example.com/v2/agents/:agentId/alerts' }, // New endpoint ], } ) console.log('Agent updated successfully') ``` ## Manage Agent Plans ### Add Plans to an Agent Associate additional payment plans with an existing agent: ```typescript theme={null} // Add new plans to an agent await payments.agents.addPlanToAgent(agentId, newPlanId) console.log(`Added plan ${newPlanId} to agent ${agentId}`) ``` ### Remove Plans from an Agent Remove payment plan associations: ```typescript theme={null} // Remove a plan from an agent await payments.agents.removePlanFromAgent(agentId, planIdToRemove) console.log(`Removed plan ${planIdToRemove} from agent ${agentId}`) ``` ## Authentication Types The Agents API supports multiple authentication types: ```typescript theme={null} // No authentication required const agentApi = { endpoints: [{ GET: 'https://api.example.com/public' }], authType: 'none', } // Basic authentication const agentApi = { endpoints: [{ POST: 'https://api.example.com/agent' }], authType: 'basic', } // Bearer token (most common for AI agents) const agentApi = { endpoints: [{ POST: 'https://api.example.com/agent' }], authType: 'bearer', // X402 access token in Authorization header } // OAuth const agentApi = { endpoints: [{ POST: 'https://api.example.com/agent' }], authType: 'oauth', } ``` ## Complete Example: Multi-Endpoint Agent ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { AgentMetadata, AgentAPIAttributes } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Comprehensive agent configuration const agentMetadata: AgentMetadata = { name: 'Advanced Weather AI', description: 'Multi-modal weather analysis with forecasts, alerts, and historical data', tags: ['weather', 'forecast', 'climate', 'alerts', 'ai'], author: 'Weather Tech Inc.', dateCreated: new Date(), } const agentApi: AgentAPIAttributes = { endpoints: [ { POST: 'https://api.weather-tech.com/v1/agents/:agentId/forecast' }, { GET: 'https://api.weather-tech.com/v1/agents/:agentId/forecast/:forecastId' }, { POST: 'https://api.weather-tech.com/v1/agents/:agentId/alerts' }, { GET: 'https://api.weather-tech.com/v1/agents/:agentId/history' }, { DELETE: 'https://api.weather-tech.com/v1/agents/:agentId/alerts/:alertId' }, ], openEndpoints: [ 'https://api.weather-tech.com/v1/health', 'https://api.weather-tech.com/v1/status', ], authType: 'bearer', agentDefinitionUrl: 'https://api.weather-tech.com/openapi.json', } // Register with multiple plans const { agentId } = await payments.agents.registerAgent( agentMetadata, agentApi, [basicPlanId, proPlanId, enterprisePlanId] ) console.log(`Advanced agent registered: ${agentId}`) // Retrieve and verify const agent = await payments.agents.getAgent(agentId) const plans = await payments.agents.getPlansForAgent(agentId) console.log(`Agent has ${plans.length} associated plans`) ``` ## Best Practices 1. **Descriptive Metadata**: Use clear names and descriptions for better discoverability 2. **Relevant Tags**: Add tags that help users find your agent 3. **RESTful Endpoints**: Follow REST conventions for endpoint paths 4. **OpenAPI Specs**: Provide agent definition URLs for better integration 5. **Multiple Plans**: Offer different pricing tiers for various use cases 6. **Bearer Auth**: Use bearer token authentication for secure access ## Related Documentation * [Payment Plans](./payment-plans) - Create plans to associate with agents * [Publishing Static Resources](./publishing-static-resources) - Register static content agents * [Validation of Requests](./validation-of-requests) - Validate incoming agent requests *** **Source References**: * `src/api/agents-api.ts` (Agents API methods) * `tests/e2e/test_payments_e2e.test.ts` (lines 373-449) * `tests/e2e/test_x402_e2e.test.ts` (agent registration examples) # Getting Started Source: https://docs.nevermined.app/docs/api-reference/typescript/initializing-the-library Initialize the Payments SDK in Node.js and browser environments with your API key # Initializing the Library This guide shows how to initialize the Nevermined Payments Library in your application. The library supports both server-side (Node.js) and browser environments with different initialization methods. ## Get Your NVM API Key Before initializing the library, you need a Nevermined API key: 1. Visit [nevermined.app](https://nevermined.app) 2. Sign in or create a free account 3. Navigate to your profile settings 4. Generate an API key **Important**: There are two types of API keys: * **Builder Key**: For AI builders who register agents and payment plans * **Subscriber Key**: For users who purchase access and query agents Most applications will use a builder key for server-side operations. ## Server-Side Initialization For Node.js applications, use the `getInstance` method: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) ``` ### Configuration Options The `getInstance` method accepts a `PaymentOptions` object: ```typescript theme={null} interface PaymentOptions { nvmApiKey: string // Your Nevermined API key (required) environment: EnvironmentName // Target environment (required) returnUrl?: string // OAuth callback URL (optional, for browser) appId?: string // Your application ID (optional) version?: string // Your application version (optional) } ``` ### Complete Server-Side Example ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' // Initialize with environment variables const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT as EnvironmentName, }) // Verify connection const accountAddress = payments.getAccountAddress() console.log(`Connected as: ${accountAddress}`) ``` ## Browser Initialization For browser-based applications, use the `getBrowserInstance` method: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getBrowserInstance({ returnUrl: 'https://myapp.example/callback', environment: 'sandbox', appId: 'my-app', version: '1.0.0', }) ``` ### Browser Authentication Flow The browser instance supports OAuth-style authentication: ```typescript theme={null} // Check if user is logged in if (!payments.isLoggedIn) { // Redirect to Nevermined login payments.connect() } // After login, use the payments instance const balance = await payments.plans.getPlanBalance(planId) // Logout when needed payments.logout() ``` ## Environments The Nevermined protocol supports multiple environments: ### Production Environments | Environment | Description | Usage | | ----------- | ------------------------------ | -------------------------------------------- | | `sandbox` | Production testing environment | Development and testing with real blockchain | | `live` | Production environment | Live agents and real payments | ### Choosing an Environment ```typescript theme={null} // Development and testing const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) // Production deployment const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'live', }) ``` **Important**: Agents and plans registered in one environment cannot be accessed from another. Always use the same environment throughout your application. ## Environment Configuration Details Each environment has specific API endpoints: ```typescript theme={null} // Example environment structure interface EnvironmentInfo { frontend: string // Nevermined App URL backend: string // API URL proxy: string // Proxy service URL heliconeUrl: string // Observability service URL } ``` The library automatically configures these endpoints based on your chosen environment. ## Verify Connection After initialization, verify the connection is working: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Get account address from API key const accountAddress = payments.getAccountAddress() console.log(`Initialized for account: ${accountAddress}`) // For browser instances if (payments.isLoggedIn) { console.log('User authenticated successfully') } ``` ## Best Practices 1. **Environment Variables**: Always store API keys in environment variables, never hardcode them 2. **Singleton Pattern**: Create one Payments instance per application 3. **Environment Consistency**: Use the same environment for all operations in a session 4. **Error Handling**: Wrap initialization in try-catch blocks for production apps ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' let payments: Payments try { payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT as EnvironmentName, }) console.log('✓ Nevermined Payments initialized') } catch (error) { console.error('Failed to initialize Payments:', error) process.exit(1) } ``` ## Related Documentation * [Payment Plans](./payment-plans) * [Agents](./agents) *** **Source References**: * `src/payments.ts` (getInstance, getBrowserInstance methods) * `src/environments.ts` (environment configurations) * `tests/e2e/fixtures.ts` (createPaymentsBuilder, createPaymentsSubscriber) # Installation Source: https://docs.nevermined.app/docs/api-reference/typescript/installation Install and configure the @nevermined-io/payments TypeScript SDK ## Installation Install the package via npm or yarn: ```bash theme={null} npm install @nevermined-io/payments yarn add @nevermined-io/payments ``` ## Basic Setup ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' // or 'live' }) ``` ## Configuration Options | Option | Type | Required | Description | | ------------- | ------------- | -------- | ------------------------------- | | `nvmApiKey` | `string` | Yes | Your Nevermined API key | | `environment` | `Environment` | Yes | `'sandbox'` or `'live'` | | `appId` | `string` | No | Optional application identifier | | `version` | `string` | No | Optional version string | ## Environment Details | Environment | Network | Use Case | | ----------- | ------------ | ----------------------- | | `sandbox` | Base Sepolia | Development and testing | | `live` | Base Mainnet | Live applications | ## TypeScript Support The SDK includes full TypeScript type definitions: ```typescript theme={null} import type { Payments, Agent, Plan, PriceConfig, CreditsConfig, ValidationResult } from '@nevermined-io/payments' ``` ## Getting Your API Key 1. Go to [nevermined.app](https://nevermined.app) 2. Sign in with Web3Auth 3. Navigate to **Settings > API Keys** 4. Generate and copy your key ```bash theme={null} export NVM_API_KEY="nvm:your-api-key-here" ``` ## Next Steps Core SDK class and methods Get started quickly # MCP Integration Source: https://docs.nevermined.app/docs/api-reference/typescript/mcp-integration Build payment-protected MCP servers with OAuth 2.1 authentication and automatic credit management # MCP Integration The Model Context Protocol (MCP) integration provides a complete payment-protected MCP server with OAuth 2.1 support and automatic credit management. This is the simplest way to run a payment-protected AI agent. ## Overview of MCP Integration The MCP integration automatically handles: * **OAuth 2.1 Discovery**: Auto-configured `.well-known` endpoints * **Paywall Protection**: Automatic token verification and credit burning * **Streaming Support**: SSE (Server-Sent Events) for streaming responses * **Credit Management**: Fixed or dynamic credit costs per tool/resource/prompt * **Client Registration**: Dynamic client registration for OAuth flows ## OAuth 2.1 Discovery When you start an MCP server, the library automatically exposes OAuth 2.1 discovery endpoints required by the X402 spec: | Endpoint | Description | | ----------------------------------------- | ----------------------------- | | `/.well-known/oauth-protected-resource` | Protected resource metadata | | `/.well-known/oauth-authorization-server` | Authorization server metadata | | `/.well-known/openid-configuration` | OpenID Connect discovery | | `POST /register` | Dynamic client registration | These endpoints are generated automatically—no manual configuration required. ## Configure MCP Initialize the MCP integration with your agent details: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Configure MCP integration payments.mcp.configure({ agentId: process.env.NVM_AGENT_ID!, serverName: 'my-mcp-server', }) ``` ## Register Tools with Credits Register MCP tools and specify their credit cost: ```typescript theme={null} import { z } from 'zod' // Register a tool with fixed credit cost payments.mcp.registerTool( 'get_weather', // Tool name { title: 'Get Weather', description: 'Get current weather for a city', inputSchema: z.object({ city: z.string().describe('City name'), }), }, async (args) => { // Handler function // Your tool logic here const weather = await getWeatherData(args.city) return { content: [{ type: 'text', text: `Weather in ${args.city}: ${weather}` }], } }, { credits: 1n } // Fixed: 1 credit per call ) ``` ### Dynamic Credit Calculation For variable costs based on input/output: ```typescript theme={null} payments.mcp.registerTool( 'analyze_text', { title: 'Analyze Text', description: 'Perform text analysis', inputSchema: z.object({ text: z.string(), }), }, async (args) => { const analysis = await analyzeText(args.text) return { content: [{ type: 'text', text: JSON.stringify(analysis) }], } }, { // Dynamic credits based on text length credits: (args, result) => { const textLength = args.text.length return BigInt(Math.ceil(textLength / 1000)) // 1 credit per KB } } ) ``` ## Register Resources Register MCP resources with payment protection: ```typescript theme={null} payments.mcp.registerResource( 'weather-data', // Resource name 'weather://current', // Resource URI { title: 'Current Weather Data', description: 'Real-time weather information', }, async (uri, vars) => { // Handler function const data = await fetchWeatherData() return { contents: [{ uri: 'weather://current', mimeType: 'application/json', text: JSON.stringify(data), }], } }, { credits: 2n } // 2 credits per access ) ``` ## Register Prompts Register MCP prompts with credit costs: ```typescript theme={null} payments.mcp.registerPrompt( 'weather-query', // Prompt name { title: 'Weather Query Template', description: 'Generate weather queries', }, async (args) => { // Handler function return { messages: [{ role: 'user', content: { type: 'text', text: `What is the weather in ${args.city}?`, }, }], } }, { credits: 1n } // 1 credit per prompt ) ``` ## Start Managed Server Start a complete MCP server with all endpoints configured: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Configure payments.mcp.configure({ agentId: process.env.NVM_AGENT_ID!, serverName: 'weather-server', }) // Register tools payments.mcp.registerTool( 'get_weather', { title: 'Get Weather', description: 'Get current weather', inputSchema: z.object({ city: z.string(), }), }, async (args) => ({ content: [{ type: 'text', text: `Weather in ${args.city}: Sunny` }], }), { credits: 1n } ) // Start server const { info, stop } = await payments.mcp.start({ port: 5001, agentId: process.env.NVM_AGENT_ID!, serverName: 'weather-server', version: '1.0.0', }) console.log(`MCP Server running at: ${info.baseUrl}`) console.log(`MCP endpoint: ${info.baseUrl}/mcp`) // Graceful shutdown process.on('SIGINT', async () => { await stop() console.log('Server stopped') process.exit(0) }) ``` ## MCP Logical URIs When registering your agent at [nevermined.app](https://nevermined.app), use MCP logical URIs instead of HTTP URLs: ### URI Format ``` mcp://// ``` ### Examples | Type | URI | Description | | ----------------- | -------------------------------------------------- | --------------- | | Tool | `mcp://weather-server/tools/get_weather` | Single tool | | Tool Wildcard | `mcp://weather-server/tools/*` | All tools | | Resource | `mcp://weather-server/resources/weather://current` | Single resource | | Resource Wildcard | `mcp://weather-server/resources/*` | All resources | | Prompt | `mcp://weather-server/prompts/weather-query` | Single prompt | ### Wildcard Registration Use wildcards to register all tools/resources/prompts at once: ``` mcp://weather-server/tools/* mcp://weather-server/resources/* mcp://weather-server/prompts/* ``` ## Auto-Configured Endpoints The `start()` method automatically creates these endpoints: ``` POST /mcp - MCP JSON-RPC endpoint GET /mcp - SSE streaming endpoint DELETE /mcp - Session cleanup GET / - Server info GET /health - Health check GET /.well-known/oauth-protected-resource GET /.well-known/oauth-authorization-server GET /.well-known/openid-configuration POST /register - Dynamic client registration ``` ## Complete Example: Weather Agent ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { z } from 'zod' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Configure MCP payments.mcp.configure({ agentId: process.env.NVM_AGENT_ID!, serverName: 'weather-agent', }) // Register multiple tools payments.mcp.registerTool( 'get_current_weather', { title: 'Get Current Weather', description: 'Get real-time weather for a location', inputSchema: z.object({ city: z.string(), country: z.string().optional(), }), }, async (args) => { const weather = await fetchWeather(args.city, args.country) return { content: [{ type: 'text', text: JSON.stringify(weather) }], } }, { credits: 1n } ) payments.mcp.registerTool( 'get_forecast', { title: 'Get Weather Forecast', description: 'Get 7-day weather forecast', inputSchema: z.object({ city: z.string(), days: z.number().min(1).max(7).default(3), }), }, async (args) => { const forecast = await fetchForecast(args.city, args.days) return { content: [{ type: 'text', text: JSON.stringify(forecast) }], } }, { credits: 2n } // Forecasts cost more ) // Register resource payments.mcp.registerResource( 'weather-alerts', 'weather://alerts', { title: 'Weather Alerts', description: 'Active weather alerts', }, async (uri) => { const alerts = await fetchAlerts() return { contents: [{ uri: 'weather://alerts', mimeType: 'application/json', text: JSON.stringify(alerts), }], } }, { credits: 1n } ) // Start server const { info, stop } = await payments.mcp.start({ port: 5001, agentId: process.env.NVM_AGENT_ID!, serverName: 'weather-agent', version: '1.0.0', }) console.log(`Weather MCP Agent running at ${info.baseUrl}`) console.log(`Register in Nevermined App with:`) console.log(` - mcp://weather-agent/tools/*`) console.log(` - mcp://weather-agent/resources/*`) // Graceful shutdown process.on('SIGINT', async () => { await stop() process.exit(0) }) // Mock functions async function fetchWeather(city: string, country?: string) { return { city, temp: 72, condition: 'sunny' } } async function fetchForecast(city: string, days: number) { return { city, days, forecast: [] } } async function fetchAlerts() { return { alerts: [] } } ``` ## Handler Options | Option | Type | Description | | --------------- | ---------------------- | -------------------------------------------------------------------- | | `credits` | `bigint` or `function` | Credits to consume per call | | `planId` | `string` | Optional override for the plan ID (otherwise inferred from token) | | `maxAmount` | `bigint` | Max credits to verify during authentication (default: `1n`) | | `onRedeemError` | `string` | `'ignore'` (default) or `'propagate'` to throw on redemption failure | ## Response Metadata (`_meta`) After each paywall-protected call, the SDK injects a `_meta` field into the response following the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic). This field is always present regardless of whether credit redemption succeeded or failed: ```typescript theme={null} // Successful redemption { content: [{ type: 'text', text: 'result' }], _meta: { success: true, txHash: '0xabc...', creditsRedeemed: '5', remainingBalance: '95', planId: 'plan-123', subscriberAddress: '0x123...', } } // Failed redemption { content: [{ type: 'text', text: 'result' }], _meta: { success: false, creditsRedeemed: '0', planId: 'plan-123', subscriberAddress: '0x123...', errorReason: 'Insufficient credits', } } ``` | Field | Type | Description | | ------------------- | --------- | --------------------------------------------- | | `success` | `boolean` | Whether credit redemption succeeded | | `txHash` | `string` | Blockchain transaction hash (only on success) | | `creditsRedeemed` | `string` | Number of credits burned (`'0'` on failure) | | `remainingBalance` | `string` | Credits remaining after redemption | | `planId` | `string` | Plan used for the operation | | `subscriberAddress` | `string` | Subscriber's wallet address | | `errorReason` | `string` | Error message (only on failure) | ## Error Handling The paywall automatically returns 402 errors for invalid/missing tokens: ```typescript theme={null} // Automatic 402 handling - no code needed payments.mcp.registerTool( 'premium_tool', toolConfig, handler, { credits: 5n, onRedeemError: 'propagate', // Propagate credit errors (default: 'ignore') } ) ``` ## Advanced: Custom Express App For existing Express applications, use `createRouter`: ```typescript theme={null} import express from 'express' import { Payments } from '@nevermined-io/payments' const app = express() const payments = Payments.getInstance({...}) payments.mcp.configure({ agentId: process.env.NVM_AGENT_ID!, serverName: 'my-server', }) // Register tools... // Create MCP router const mcpRouter = payments.mcp.createRouter({ agentId: process.env.NVM_AGENT_ID!, serverName: 'my-server', }) // Mount router app.use('/mcp', mcpRouter) // Your other routes app.get('/custom', (req, res) => { res.json({ status: 'ok' }) }) app.listen(5001) ``` ## Best Practices 1. **Descriptive Names**: Use clear tool/resource/prompt names 2. **Schema Validation**: Always define proper input schemas with Zod 3. **Credit Pricing**: Set appropriate credits based on resource cost 4. **Error Messages**: Provide clear error messages in responses 5. **Graceful Shutdown**: Always implement `SIGINT` handlers 6. **Wildcard URIs**: Use wildcards for simpler agent registration 7. **Version Control**: Include version in server configuration ## Related Documentation * [Agents](./agents) - Register your MCP agent in Nevermined * [Payment Plans](./payment-plans) - Configure pricing for your agent * [Validation of Requests](./validation-of-requests) - Understanding the validation flow *** **Source References**: * `RUN.md` (MCP Server section, lines 16-86) * `src/mcp/index.ts` (MCP integration API) * `tests/integration/mcp-integration.test.ts` (integration examples) # Payment Plans Source: https://docs.nevermined.app/docs/api-reference/typescript/payment-plans Create and manage flexible payment plans with credits, time-based access, and multiple pricing options # Payment Plans Payment plans define how subscribers access your AI agents. The Nevermined Payments Library provides a flexible Plans API for registering different types of plans with various pricing and credit configurations. ## Overview of Payment Plans API The Plans API (`payments.plans`) allows AI builders to: * Define pricing structures (fiat, crypto, ERC20 tokens, or free) * Configure credit allocation (fixed, dynamic, time-based) * Register plans with the Nevermined protocol * Retrieve and manage existing plans * Associate plans with agents ## Types of Payment Plans ### Credits Plans Grant subscribers a specific number of credits to use across multiple requests. Each agent request burns a defined number of credits. **Use cases**: API calls, LLM queries, data processing tasks ### Time Plans Provide unlimited access for a fixed duration. No per-request credit deduction during the validity period. **Use cases**: Subscriptions, unlimited access periods, trial memberships ### Trial Plans Free plans with time limitations, perfect for demos and user onboarding. **Use cases**: Free trials, demonstrations, testing ### Pay-As-You-Go Plans Dynamic pricing where subscribers pay per request without pre-purchasing credits. **Use cases**: Variable usage patterns, enterprise integrations ## Price Configuration The library provides helper functions for different payment methods: ### Crypto Payment (Native Token) ```typescript theme={null} import { Payments } from '@nevermined-io/payments' import { ZeroAddress } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) const builderAddress = payments.getAccountAddress() // Native token pricing (ETH, MATIC, etc.) const priceConfig = payments.plans.getCryptoPriceConfig( 100n, // Amount in wei builderAddress, // Receiver address ZeroAddress // Native token ) ``` ### ERC20 Token Payment ```typescript theme={null} // ERC20 token pricing (USDC, DAI, etc.) const ERC20_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // USDC on Base const priceConfig = payments.plans.getERC20PriceConfig( 20n, // Amount in token's smallest unit ERC20_ADDRESS, // Token contract address builderAddress // Receiver address ) ``` ### Fiat Payment (Stripe) ```typescript theme={null} // Fiat pricing (USD via Stripe) const priceConfig = payments.plans.getFiatPriceConfig( 1000n, // Amount in cents ($10.00) builderAddress ) ``` ### Free Plans ```typescript theme={null} // Free plan (no payment required) const priceConfig = payments.plans.getFreePriceConfig() ``` ### Pay-As-You-Go Pricing ```typescript theme={null} // Get template address for PAYG plans const templateAddress = await payments.contracts.getPayAsYouGoTemplateAddress() const priceConfig = payments.plans.getPayAsYouGoPriceConfig( 100n, // Cost per request builderAddress, ZeroAddress, // Token address (optional) templateAddress ) ``` ## Credits Configuration ### Fixed Credits Grants a specific number of credits with a fixed burn rate per request. ```typescript theme={null} import { ONE_DAY_DURATION } from '@nevermined-io/payments' // Grant 100 credits, burn 1 credit per request const creditsConfig = payments.plans.getFixedCreditsConfig( 100n, // Total credits granted 1n // Credits per request ) ``` ### Dynamic Credits Allows variable credit consumption per request within a range. ```typescript theme={null} // Grant 10 credits, burn 1-2 credits per request (agent decides) const creditsConfig = payments.plans.getDynamicCreditsConfig( 10n, // Total credits granted 1n, // Minimum credits per request 2n // Maximum credits per request ) ``` ### Time-Based Credits (Expirable) Provides unlimited access for a fixed duration. ```typescript theme={null} import { ONE_DAY_DURATION, ONE_WEEK_DURATION, ONE_MONTH_DURATION } from '@nevermined-io/payments' // Unlimited access for 24 hours const creditsConfig = payments.plans.getExpirableDurationConfig( ONE_DAY_DURATION // Duration in seconds ) // Other durations const weeklyConfig = payments.plans.getExpirableDurationConfig(ONE_WEEK_DURATION) const monthlyConfig = payments.plans.getExpirableDurationConfig(ONE_MONTH_DURATION) ``` ### Non-Expirable Duration ```typescript theme={null} // Unlimited access with no expiration const creditsConfig = payments.plans.getNonExpirableDurationConfig() ``` ## Registering Plans ### Register a Credits Plan ```typescript theme={null} import { Payments } from '@nevermined-io/payments' import { PlanMetadata } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) const builderAddress = payments.getAccountAddress() const ERC20_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // Define plan metadata const planMetadata: PlanMetadata = { name: 'Basic AI Agent Access', description: 'Get 100 credits to query our AI agent', } // Configure pricing and credits const priceConfig = payments.plans.getERC20PriceConfig( 20n, ERC20_ADDRESS, builderAddress ) const creditsConfig = payments.plans.getFixedCreditsConfig(100n, 1n) // Register the plan const { planId } = await payments.plans.registerCreditsPlan( planMetadata, priceConfig, creditsConfig ) console.log(`Credits plan registered with ID: ${planId}`) ``` ### Register a Time Plan ```typescript theme={null} import { ONE_DAY_DURATION, PlanMetadata } from '@nevermined-io/payments' const planMetadata: PlanMetadata = { name: '24-Hour Unlimited Access', description: 'Unlimited queries for 24 hours', } const priceConfig = payments.plans.getERC20PriceConfig( 50n, ERC20_ADDRESS, builderAddress ) const creditsConfig = payments.plans.getExpirableDurationConfig(ONE_DAY_DURATION) const { planId } = await payments.plans.registerTimePlan( planMetadata, priceConfig, creditsConfig ) console.log(`Time plan registered with ID: ${planId}`) ``` ### Register a Trial Plan ```typescript theme={null} import { ONE_DAY_DURATION, PlanMetadata } from '@nevermined-io/payments' const planMetadata: PlanMetadata = { name: 'Free 24-Hour Trial', description: 'Try our AI agent free for 24 hours', } // Free pricing const priceConfig = payments.plans.getFreePriceConfig() // Limited duration const creditsConfig = payments.plans.getExpirableDurationConfig(ONE_DAY_DURATION) const { planId } = await payments.plans.registerTimeTrialPlan( planMetadata, priceConfig, creditsConfig ) console.log(`Trial plan registered with ID: ${planId}`) ``` ## Retrieving Plans ### Get a Specific Plan ```typescript theme={null} // Retrieve plan details by ID const plan = await payments.plans.getPlan(planId) console.log(`Plan: ${plan.name}`) console.log(`Price: ${plan.price}`) console.log(`Credits: ${plan.credits}`) ``` ### Get All Published Plans ```typescript theme={null} // Get all plans published by your account const plans = await payments.plans.getPlans() plans.forEach(plan => { console.log(`${plan.name}: ${plan.planId}`) }) ``` ### Get Agents for a Plan ```typescript theme={null} // Get all agents associated with a plan const agents = await payments.plans.getAgentsForPlan(planId) agents.forEach(agent => { console.log(`Agent: ${agent.name} (${agent.agentId})`) }) ``` ## Duration Constants The library provides convenient duration constants: ```typescript theme={null} import { ONE_DAY_DURATION, // 86,400 seconds (24 hours) ONE_WEEK_DURATION, // 604,800 seconds (7 days) ONE_MONTH_DURATION, // 2,629,746 seconds (~30.44 days) ONE_YEAR_DURATION // 31,557,600 seconds (~365.25 days) } from '@nevermined-io/payments' ``` ## Complete Example: Multi-Tier Plans ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { ONE_DAY_DURATION, ONE_MONTH_DURATION } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) const builderAddress = payments.getAccountAddress() const ERC20_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // Free trial plan const { planId: trialPlanId } = await payments.plans.registerTimeTrialPlan( { name: 'Free Trial', description: '24 hours free access' }, payments.plans.getFreePriceConfig(), payments.plans.getExpirableDurationConfig(ONE_DAY_DURATION) ) // Basic tier - 100 credits const { planId: basicPlanId } = await payments.plans.registerCreditsPlan( { name: 'Basic Plan', description: '100 credits for basic usage' }, payments.plans.getERC20PriceConfig(10n, ERC20_ADDRESS, builderAddress), payments.plans.getFixedCreditsConfig(100n, 1n) ) // Pro tier - unlimited monthly access const { planId: proPlanId } = await payments.plans.registerTimePlan( { name: 'Pro Plan', description: 'Unlimited access for 30 days' }, payments.plans.getERC20PriceConfig(50n, ERC20_ADDRESS, builderAddress), payments.plans.getExpirableDurationConfig(ONE_MONTH_DURATION) ) console.log('Multi-tier plans created:') console.log(`Trial: ${trialPlanId}`) console.log(`Basic: ${basicPlanId}`) console.log(`Pro: ${proPlanId}`) ``` ## Related Documentation * [Agents](./agents) - Associate plans with agents * [Payments and Balance](./payments-and-balance) - How subscribers order plans *** **Source References**: * `src/plans.ts` (helper functions, duration constants) * `src/api/plans-api.ts` (Plans API methods) * `tests/e2e/test_payments_e2e.test.ts` (lines 277-369) # Payments & Balance Source: https://docs.nevermined.app/docs/api-reference/typescript/payments-and-balance Purchase payment plans and manage credit balances for accessing AI agents # Payments and Balance This guide explains how subscribers purchase payment plans and check their credit balances. These operations are typically performed by users who want to access AI agents. ## Overview The payment flow consists of: 1. **Discovering Plans**: Browse available payment plans for agents 2. **Ordering Plans**: Purchase a plan to receive credits 3. **Checking Balance**: Monitor available credits and subscription status ## Get Plan Balance Subscribers can check their available credits for a specific plan: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' // Initialize with subscriber's API key const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Get balance for a plan const balance = await subscriberPayments.plans.getPlanBalance(planId) console.log(`Available credits: ${balance.balance}`) console.log(`Is owner: ${balance.isOwner}`) console.log(`Is subscriber: ${balance.isSubscriber}`) ``` ### Balance Response Structure The balance response contains: ```typescript theme={null} interface PlanBalance { balance: string // Available credits as string isOwner: boolean // True if you're the plan creator isSubscriber: boolean // True if you have an active subscription } ``` ### Working with Balance ```typescript theme={null} // Get balance const balance = await subscriberPayments.plans.getPlanBalance(planId) // Convert to BigInt for calculations const creditsAvailable = BigInt(balance.balance) // Check if sufficient credits const creditsNeeded = 10n if (creditsAvailable >= creditsNeeded) { console.log('Sufficient credits available') } else { console.log(`Need ${creditsNeeded - creditsAvailable} more credits`) } ``` ## Order a Plan Subscribers purchase plans to receive credits: ### Crypto Payment For plans priced in cryptocurrency: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Order the plan const orderResult = await subscriberPayments.plans.orderPlan(planId) if (orderResult.success) { console.log('Plan ordered successfully!') console.log(`Transaction: ${orderResult.transaction}`) } else { console.error('Failed to order plan') } ``` ### Fiat Payment (Stripe) For plans priced in fiat currency, use Stripe checkout: ```typescript theme={null} // Get Stripe checkout session const { clientSecret, checkoutUrl } = await subscriberPayments.plans.getStripeCheckout( planId, 'https://myapp.example/success', // Success redirect URL 'https://myapp.example/cancel' // Cancel redirect URL ) // Redirect user to Stripe checkout window.location.href = checkoutUrl ``` ### Complete Purchase Flow ```typescript theme={null} async function purchaseAndVerify(planId: string) { const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox', }) // Check current balance const beforeBalance = await subscriberPayments.plans.getPlanBalance(planId) console.log(`Balance before: ${beforeBalance.balance} credits`) // Order the plan const orderResult = await subscriberPayments.plans.orderPlan(planId) if (!orderResult.success) { throw new Error('Failed to order plan') } console.log('Plan ordered successfully') // Wait for credits to be allocated (blockchain settlement) await new Promise(resolve => setTimeout(resolve, 5000)) // Check new balance const afterBalance = await subscriberPayments.plans.getPlanBalance(planId) console.log(`Balance after: ${afterBalance.balance} credits`) } ``` ## Waiting for Balance Updates After ordering a plan, credits are allocated on-chain, which may take a few seconds: ```typescript theme={null} async function waitForCredits( payments: Payments, planId: string, timeoutMs: number = 60000 ): Promise { const startTime = Date.now() const checkInterval = 2000 // Check every 2 seconds while (Date.now() - startTime < timeoutMs) { const balance = await payments.plans.getPlanBalance(planId) const credits = BigInt(balance.balance) if (credits > 0n) { return credits } await new Promise(resolve => setTimeout(resolve, checkInterval)) } throw new Error('Timeout waiting for credits') } // Usage const credits = await waitForCredits(subscriberPayments, planId) console.log(`Received ${credits} credits`) ``` ## Check Multiple Plan Balances If a subscriber has multiple plans: ```typescript theme={null} async function checkAllBalances(planIds: string[]) { const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox', }) for (const planId of planIds) { const balance = await subscriberPayments.plans.getPlanBalance(planId) console.log(`Plan ${planId}: ${balance.balance} credits`) } } await checkAllBalances([planId1, planId2, planId3]) ``` ## Stripe Checkout Integration ### Complete Stripe Flow ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' // Initialize const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Create checkout session async function initiateStripeCheckout(planId: string) { const successUrl = `${window.location.origin}/payment-success` const cancelUrl = `${window.location.origin}/payment-cancelled` const { clientSecret, checkoutUrl } = await payments.plans.getStripeCheckout( planId, successUrl, cancelUrl ) // Save client secret for later verification (optional) sessionStorage.setItem('stripe_client_secret', clientSecret) // Redirect to Stripe window.location.href = checkoutUrl } // Handle success callback async function handlePaymentSuccess(planId: string) { // Wait for credits to be allocated const credits = await waitForCredits(payments, planId) console.log(`Payment successful! Received ${credits} credits`) // Redirect to agent interface window.location.href = '/agents' } ``` ## Plan Information Before ordering, subscribers might want to view plan details: ```typescript theme={null} // Get plan details const plan = await subscriberPayments.plans.getPlan(planId) console.log(`Plan: ${plan.name}`) console.log(`Description: ${plan.description}`) console.log(`Price: ${plan.price}`) console.log(`Credits: ${plan.credits}`) // Check if already subscribed const balance = await subscriberPayments.plans.getPlanBalance(planId) if (balance.isSubscriber && BigInt(balance.balance) > 0n) { console.log('Already subscribed with available credits') } else { console.log('Need to order plan') } ``` ## Complete Example: Purchase Flow with UI ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' class PlanPurchaseManager { private payments: Payments constructor(apiKey: string, environment: EnvironmentName) { this.payments = Payments.getInstance({ nvmApiKey: apiKey, environment, }) } async checkBalance(planId: string): Promise { const balance = await this.payments.plans.getPlanBalance(planId) return BigInt(balance.balance) } async purchasePlan(planId: string): Promise { try { // Get plan details const plan = await this.payments.plans.getPlan(planId) console.log(`Purchasing: ${plan.name}`) // Check if already subscribed const currentBalance = await this.checkBalance(planId) if (currentBalance > 0n) { console.log(`Already have ${currentBalance} credits`) return true } // Order the plan const orderResult = await this.payments.plans.orderPlan(planId) if (!orderResult.success) { throw new Error('Order failed') } console.log(`Order successful: ${orderResult.transaction}`) // Wait for credits const credits = await this.waitForCredits(planId) console.log(`Received ${credits} credits`) return true } catch (error) { console.error('Purchase failed:', error) return false } } private async waitForCredits( planId: string, timeoutMs: number = 60000 ): Promise { const startTime = Date.now() const checkInterval = 2000 while (Date.now() - startTime < timeoutMs) { const credits = await this.checkBalance(planId) if (credits > 0n) return credits await new Promise(resolve => setTimeout(resolve, checkInterval)) } throw new Error('Timeout waiting for credits') } } // Usage const manager = new PlanPurchaseManager( process.env.SUBSCRIBER_API_KEY!, 'sandbox' ) const success = await manager.purchasePlan(planId) if (success) { const balance = await manager.checkBalance(planId) console.log(`Ready to use agent with ${balance} credits`) } ``` ## Best Practices 1. **Check Balance First**: Always check existing balance before ordering 2. **Wait for Settlement**: Allow time for on-chain credit allocation after purchase 3. **Handle Errors**: Wrap order operations in try-catch blocks 4. **User Feedback**: Provide clear feedback during the purchase process 5. **Balance Monitoring**: Check balance before each agent request 6. **Stripe Redirects**: Use absolute URLs for Stripe success/cancel URLs ## Related Documentation * [Payment Plans](./payment-plans) - Understanding plan types and pricing * [Querying an Agent](./querying-an-agent) - Using credits to access agents *** **Source References**: * `src/api/plans-api.ts` (orderPlan, getPlanBalance, getStripeCheckout methods) * `tests/e2e/test_payments_e2e.test.ts` (ordering and balance verification) # Static Resources Source: https://docs.nevermined.app/docs/api-reference/typescript/publishing-static-resources Monetize access to static content like files, datasets, and documents with payment protection # Publishing Static Resources In addition to AI agents, you can register static resources (files, datasets, documents) as payment-protected content. This allows you to monetize access to files, datasets, models, or any static content through the Nevermined protocol. ## Overview of Static Resources Static resource agents are specialized agent registrations that provide access to files and documents instead of interactive AI services. They work identically to regular agents but are optimized for static content delivery. **Use Cases**: * Premium datasets (CSV, JSON, Parquet files) * Trained ML models (PyTorch, TensorFlow checkpoints) * Documents and reports (PDF, DOCX) * Media files (images, audio, video) * Configuration files and templates * API specifications and documentation ## Register Static Resource Agents Static resources are registered using the same Agents API, but with HTTP GET endpoints pointing to your content URLs: ```typescript theme={null} import { Payments, AgentMetadata, AgentAPIAttributes } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) // Define static resource metadata const resourceMetadata: AgentMetadata = { name: 'Premium Weather Datasets', description: 'Historical weather data for the past 10 years', tags: ['dataset', 'weather', 'csv', 'historical'], } // Configure file endpoints const resourceApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://storage.example.com/weather/monthly-data-2024.csv' }, { GET: 'https://storage.example.com/weather/monthly-data-2023.csv' }, ], authType: 'bearer', // Require X402 access token } // Register as agent const { agentId } = await payments.agents.registerAgent( resourceMetadata, resourceApi, [planId] ) console.log(`Static resource agent registered: ${agentId}`) ``` ## Using Wildcards for Multiple Files You can use wildcards to grant access to multiple files with a single endpoint pattern: ### Single Wildcard ```typescript theme={null} const resourceApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://storage.example.com/datasets/:dataset/*' }, ], authType: 'bearer', } ``` This pattern allows access to: * `https://storage.example.com/datasets/weather/2024-data.csv` * `https://storage.example.com/datasets/weather/hourly-readings.json` * `https://storage.example.com/datasets/climate/temperature-records.csv` ### Multiple Wildcards ```typescript theme={null} const resourceApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://storage.example.com/datasets/:category/:year/*' }, ], authType: 'bearer', } ``` This pattern allows access to: * `https://storage.example.com/datasets/weather/2024/january.csv` * `https://storage.example.com/datasets/climate/2023/summer.json` ### File Type Wildcards ```typescript theme={null} const resourceApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://storage.example.com/models/:model/*.pt' }, // PyTorch models { GET: 'https://storage.example.com/models/:model/*.safetensors' }, // Safetensors { GET: 'https://storage.example.com/datasets/:name/*.csv' }, // CSV files ], authType: 'bearer', } ``` ## Examples ### Example 1: Dataset Collection ```typescript theme={null} import { Payments, AgentMetadata, AgentAPIAttributes } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) const datasetMetadata: AgentMetadata = { name: 'ML Training Datasets', description: 'Curated datasets for machine learning training', tags: ['dataset', 'ml', 'training', 'csv', 'json'], } const datasetApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://cdn.example.com/datasets/:category/:filename' }, ], openEndpoints: [ 'https://cdn.example.com/datasets/samples/preview.json', // Free preview ], authType: 'bearer', } const { agentId } = await payments.agents.registerAgent( datasetMetadata, datasetApi, [planId] ) console.log(`Dataset collection registered: ${agentId}`) ``` ### Example 2: Model Repository ```typescript theme={null} const modelMetadata: AgentMetadata = { name: 'Fine-tuned Language Models', description: 'Pre-trained and fine-tuned models for NLP tasks', tags: ['model', 'nlp', 'pytorch', 'transformers'], } const modelApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://models.example.com/nlp/:model-name/*.pt' }, { GET: 'https://models.example.com/nlp/:model-name/*.safetensors' }, { GET: 'https://models.example.com/nlp/:model-name/config.json' }, ], authType: 'bearer', } const { agentId } = await payments.agents.registerAgent( modelMetadata, modelApi, [premiumPlanId] ) ``` ### Example 3: Document Library ```typescript theme={null} const documentMetadata: AgentMetadata = { name: 'Research Reports & Whitepapers', description: 'Premium industry research and technical whitepapers', tags: ['documents', 'research', 'reports', 'pdf'], } const documentApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://docs.example.com/reports/:year/:category/*.pdf' }, { GET: 'https://docs.example.com/whitepapers/:topic/*.pdf' }, ], openEndpoints: [ 'https://docs.example.com/samples/index.html', // Catalog ], authType: 'bearer', } const { agentId } = await payments.agents.registerAgent( documentMetadata, documentApi, [basicPlanId, proPlanId] ) ``` ### Example 4: Directory-Level Access ```typescript theme={null} const directoryMetadata: AgentMetadata = { name: 'Premium Asset Library', description: 'Complete directory access to all assets', tags: ['assets', 'media', 'images', 'videos'], } const directoryApi: AgentAPIAttributes = { endpoints: [ { GET: 'https://assets.example.com/premium/*' }, // Everything under /premium/ ], authType: 'bearer', } const { agentId } = await payments.agents.registerAgent( directoryMetadata, directoryApi, [unlimitedPlanId] ) ``` ## MCP Logical URIs for Static Resources When registering static resource agents in the Nevermined App, you can also use MCP logical URIs with wildcards: ``` mcp://storage-server/resources/weather://2024/* mcp://model-server/resources/models://:model-name/* mcp://docs-server/resources/reports://:category/* ``` ## Access Control Static resources use the same X402 access token authentication as AI agents: 1. Subscribers order a plan and receive credits 2. Subscribers generate an X402 access token 3. Requests to static resources include the token in the `PAYMENT-SIGNATURE` header 4. Your server verifies the token and grants access 5. Credits are burned based on your plan configuration See [Validation of Requests](./validation-of-requests) for implementation details. ## Best Practices 1. **Organized Structure**: Use clear URL patterns with logical path segments 2. **Wildcard Strategy**: Balance flexibility (broad wildcards) with security (specific patterns) 3. **File Types**: Specify file extensions in patterns when possible (e.g., `*.csv`, `*.pdf`) 4. **Preview Access**: Consider providing free samples via `openEndpoints` 5. **Metadata Tags**: Include file types and categories in tags for discoverability 6. **Plan Configuration**: Set appropriate credits per download based on file size/value ## Complete Example: Multi-Format Resource Library ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { AgentMetadata, AgentAPIAttributes } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) const resourceMetadata: AgentMetadata = { name: 'Data Science Resource Library', description: 'Comprehensive collection of datasets, models, and notebooks', tags: ['dataset', 'model', 'notebook', 'ml', 'ai'], } const resourceApi: AgentAPIAttributes = { endpoints: [ // Datasets { GET: 'https://cdn.example.com/datasets/:category/:name.csv' }, { GET: 'https://cdn.example.com/datasets/:category/:name.json' }, { GET: 'https://cdn.example.com/datasets/:category/:name.parquet' }, // Models { GET: 'https://cdn.example.com/models/:framework/:name/*' }, // Notebooks { GET: 'https://cdn.example.com/notebooks/:topic/*.ipynb' }, // Documentation { GET: 'https://cdn.example.com/docs/:section/*.pdf' }, ], openEndpoints: [ 'https://cdn.example.com/catalog.json', 'https://cdn.example.com/samples/preview-dataset.csv', ], authType: 'bearer', } const { agentId } = await payments.agents.registerAgent( resourceMetadata, resourceApi, [basicPlanId, proPlanId, enterprisePlanId] ) console.log(`Multi-format resource library registered: ${agentId}`) ``` ## Related Documentation * [Agents](./agents) - Agent registration fundamentals * [Payment Plans](./payment-plans) - Configure pricing for resource access * [Validation of Requests](./validation-of-requests) - Verify access tokens for downloads *** **Source References**: * `src/api/agents-api.ts` (Agents API for static resources) * `RUN.md` (MCP logical URIs with wildcards) # Querying Agents Source: https://docs.nevermined.app/docs/api-reference/typescript/querying-an-agent Generate X402 access tokens and make authenticated requests to AI agents # Querying an Agent After purchasing a payment plan, subscribers can generate X402 access tokens and use them to query AI agents. This guide explains how to get tokens and make authenticated requests. ## Overview The query flow consists of: 1. **Generate X402 Access Token**: Get a payment-authorized token for agent access 2. **Make Authenticated Requests**: Include the token in HTTP headers 3. **Receive Responses**: Agent validates the token and processes the request ## Get X402 Access Token The X402 access token is a cryptographically signed credential that proves you have purchased access to an agent: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' // Initialize with subscriber's API key const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Generate access token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, // Required: The plan you purchased agentId // Optional: Specific agent ID to restrict access ) console.log('Access token generated') ``` ### Token Parameters The `getX402AccessToken` method accepts several optional parameters: ```typescript theme={null} const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, // Optional: Restrict to specific agent redemptionLimit, // Optional: Max credits this token can burn (BigInt) orderLimit, // Optional: Order limit (string) expiration // Optional: Token expiration date (ISO string) ) ``` ### Basic Example ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Simple token generation const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) console.log(`Token generated, length: ${accessToken.length} characters`) ``` ## Token Structure (X402 v2) The access token is a JSON Web Token (JWT) containing an X402 v2 payment credential: ```json theme={null} { "x402Version": 2, "accepted": { "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "plan-123", "extra": { "version": "1", "agentId": "did:nv:agent-456" } }, "payload": { "signature": "0x...", "authorization": { "from": "0xSubscriberAddress", "sessionKeysProvider": "zerodev", "sessionKeys": [] } }, "extensions": {} } ``` ## Make Requests with X402 Token ### Using PAYMENT-SIGNATURE Header (X402 v2 Spec) The X402 v2 specification defines the `PAYMENT-SIGNATURE` header for payment credentials: ```typescript theme={null} // Make authenticated request to agent const response = await fetch('https://agent.example.com/api/v1/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, // X402 v2 standard header }, body: JSON.stringify({ prompt: 'What is the weather in San Francisco?', }), }) const result = await response.json() console.log(result) ``` ### Using Authorization Header (Alternative) Many agents also support the standard Authorization header with Bearer scheme: ```typescript theme={null} const response = await fetch('https://agent.example.com/api/v1/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, }, body: JSON.stringify({ prompt: 'What is the weather in San Francisco?', }), }) ``` ## Complete Query Example ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' async function queryAgent( planId: string, agentId: string, agentUrl: string, prompt: string ) { // Initialize subscriber payments const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) try { // 1. Generate access token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) console.log('✓ Access token generated') // 2. Make authenticated request const response = await fetch(`${agentUrl}/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, }, body: JSON.stringify({ prompt }), }) // 3. Handle response if (!response.ok) { if (response.status === 402) { console.error('Payment required - insufficient credits') } else { console.error(`Request failed: ${response.statusText}`) } return null } const result = await response.json() console.log('✓ Agent response received') return result } catch (error) { console.error('Failed to query agent:', error) return null } } // Usage const result = await queryAgent( 'plan-123', 'did:nv:agent-456', 'https://weather-agent.example.com/api/v1', 'What is the weather forecast for tomorrow?' ) ``` ## Handling 402 Payment Required If the token is invalid or credits are insufficient, agents return HTTP 402 with payment details: ```typescript theme={null} const response = await fetch(agentUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, }, body: JSON.stringify({ prompt }), }) if (response.status === 402) { // Read PAYMENT-REQUIRED header const paymentRequiredHeader = response.headers.get('PAYMENT-REQUIRED') if (paymentRequiredHeader) { // Decode base64 payment required info const paymentRequired = JSON.parse( Buffer.from(paymentRequiredHeader, 'base64').toString() ) console.log('Payment required:', paymentRequired) console.log(`Plan ID: ${paymentRequired.accepts[0].planId}`) // User needs to purchase plan or top up credits } } ``` ## MCP JSON-RPC Requests For MCP-based agents, use JSON-RPC format: ```typescript theme={null} const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) const response = await fetch('https://mcp-agent.example.com/mcp', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'get_weather', arguments: { city: 'San Francisco' } } }), }) const result = await response.json() console.log(result.result) ``` ## Token Reuse X402 access tokens can be reused for multiple requests until they expire or credits are exhausted: ```typescript theme={null} // Generate token once const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) // Use for multiple requests for (const city of ['San Francisco', 'New York', 'London']) { const response = await fetch(agentUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, // Reuse same token }, body: JSON.stringify({ prompt: `Weather in ${city}` }), }) const result = await response.json() console.log(`${city}:`, result) } ``` ## Check Balance Before Querying To avoid 402 errors, check your balance before making requests: ```typescript theme={null} async function queryWithBalanceCheck( planId: string, agentId: string, agentUrl: string, prompt: string ) { const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox', }) // Check balance first const balance = await subscriberPayments.plans.getPlanBalance(planId) const availableCredits = BigInt(balance.balance) if (availableCredits === 0n) { console.error('No credits available - please purchase a plan') return null } console.log(`Available credits: ${availableCredits}`) // Generate token and query const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) const response = await fetch(agentUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, }, body: JSON.stringify({ prompt }), }) return await response.json() } ``` ## Best Practices 1. **Token Caching**: Generate tokens once and reuse them for multiple requests 2. **Balance Checking**: Check balance before generating tokens 3. **Error Handling**: Always handle 402 Payment Required responses 4. **HTTPS Only**: Never send tokens over unencrypted HTTP 5. **Token Expiration**: Regenerate tokens if you receive 402 errors 6. **Header Standard**: Prefer `PAYMENT-SIGNATURE` header (X402 v2 spec) ## Related Documentation * [Payments and Balance](./payments-and-balance) - How to purchase plans and check credits * [Validation of Requests](./validation-of-requests) - How agents validate tokens (for builders) * [X402 Protocol](./x402) - Complete X402 specification *** **Source References**: * `src/x402/token.ts` (getX402AccessToken method) * `tests/e2e/test_x402_e2e.test.ts` (lines 114-133, token generation) * `tests/e2e/test_payments_e2e.test.ts` (MockAgentServer, lines 72-127) # Request Validation Source: https://docs.nevermined.app/docs/api-reference/typescript/validation-of-requests Verify X402 access tokens and settle payments when subscribers access your agents # Validation of Requests This guide explains how AI agents validate incoming requests and settle payments using the Nevermined Facilitator. Agent builders use these methods to verify subscriber access and burn credits. ## Overview The validation flow consists of: 1. **Extract Token**: Read X402 access token from request headers 2. **Build Payment Required**: Create payment requirement specification 3. **Verify Permissions**: Check if subscriber has valid access 4. **Execute Task**: Process the agent request 5. **Settle Permissions**: Burn credits after successful execution 6. **Return Response**: Send result or 402 error to subscriber ## Receiving Requests Extract the X402 access token from request headers. The X402 v2 spec defines the `PAYMENT-SIGNATURE` header: ```typescript theme={null} import express from 'express' const app = express() app.use(express.json()) app.post('/api/v1/tasks', async (req, res) => { // Extract token from PAYMENT-SIGNATURE header (X402 v2) const accessToken = req.headers['payment-signature'] as string // Alternative: Authorization header // const accessToken = req.headers.authorization?.replace('Bearer ', '') if (!accessToken) { return res.status(402).json({ error: 'Payment required' }) } // Process request... }) ``` ## Build Payment Required Use the `buildPaymentRequired` helper to create the payment specification: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, // Resource URL agentId: agentId, // Your agent ID httpVerb: req.method, // HTTP method (POST, GET, etc.) network: 'eip155:84532', // Optional: blockchain network description: 'Task API' // Optional: description }) ``` ## Verify Permissions Before executing the request, verify the subscriber has valid access: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { buildPaymentRequired } from '@nevermined-io/payments' const agentPayments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, // Builder/agent API key environment: 'sandbox' as EnvironmentName, }) app.post('/api/v1/tasks', async (req, res) => { const accessToken = req.headers['payment-signature'] as string if (!accessToken) { return res.status(402).json({ error: 'Payment required' }) } // Build payment required const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) try { // Verify permissions const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 1n, // Max credits to verify }) if (!verification.isValid) { return res.status(402).json({ error: 'Insufficient credits or invalid token' }) } // Verification successful, proceed with request... } catch (error) { return res.status(500).json({ error: error.message }) } }) ``` ## Settle Permissions After successfully processing the request, burn the credits: ```typescript theme={null} app.post('/api/v1/tasks', async (req, res) => { const accessToken = req.headers['payment-signature'] as string if (!accessToken) { return res.status(402).json({ error: 'Payment required' }) } const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) try { // Verify const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 1n, }) if (!verification.isValid) { return res.status(402).json({ error: 'Insufficient credits' }) } // Execute task const result = await processTask(req.body) // Settle permissions (burn credits) const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 1n, // Credits to burn }) // Return success with payment metadata return res.json({ result, transaction: settlement.transaction, creditsUsed: settlement.creditsRedeemed, remainingBalance: settlement.remainingBalance, }) } catch (error) { return res.status(500).json({ error: error.message }) } }) ``` ## Return 402 Payment Required When payment is required, return HTTP 402 with the `PAYMENT-REQUIRED` header: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' function return402Response(res: express.Response, planId: string, agentId: string, endpoint: string) { const paymentRequired = buildPaymentRequired(planId, { endpoint, agentId, httpVerb: 'POST', }) // Encode payment required as base64 const paymentRequiredHeader = Buffer.from( JSON.stringify(paymentRequired) ).toString('base64') res.writeHead(402, { 'Content-Type': 'application/json', 'PAYMENT-REQUIRED': paymentRequiredHeader, }) res.end(JSON.stringify({ error: 'Payment required', message: 'Valid X402 access token required in PAYMENT-SIGNATURE header', })) } // Usage app.post('/api/v1/tasks', async (req, res) => { const accessToken = req.headers['payment-signature'] as string if (!accessToken) { return return402Response(res, planId, agentId, req.url) } // Verify and process... }) ``` ## Complete Validation Example ```typescript theme={null} import express from 'express' import { Payments, EnvironmentName } from '@nevermined-io/payments' import { buildPaymentRequired } from '@nevermined-io/payments' const app = express() app.use(express.json()) const agentPayments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) const PLAN_ID = process.env.NVM_PLAN_ID! const AGENT_ID = process.env.NVM_AGENT_ID! app.post('/api/v1/tasks', async (req, res) => { // 1. Extract token const accessToken = req.headers['payment-signature'] as string if (!accessToken) { return return402Response(res, PLAN_ID, AGENT_ID, req.url) } // 2. Build payment required const paymentRequired = buildPaymentRequired(PLAN_ID, { endpoint: req.url, agentId: AGENT_ID, httpVerb: req.method, }) try { // 3. Verify permissions const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 1n, }) if (!verification.isValid) { console.log('Verification failed: insufficient credits or invalid token') return return402Response(res, PLAN_ID, AGENT_ID, req.url) } console.log('✓ Permissions verified') // 4. Execute task const result = await processTask(req.body) // 5. Settle permissions const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 1n, }) console.log(`✓ Credits settled: ${settlement.creditsRedeemed}`) console.log(`✓ Transaction: ${settlement.transaction}`) // 6. Return response with payment metadata const paymentResponse = { success: settlement.success, network: settlement.network, transaction: settlement.transaction, creditsRedeemed: settlement.creditsRedeemed, } res.writeHead(200, { 'Content-Type': 'application/json', 'PAYMENT-RESPONSE': Buffer.from(JSON.stringify(paymentResponse)).toString('base64'), }) res.end(JSON.stringify({ result })) } catch (error) { console.error('Request processing failed:', error) res.status(500).json({ error: error.message }) } }) function return402Response( res: express.Response, planId: string, agentId: string, endpoint: string ) { const paymentRequired = buildPaymentRequired(planId, { endpoint, agentId, httpVerb: 'POST', }) const paymentRequiredHeader = Buffer.from( JSON.stringify(paymentRequired) ).toString('base64') res.writeHead(402, { 'Content-Type': 'application/json', 'PAYMENT-REQUIRED': paymentRequiredHeader, }) res.end(JSON.stringify({ error: 'Payment required' })) } async function processTask(body: any): Promise { // Your agent logic here return { response: 'Task completed' } } app.listen(3000, () => { console.log('Agent server running on port 3000') }) ``` ## Dynamic Credit Burning For agents with variable credit costs, calculate credits based on the request: ```typescript theme={null} async function calculateCredits(request: any, response: any): Promise { // Example: charge based on response length const responseLength = JSON.stringify(response).length return BigInt(Math.ceil(responseLength / 1000)) // 1 credit per KB } app.post('/api/v1/tasks', async (req, res) => { const accessToken = req.headers['payment-signature'] as string const paymentRequired = buildPaymentRequired(PLAN_ID, { endpoint: req.url, agentId: AGENT_ID, httpVerb: req.method, }) // Verify with max possible credits const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 10n, // Max credits subscriber might need }) if (!verification.isValid) { return return402Response(res, PLAN_ID, AGENT_ID, req.url) } // Execute const result = await processTask(req.body) // Calculate actual credits used const creditsUsed = await calculateCredits(req.body, result) // Settle with actual amount const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: creditsUsed, }) return res.json({ result, creditsUsed: settlement.creditsRedeemed, }) }) ``` ## Settle Options The `settlePermissions` method accepts additional options: ```typescript theme={null} const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 5n, // Credits to burn batch: false, // Batch settlement (optional) marginPercent: 5, // Add 5% margin to calculated cost (optional) agentRequestId: 'req-123', // Request ID from verification (optional) }) ``` ## Best Practices 1. **Always Verify First**: Call `verifyPermissions` before executing tasks 2. **Settle After Success**: Only burn credits after successful task completion 3. **Handle Errors**: Wrap verification/settlement in try-catch blocks 4. **Return 402 Properly**: Include `PAYMENT-REQUIRED` header with payment details 5. **Log Transactions**: Record transaction hashes for audit trails 6. **Dynamic Pricing**: Calculate credits based on actual resource usage 7. **Token Validation**: Never skip verification even if token looks valid ## Related Documentation * [Querying an Agent](./querying-an-agent) - How subscribers generate and use tokens * [X402 Protocol](./x402) - Complete X402 specification * [MCP Integration](./mcp-integration) - Automatic validation in MCP servers * [A2A Integration](./a2a-integration) - Automatic validation in A2A servers *** **Source References**: * `src/x402/facilitator-api.ts` (buildPaymentRequired, verifyPermissions, settlePermissions) * `tests/e2e/test_payments_e2e.test.ts` (MockAgentServer class, complete validation flow) * `tests/e2e/test_x402_e2e.test.ts` (lines 135-150, verification examples) # X402 Protocol Source: https://docs.nevermined.app/docs/api-reference/typescript/x402 Understand the X402 payment protocol for HTTP-based AI agent authentication and authorization # X402 Protocol The X402 protocol is a payment-aware HTTP extension that enables AI agents to require and verify payments for API access. This guide provides a complete overview of X402 implementation in the Nevermined Payments Library. ## Overview of X402 X402 is an HTTP-based protocol for payment-protected resources: * **402 Payment Required**: HTTP status code for payment requests * **PAYMENT-SIGNATURE**: Header containing payment credentials (X402 v2) * **PAYMENT-REQUIRED**: Header with payment requirements in 402 responses * **PAYMENT-RESPONSE**: Header with payment settlement details in success responses * **Cryptographic Signatures**: ERC-4337 account abstraction for secure payments ## X402 Version 2 Specification The Nevermined Payments Library implements X402 v2, which uses: * `PAYMENT-SIGNATURE` header for access tokens (replaces Authorization) * `PAYMENT-REQUIRED` header for payment requirements (replaces custom formats) * `PAYMENT-RESPONSE` header for settlement receipts * Structured payment credentials with cryptographic signatures ## Generate X402 Access Tokens Subscribers generate access tokens to query agents: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // Generate X402 access token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, // Required: Plan ID agentId, // Optional: Agent ID (restricts token to specific agent) redemptionLimit, // Optional: Max credits this token can burn orderLimit, // Optional: Order limit expiration // Optional: Expiration date (ISO 8601 string) ) console.log('X402 access token generated') ``` ### Generate Tokens via Nevermined App Users can also generate X402 access tokens through the Nevermined App: 1. Visit [nevermined.app/permissions/agent-permissions](https://nevermined.app/permissions/agent-permissions) 2. Select the plan you've purchased 3. Configure token parameters (agent, expiration, limits) 4. Generate and copy the X402 access token 5. Use the token in API requests This provides a user-friendly interface for non-technical users to generate tokens without code. ## X402 Access Token Structure The access token is a JWT containing an X402 v2 payment credential: ```json theme={null} { "x402Version": 2, "accepted": { "scheme": "nvm:erc4337", // Payment scheme "network": "eip155:84532", // Blockchain network (Base Sepolia) "planId": "plan-123", // Payment plan ID "extra": { "version": "1", // Extra version info "agentId": "did:nv:agent-456" // Optional agent restriction } }, "payload": { "signature": "0x...", // Cryptographic signature "authorization": { "from": "0xSubscriberAddress", // Subscriber's wallet address "sessionKeysProvider": "zerodev", // Session key provider "sessionKeys": [] // Session keys for gasless transactions } }, "extensions": {} // Optional extensions } ``` ### Token Components * **x402Version**: Protocol version (2 for current spec) * **accepted**: Payment method specification * **scheme**: `nvm:erc4337` (Nevermined ERC-4337 account abstraction) * **network**: Blockchain network (e.g., `eip155:84532` for Base Sepolia) * **planId**: The payment plan being used * **extra**: Additional metadata (version, agentId, etc.) * **payload**: Payment authorization * **signature**: Cryptographic proof of payment authorization * **authorization**: Subscriber identity and session keys * **extensions**: Optional protocol extensions ## Verify X402 Permissions Agents verify tokens before executing requests: ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' import { buildPaymentRequired } from '@nevermined-io/payments' const agentPayments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, // Builder/agent API key environment: 'sandbox' as EnvironmentName, }) // Build payment required specification const paymentRequired = buildPaymentRequired(planId, { endpoint: '/api/v1/tasks', agentId: agentId, httpVerb: 'POST', network: 'eip155:84532', description: 'Task execution API', }) // Verify subscriber permissions const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 2n, // Max credits to verify }) if (verification.isValid) { console.log('✓ Permissions verified') console.log(`Subscriber: ${verification.agentRequest.subscriber}`) } else { console.log('✗ Verification failed') } ``` ### Verification Response ```typescript theme={null} interface VerifyPermissionsResult { isValid: boolean // True if token is valid agentRequest?: { agentRequestId: string // Request ID for settlement subscriber: string // Subscriber address balance: string // Available credits } } ``` ## Settle X402 Permissions After successful execution, burn credits: ```typescript theme={null} // Settle permissions (burn credits) const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 2n, // Credits to burn batch: false, // Batch settlement (optional) marginPercent: 5, // Add 5% margin (optional) agentRequestId: verification.agentRequest?.agentRequestId, // From verification }) console.log(`✓ Settlement successful`) console.log(`Transaction: ${settlement.transaction}`) console.log(`Credits burned: ${settlement.creditsRedeemed}`) console.log(`Remaining balance: ${settlement.remainingBalance}`) ``` ### Settlement Response ```typescript theme={null} interface SettlePermissionsResult { success: boolean // True if settlement succeeded transaction: string // Blockchain transaction hash creditsRedeemed: string // Credits burned remainingBalance: string // Subscriber's remaining credits network: string // Blockchain network } ``` ## HTTP Headers (X402 v2) ### PAYMENT-SIGNATURE Header Subscribers include this header in requests: ```http theme={null} POST /api/v1/tasks HTTP/1.1 Host: agent.example.com Content-Type: application/json PAYMENT-SIGNATURE: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... {"prompt": "Hello"} ``` ### PAYMENT-REQUIRED Header (402 Response) Agents return this header when payment is required: ```http theme={null} HTTP/1.1 402 Payment Required Content-Type: application/json PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2UiOnsidXJsIjoiL2FwaS92MS90YXNrcyJ9... {"error": "Payment required"} ``` The header contains base64-encoded payment requirement JSON: ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/tasks", "description": "Task execution API", "mimeType": "application/json" }, "accepts": [ { "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "plan-123", "extra": { "version": "1", "agentId": "did:nv:agent-456" } } ], "extensions": {} } ``` ### PAYMENT-RESPONSE Header (Success) Agents include this header in successful responses: ```http theme={null} HTTP/1.1 200 OK Content-Type: application/json PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4Li4uIn0= {"result": "Task completed"} ``` The header contains base64-encoded settlement details: ```json theme={null} { "success": true, "network": "eip155:84532", "transaction": "0x...", "creditsRedeemed": "2", "remainingBalance": "98" } ``` ## Complete X402 Flow ### Subscriber Side ```typescript theme={null} import { Payments, EnvironmentName } from '@nevermined-io/payments' const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // 1. Generate access token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId ) // 2. Make request with PAYMENT-SIGNATURE header const response = await fetch('https://agent.example.com/api/v1/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': accessToken, }, body: JSON.stringify({ prompt: 'Hello, agent!' }), }) // 3. Handle response if (response.status === 402) { // Payment required - need to purchase plan or top up const paymentRequired = response.headers.get('PAYMENT-REQUIRED') console.error('Payment required:', paymentRequired) } else { const result = await response.json() const paymentResponse = response.headers.get('PAYMENT-RESPONSE') console.log('Success:', result) console.log('Payment:', paymentResponse) } ``` ### Agent Side ```typescript theme={null} import express from 'express' import { Payments, EnvironmentName } from '@nevermined-io/payments' import { buildPaymentRequired } from '@nevermined-io/payments' const app = express() app.use(express.json()) const agentPayments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' as EnvironmentName, }) app.post('/api/v1/tasks', async (req, res) => { // 1. Extract token const accessToken = req.headers['payment-signature'] as string if (!accessToken) { const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) return res .status(402) .set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(paymentRequired)).toString('base64')) .json({ error: 'Payment required' }) } // 2. Build payment required const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) try { // 3. Verify permissions const verification = await agentPayments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 2n, }) if (!verification.isValid) { return res .status(402) .set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(paymentRequired)).toString('base64')) .json({ error: 'Insufficient credits' }) } // 4. Execute task const result = await processTask(req.body) // 5. Settle permissions const settlement = await agentPayments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: accessToken, maxAmount: 2n, }) // 6. Return response with PAYMENT-RESPONSE header const paymentResponse = { success: settlement.success, network: settlement.network, transaction: settlement.transaction, creditsRedeemed: settlement.creditsRedeemed, } return res .status(200) .set('PAYMENT-RESPONSE', Buffer.from(JSON.stringify(paymentResponse)).toString('base64')) .json({ result }) } catch (error) { res.status(500).json({ error: error.message }) } }) app.listen(3000) ``` ## buildPaymentRequired Helper Simplifies creating X402PaymentRequired objects: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' const paymentRequired = buildPaymentRequired( planId, { endpoint: '/api/v1/tasks', // Resource URL agentId: 'did:nv:agent-456', // Agent ID httpVerb: 'POST', // HTTP method network: 'eip155:84532', // Blockchain (default) description: 'Task execution' // Description } ) ``` ## Best Practices 1. **Always Verify Before Execute**: Never skip token verification 2. **Settle After Success**: Only burn credits after successful execution 3. **Use X402 v2 Headers**: Prefer `PAYMENT-SIGNATURE` over Authorization 4. **Return 402 Properly**: Include `PAYMENT-REQUIRED` header with details 5. **Log Transactions**: Record settlement transaction hashes 6. **Handle Errors**: Provide clear error messages in 402 responses 7. **Token Reuse**: Subscribers can reuse tokens for multiple requests ## Related Documentation * [Querying an Agent](./querying-an-agent) - How subscribers use X402 tokens * [Validation of Requests](./validation-of-requests) - How agents verify and settle * [MCP Integration](./mcp-integration) - Automatic X402 handling in MCP * [A2A Integration](./a2a-integration) - Automatic X402 handling in A2A *** **Source References**: * `src/x402/token.ts` (getX402AccessToken) * `src/x402/facilitator-api.ts` (verifyPermissions, settlePermissions, buildPaymentRequired) * `tests/e2e/test_x402_e2e.test.ts` (complete X402 flow) # Create Permission Source: https://docs.nevermined.app/docs/api-reference/x402/create-permission POST /x402/permissions Creates a delegated permission and generates an x402 access token. # Get Permission Source: https://docs.nevermined.app/docs/api-reference/x402/get-permission GET /x402/permissions/{permissionHash} Retrieves complete information about a specific permission. # List Permissions Source: https://docs.nevermined.app/docs/api-reference/x402/list-permissions GET /x402/permissions Retrieves paginated list of your delegated permissions. # Revoke Permission Source: https://docs.nevermined.app/docs/api-reference/x402/revoke-permission DELETE /x402/permissions/{permissionHash} Invalidates a delegated permission, preventing further use. # Settle Source: https://docs.nevermined.app/docs/api-reference/x402/settle-permission POST /x402/settle Finalizes and records delegated permission usage after service delivery. # Verify Source: https://docs.nevermined.app/docs/api-reference/x402/verify-permission POST /x402/verify Validates a delegated permission before processing a request. # Documentation MCP Source: https://docs.nevermined.app/docs/development-guide/build-using-nvm-mcp Vibe-code your Nevermined payments integrations directly from your AI IDE using our MCP server. You can use the Nevermined Docs MCP server to vibe-code applications directly from Claude Code, Open Code, Cursor, Continue, Zed, and other MCP-aware IDE copilots. Point any MCP-compatible client at the server URL below to stream up-to-date docs into your coding sessions. **LLM Context Files for AI Assistants** If you're using an AI assistant for development, you can provide comprehensive context files to streamline your integration: * **Concise Context**: [https://nevermined.ai/docs/llms.txt](https://nevermined.ai/docs/llms.txt) - Quick reference with key concepts and examples * **Full Context**: [https://nevermined.ai/docs/llms-full.txt](https://nevermined.ai/docs/llms-full.txt) - Complete documentation for in-depth integration work Simply copy the contents and paste them into your LLM's input as a knowledge base. **MCP Configuration File** For quick setup with MCP-compatible IDEs, download our pre-configured MCP file: * **Download**: [nevermined.mcp.json](/docs/download/nevermined-mcp-config) - Ready-to-use configuration for Cursor, Continue, Claude Desktop, and other MCP clients Use this file as a reference or copy its contents into your IDE's MCP configuration. The Nevermined Docs MCP Server provides seamless access to all Nevermined's documentation directly within your AI development environment. By integrating this MCP server, you can build AI agents and applications with real-time access to Nevermined's payment SDKs and Libraries, API references, and development guides. ## What is MCP? The Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to external data sources and tools. Think of MCP as a "USB-C for AI" - it provides a standardized way to connect AI models to different services, documentation, and APIs. **Key Benefits:** * 🔌 **Standardized Connections**: Connect any MCP-compatible AI client to our documentation * 🔄 **Real-time Access**: Always up-to-date information from our docs * 🛡️ **Secure Integration**: Built-in security and authentication * 🌍 **Remote Access**: No local installation required ## MCP Server Details **Nevermined Documentation MCP Server URL:** `https://docs.nevermined.app/mcp` This server provides access to: * Complete Nevermined API documentation * Development guides and tutorials * Code examples in Python and TypeScript * Payment plan configuration guides * AI agent registration workflows ### Instant Setup Commands Choose your IDE and run the corresponding command for zero-friction setup: ```bash theme={null} claude mcp add --transport http --url https://docs.nevermined.app/mcp nevermined-docs ``` ```bash theme={null} mkdir -p ~/.cursor && echo '{ "mcpServers": { "nevermined-docs": { "url": "https://docs.nevermined.app/mcp", "type": "http" } } }' > ~/.cursor/mcp.json ``` ```json theme={null} // Add to ~/.continue/config.json { "mcpServers": [ { "name": "nevermined-docs", "url": "https://docs.nevermined.app/mcp", "transport": "http" } ] } ``` ```json theme={null} // Add to ~/Library/Application Support/Claude/claude_desktop_config.json { "mcpServers": { "nevermined-docs": { "url": "https://docs.nevermined.app/mcp", "type": "http" } } } ``` Then restart your IDE to activate the connection. ## Quick Setup for Vibe-Code Tools Follow these lightweight steps to attach the Nevermined Docs MCP server to popular code-native AI tools. Each tool simply needs the remote MCP endpoint `https://docs.nevermined.app/mcp`. **Quick Start**: Download the [nevermined.mcp.json](/docs/download/nevermined-mcp-config) configuration file and use it as a reference for your IDE's MCP setup. The file contains the complete configuration needed for most MCP-compatible tools. ### Claude Code (VS Code extension) **One-Line Setup** (Recommended): Run this command in your terminal for instant setup: ```bash theme={null} claude mcp add --transport http --url https://docs.nevermined.app/mcp nevermined-docs ``` **Alternative - GUI Setup**: 1. Open the Command Palette and run "Claude: Open Settings" 2. In **Model Context Protocol** → **Custom Connectors**, click **Add** 3. Enter the server URL `https://docs.nevermined.app/mcp` and save 4. Start chatting; the Nevermined docs source appears under available connectors ### Cursor IDE **One-Line Setup** (Recommended): ```bash theme={null} # Create or update Cursor MCP config mkdir -p ~/.cursor && echo '{ "mcpServers": { "nevermined-docs": { "url": "https://docs.nevermined.app/mcp", "type": "http" } } }' > ~/.cursor/mcp.json ``` Then restart Cursor to activate the connection. **Alternative Methods**: 1. **Using Configuration File**: * Download [nevermined.mcp.json](/docs/download/nevermined-mcp-config) * Copy contents to `~/.cursor/mcp.json` * Reload the IDE 2. **Manual GUI Setup**: * Open Settings → search for **MCP Servers** * Add remote server: `https://docs.nevermined.app/mcp` * Enable the connector in chat ### Open Code **Quick Setup**: Add to your MCP configuration: ```bash theme={null} # Add to your Open Code MCP settings echo '{ "mcpServers": { "nevermined-docs": { "url": "https://docs.nevermined.app/mcp", "type": "http" } } }' >> ~/.opencode/mcp.json ``` Or manually add the server in Settings → **Model Context Protocol** ### Continue (VS Code / JetBrains) **Quick Setup**: Add to your Continue configuration (`~/.continue/config.json`): ```json theme={null} { "mcpServers": [ { "name": "nevermined-docs", "url": "https://docs.nevermined.app/mcp", "transport": "http" } ] } ``` **Or via Continue UI**: 1. Open Continue settings and locate **MCP / Tools** 2. Add MCP server: `nevermined-docs` → `https://docs.nevermined.app/mcp` 3. Reload Continue to activate > If your IDE supports MCP but is not listed, add a remote MCP connector pointing to > `https://docs.nevermined.app/mcp` and enable it for chat/code actions. The server > requires no extra auth beyond your Nevermined API usage in code samples. ## Setting Up Your IDE with Nevermined MCP Server ### Option 1: Using Claude Desktop (Recommended) Claude Desktop provides excellent MCP support with a user-friendly interface for connecting to remote MCP servers. #### Step 1: Install Claude Desktop Download and install Claude Desktop from [Claude's official website](https://claude.ai/download). #### Step 2: Configure the MCP Server 1. **Open Claude Desktop** and sign in to your account 2. **Access Settings**: Click on your profile icon and select "Settings" 3. **Navigate to Connectors**: In the settings sidebar, click on "Connectors" 4. **Add Custom Connector**: Scroll to the bottom and click "Add custom connector" 5. **Enter Server URL**: In the dialog box, enter: ```text theme={null} https://docs.nevermined.app/mcp ``` 6. **Complete Setup**: Click "Add" to establish the connection The Nevermined Documentation MCP server will now be available in your Claude conversations! **Configuration Reference**: If you prefer to edit the configuration file directly (located at `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS), see our [nevermined.mcp.json](/docs/download/nevermined-mcp-config) for the correct format. #### Step 3: Access Nevermined Documentation 1. **Start a Conversation**: Open a new chat in Claude Desktop 2. **Access Resources**: Click the paperclip icon (📎) in the message input area 3. **Select Nevermined Resources**: You'll see available documentation resources from the Nevermined MCP server 4. **Use in Prompts**: Select relevant documentation to include context in your conversations ### Option 2: Using Claude Web (claude.ai) You can also connect to the Nevermined MCP server through Claude's web interface using Custom Connectors. #### Step 1: Access Claude Settings 1. Go to [claude.ai](https://claude.ai/) and sign in 2. Click your profile icon and select "Settings" 3. Navigate to "Connectors" in the sidebar #### Step 2: Add Nevermined MCP Server 1. Click "Add custom connector" 2. Enter the server URL: `https://docs.nevermined.app/mcp` 3. Complete any authentication prompts 4. Configure tool permissions as needed ### Option 3: Other MCP-Compatible Clients The Nevermined MCP server works with any MCP-compatible client. For a full list of supported clients, visit the [MCP Clients page](https://modelcontextprotocol.io/clients). Popular MCP-compatible tools include: * **Continue** (VS Code extension) * **Cursor IDE** * **Zed Editor** * **Custom applications** using MCP SDKs For detailed setup instructions for other clients, refer to the [Model Context Protocol documentation](https://modelcontextprotocol.io/docs/getting-started/intro). ## Prompt Examples for Building AI Agents Once you have the Nevermined MCP server connected, you can use these example prompts to build AI agents and client applications: ### 1. Create a Basic AI Agent ```text theme={null} I want to create an AI agent that validates user requests using Nevermined Pay. Please show me how to: 1. Register an AI agent with Nevermined 2. Set up payment plans with credits-based pricing 3. Implement request validation in TypeScript 4. Handle authorization errors properly Include complete code examples and explain each step. ``` ### 2. Build a Client Application ```text theme={null} Help me build a TypeScript client application that can: 1. Purchase access to a Nevermined AI agent 2. Send authenticated requests to the agent 3. Handle payment errors and reauthorization 4. Monitor remaining credits and subscription status Show me the complete implementation with error handling. ``` ### 3. Multi-Agent Workflow ```text theme={null} I want to create a multi-agent system where: 1. Agent A processes text analysis requests 2. Agent B generates summaries based on Agent A's output 3. Both agents use different payment plans 4. Users pay once but can access both agents How do I implement this workflow using Nevermined? Show me the agent registration, payment configuration, and orchestration code. ``` ### 4. Payment Plan Configuration ```text theme={null} Help me design payment plans for my AI service: 1. A free trial plan (100 requests, 7 days) 2. A basic plan (1000 requests per month, $10 USDC) 3. A premium plan (unlimited requests, $50 USDC per month) Show me how to implement these plans and handle upgrades/downgrades. ``` ### 5. Agent-to-Agent Payments ```text theme={null} I'm building an AI agent marketplace where agents can purchase services from other agents. Show me how to: 1. Set up autonomous agent-to-agent payments 2. Implement automatic credit management 3. Handle payment authorization between agents 4. Track revenue and usage across multiple agents Provide complete Python and TypeScript examples. ``` ### 6. Integration with Existing Services ```text theme={null} I have an existing AI service running on FastAPI. Help me integrate it with Nevermined to: 1. Add payment validation to my existing endpoints 2. Implement subscription management 3. Add usage tracking and analytics 4. Support both crypto and fiat payments Show me how to modify my existing code with minimal changes. ``` ## Advanced Use Cases ### Building Custom MCP Servers You can also create your own MCP servers that integrate with Nevermined. Here's how to get started: ```text theme={null} I want to build a custom MCP server that: 1. Exposes my company's internal AI tools 2. Uses Nevermined for access control and payments 3. Provides dynamic pricing based on tool complexity 4. Integrates with our existing authentication system Show me the architecture and implementation using the MCP SDK. ``` ### Marketplace Development ```text theme={null} Help me build an AI agent marketplace using Nevermined where: 1. Developers can register and monetize their AI agents 2. Users can discover and purchase access to agents 3. The platform takes a commission on sales 4. Agents can be combined into workflows Include the frontend, backend, and payment integration code. ``` ### Enterprise Integration ```text theme={null} I need to integrate Nevermined into our enterprise environment: 1. Connect with our existing identity provider (OAuth/SAML) 2. Implement cost allocation across departments 3. Add detailed usage reporting and analytics 4. Support both on-premise and cloud deployments Show me the architecture and configuration needed. ``` ## Best Practices When using the Nevermined MCP server, follow these best practices: ### Security Considerations * **Verify Connections**: Always ensure you're connecting to the official Nevermined MCP server * **Review Permissions**: Only grant necessary tool permissions in your MCP client * **Secure API Keys**: Store Nevermined API keys securely and never expose them in client-side code ### Development Workflow 1. **Start with Documentation**: Use the MCP server to understand Nevermined concepts 2. **Test in Sandbox**: Always test your implementations in Nevermined's sandbox environment 3. **Iterate with Examples**: Use the provided code examples as starting points 4. **Monitor Usage**: Implement proper logging and monitoring for production deployments ### Performance Optimization * **Cache Documentation**: Cache frequently accessed documentation locally when possible * **Batch Requests**: Group related operations to minimize API calls * **Implement Retries**: Add retry logic for transient failures * **Monitor Limits**: Respect rate limits and implement backoff strategies ## Troubleshooting ### Common Issues #### Connection Failed * Verify you're using the correct URL: `https://docs.nevermined.app/mcp` * Check your internet connection * Ensure your MCP client supports remote servers #### Authentication Errors * Verify your Nevermined API key is correctly configured * Check that you're using the right environment (sandbox vs production) * Ensure your API key has the necessary permissions #### Documentation Not Loading * Try refreshing the connection in your MCP client * Check if the MCP server is accessible at: [https://docs.nevermined.app/mcp](https://docs.nevermined.app/mcp) * Restart your MCP client if issues persist ### Getting Help If you encounter issues or need assistance: 1. **Check the Documentation**: Use the MCP server to search for relevant guides 2. **Community Support**: Join our [Discord community](https://discord.com/invite/GZju2qScKq) for help 3. **GitHub Issues**: Report bugs or request features on our [GitHub repository](https://github.com/nevermined-io) 4. **Enterprise Support**: Contact our team for enterprise-level assistance ## Next Steps Once you have the Nevermined MCP server set up: 1. **Explore the Documentation**: Use the MCP connection to browse our complete documentation 2. **Build Your First Agent**: Follow the quickstart guide to create your first monetized AI agent 3. **Implement Payment Plans**: Design and configure payment plans that match your business model 4. **Test Integration**: Use the sandbox environment to test your implementation 5. **Go Live**: Deploy your AI agents to production with real payments The Nevermined MCP server provides the foundation for building sophisticated AI applications with built-in monetization. Start exploring and building today! # Build with the Nevermined AI Skill Source: https://docs.nevermined.app/docs/development-guide/build-using-nvm-skill Import Nevermined payment knowledge into your AI coding assistant and start building monetizable agents in minutes. Use the **Nevermined AI Skill** to give your coding assistant deep knowledge of the Nevermined Payments SDK, the x402 payment protocol, and integration patterns for Express.js, FastAPI, Strands agents, MCP servers, and Google A2A. Instead of copy-pasting docs, you import the skill once and your AI assistant knows how to wire up payments for you. ## What's Included The skill provides your coding assistant with: * **SDK initialization** patterns for TypeScript and Python * **Framework-specific middleware** (Express.js, FastAPI, Strands, MCP, Google A2A) * **x402 protocol** flow — headers, verification, settlement * **Payment plan** registration and configuration * **Client-side** integration — ordering plans, generating tokens, calling protected APIs * **Troubleshooting** for common errors (402, `-32003`, `-32002`) ## Install the Skill Choose your AI coding tool below for step-by-step installation instructions. ### Claude Code **One-command install** from the Nevermined repository: ```bash theme={null} claude /install-skill https://github.com/nevermined-io/docs skills/nevermined-payments ``` This downloads the `SKILL.md` and all reference files into your local Claude Code skills directory. Claude Code automatically loads the skill when it's relevant to your work. **Alternative — manual installation:** 1. Clone or download the skill files: ```bash theme={null} git clone https://github.com/nevermined-io/docs.git /tmp/nvm-docs ``` 2. Copy to your personal skills directory: ```bash theme={null} cp -r /tmp/nvm-docs/skills/nevermined-payments ~/.claude/skills/ ``` 3. Or copy to a project-specific location: ```bash theme={null} cp -r /tmp/nvm-docs/skills/nevermined-payments .claude/skills/ ``` Claude Code discovers skills automatically from `~/.claude/skills/` (personal) and `.claude/skills/` (project). ### Cursor **Option A — Remote rule import (recommended):** 1. Open **Cursor Settings** (gear icon) 2. Navigate to **Rules** section 3. Click **Add Remote Rule** 4. Paste the GitHub repository URL: `https://github.com/nevermined-io/docs` 5. Cursor syncs the rules automatically **Option B — Copy rule files to your project:** Download the rule file and add it to your project: ```bash theme={null} mkdir -p .cursor/rules curl -o .cursor/rules/nevermined-payments.mdc \ https://raw.githubusercontent.com/nevermined-io/docs/main/.cursor/rules/nevermined-payments.mdc ``` The `.cursor/rules/*.mdc` format supports glob-based activation — the Nevermined rule auto-activates when you work with `.ts`, `.js`, or `.py` files. ### Windsurf (Codeium) Copy the rule file to your project's Windsurf rules directory: ```bash theme={null} mkdir -p .windsurf/rules curl -o .windsurf/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.windsurf/rules/nevermined-payments.md ``` Windsurf automatically discovers rules from `.windsurf/rules/` and applies them based on their activation mode. Windsurf rules have a 6,000-character limit per file. The Nevermined rule file stays within this limit with the most essential patterns. For the full reference, point your assistant to the `SKILL.md`. ### GitHub Copilot **Repository-wide instructions:** Add the instructions file to your project: ```bash theme={null} mkdir -p .github curl -o .github/copilot-instructions.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.github/copilot-instructions.md ``` Copilot reads `.github/copilot-instructions.md` automatically for every request within the repository. **Path-specific instructions (optional):** For more targeted activation, create a file at `.github/instructions/nevermined.instructions.md`: ```markdown theme={null} --- applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.py" --- When working with Nevermined payments, use the patterns from .github/copilot-instructions.md ``` ### OpenAI Codex CLI Codex CLI reads `AGENTS.md` files for project context. Add it to your project: ```bash theme={null} curl -o AGENTS.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/AGENTS.md ``` Codex CLI discovers `AGENTS.md` automatically by scanning from the current directory up to the repository root. **Alternative — install as a Codex skill:** ```bash theme={null} mkdir -p .agents/skills cp -r skills/nevermined-payments .agents/skills/ ``` Codex CLI detects skills from `.agents/skills/` and loads them when relevant. ### Cline (VS Code) Copy the rule file to your project: ```bash theme={null} mkdir -p .clinerules curl -o .clinerules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.clinerules/nevermined-payments.md ``` Cline discovers all `.md` files in the `.clinerules/` directory and applies them based on the `paths` frontmatter. The Nevermined rule activates for `.ts`, `.js`, and `.py` files. You can also create rules through the Cline UI: click the **+** button in the Rules popover below the chat input. ### Amazon Q Developer Copy the rule file to your project: ```bash theme={null} mkdir -p .amazonq/rules curl -o .amazonq/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.amazonq/rules/nevermined-payments.md ``` Amazon Q automatically uses rules from `.amazonq/rules/` as context. You can toggle rules on/off via the Rules button in the Amazon Q chat panel. ### ChatGPT, Claude.ai, or any other assistant Share the skill content directly with your assistant: 1. Open the [`SKILL.md` file](https://github.com/nevermined-io/docs/blob/main/skills/nevermined-payments/SKILL.md) 2. Copy the contents 3. Paste into your assistant's context, knowledge base, or system prompt For a more concise version, use the [`AGENTS.md` file](https://github.com/nevermined-io/docs/blob/main/AGENTS.md) instead. ## Supported Tools at a Glance | Tool | Config File | Auto-Activates | | ------------------ | ---------------------------------------------------- | ---------------------------------- | | **Claude Code** | `~/.claude/skills/nevermined-payments/SKILL.md` | Yes — when relevant | | **Cursor** | `.cursor/rules/nevermined-payments.mdc` | Yes — on `.ts`, `.js`, `.py` files | | **Windsurf** | `.windsurf/rules/nevermined-payments.md` | Yes — model decides | | **GitHub Copilot** | `.github/copilot-instructions.md` | Yes — always | | **Codex CLI** | `AGENTS.md` or `.agents/skills/nevermined-payments/` | Yes — always or when relevant | | **Cline** | `.clinerules/nevermined-payments.md` | Yes — on `.ts`, `.js`, `.py` files | | **Amazon Q** | `.amazonq/rules/nevermined-payments.md` | Yes — always | ## Example Prompts After installing the skill, try prompts like: "Add Nevermined x402 payment protection to my Express.js API. I want to charge 1 credit per request on the `/ask` endpoint." "Integrate Nevermined payments into my FastAPI app. Use dynamic credits based on the token count in the response." "Create an MCP server with Nevermined payments. Register a weather tool that costs 5 credits per call." "Protect my Strands agent tool with the @requires\_payment decorator. Charge 2 credits per call." ## Alternative: Use the MCP Search Server If you prefer real-time documentation search over a static skill, you can connect your coding assistant to the Nevermined MCP documentation server: ```json theme={null} { "mcpServers": { "nevermined-docs": { "url": "https://docs.nevermined.app/mcp" } } } ``` This gives your assistant the ability to search the full Nevermined documentation on-demand. See [Build Using Nevermined MCP](/docs/development-guide/build-using-nvm-mcp) for details. ## Skill vs. MCP Server | Approach | Best For | Tradeoff | | ----------------------- | ------------------------------------------ | ---------------------------------- | | **AI Skill** (SKILL.md) | Fast, offline-capable, consistent patterns | Static — update when SDK changes | | **MCP Search Server** | Always up-to-date, full doc coverage | Requires network, slower per-query | For the best experience, use both: install the skill for core patterns and connect the MCP server for edge cases. ## What's Next? Register your first agent and plan Full Express.js payment middleware guide Full FastAPI payment middleware guide Build monetizable MCP servers # Nevermined x402 Source: https://docs.nevermined.app/docs/development-guide/nevermined-x402 Nevermined provides first-class support for the x402 payment protocol, enabling AI agents, APIs, and services to charge per-request using secure, locally-signed payment authorizations. # Nevermined x402 Nevermined provides first-class support for the x402 payment protocol, enabling AI agents, APIs, and services to charge per-request using secure, locally-signed payment authorizations. For the complete technical specification, see the [x402 Smart Accounts Extension Spec](/docs/specs/x402-smart-accounts). ## Overview This section explains: * The x402 HTTP 402 handshake and `PAYMENT-SIGNATURE` retry pattern * How Nevermined extends x402 with Smart Accountbased settlement * How subscribers generate and sign x402 payment proofs * How permissions, session keys, and delegated execution work * How the facilitator verifies and settles requests * How to use the Python and TypeScript x402 client libraries * Advanced integration with Google A2A/AP2 For pricing and plan configuration (credits, time-based access, dynamic pricing), see: * [Payment Models](/docs/products/x402-facilitator/payment-models) Nevermined's x402 implementation is compatible with the standard protocol while adding programmable settlement layers powered by Nevermined smart contracts. ## Background: What x402 Solves The x402 protocol defines a payment-enforced HTTP 402 mechanism: 1. A client calls an API. 2. The server responds with HTTP 402 Payment Required and instructions. 3. The client signs a payment authorization locally (no private key leaves the device). 4. The signed authorization is included in the next request. 5. The server forwards it to a facilitator, which: * Verifies the signature * Checks balance/permissions * Settles payment on-chain (EIP-3009 or equivalent) Nevermined extends this with ERC-4337 Smart Accounts, session keys, and programmable billing models, allowing much more complex behavior than standard EIP-3009-based transfers. ## Why Nevermined Extends x402 x402 itself focuses on single ERC-20, pay-per-request flows. Nevermined introduces: | Area | x402 | Nevermined | | --------------------- | --------------- | ------------------------------------------------------------------- | | Payment authorization | EIP-3009 | ERC-4337 UserOps + session keys | | Wallet model | EOA signatures | Smart Accounts with granular permissions | | Billing models | Pay-per-request | Subscriptions, credits, time windows, credit-burning | | Delegated execution | Basic | Fully programmable “burn”, “order”, “redeem”, plan-specific actions | | Settlement layer | ERC-20 | Nevermined smart-contract settlement | This means a subscriber can grant a server the ability to redeem credits or burn usage units while maintaining strict control over what the server can do. ## High-Level Architecture ```mermaid theme={null} flowchart LR A["Subscriber Smart Account Delegates Permissions"] --> B["Client Application"] B --> C["Server / AI Agent"] C --> D["Nevermined Facilitator"] D --> E["Blockchain\nSmart Contracts"] C -- "Delegated Permissions" --> D D -- "Settles Payment" --> E ``` Roles: * Subscriber: owns a Smart Account; delegates permissions through smart account policies * Server/Agent: exposes an API secured by x402 * Facilitator: Nevermined component that verifies and settles payments * Blockchain: executes credit burns, orders, or other plan-specific actions ## The Nevermined x402 Smart-Account Extension Nevermined introduces a new x402 scheme: `nvm:erc4337` This scheme uses ERC-4337 smart accounts instead of EOA wallets, enabling programmable settlement through session keys and UserOperations. Instead of embedding an EIP-3009 transfer, the payload includes: * An EIP-712 signature * One or more session keys * Encoded UserOperations representing actions like: * order (purchase credits if balance is low) * burn (burn credits for usage) * redeem (convert plan entitlements into usage) ### PaymentRequired Response (402) When a server requires payment, it returns a `402` response with a `payment-required` header: ```json theme={null} { "x402Version": 2, "error": "Payment required to access resource", "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepts": [{ "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015" } }], "extensions": {} } ``` ### PaymentPayload (Client Response) The client responds with a `payment-signature` header containing the payment payload: ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks" }, "accepted": { "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015" } }, "payload": { "signature": "0x01845ADb2C711129d4f3966735eD98a9F09fC4cE...", "authorization": { "from": "0xD4f58B60330bC59cB0A07eE6A1A66ad64244eC8c", "sessionKeysProvider": "zerodev", "sessionKeys": [ { "id": "order", "data": "0x20a13d82dd9ee289fc5e5a90f4011c8dc03f8f5d..." }, { "id": "redeem", "data": "0x68e8e34d659149087451cafc89a7320114072e49..." } ] } }, "extensions": {} } ``` ### What the subscriber delegates | Permission | Meaning | | ------------- | ---------------------------------------------------------- | | **order** | Allows facilitator to top-up credits (e.g., auto-purchase) | | **redeem** | Allows facilitator to deduct credits per request | | **\** | Additional session-key-scoped behaviors you define | ## Complete Payment & Execution Flow Below is the Nevermined x402 Smart Account flow (verification + settlement). ```mermaid theme={null} sequenceDiagram autonumber actor Client participant Server as Server / AI Agent participant Facilitator as Facilitator (Nevermined API) participant Chain as Blockchain Client->>Server: GET /resource Server->>Client: 402 Payment Required Client->>Client: Create x402 payload + Session Keys Client->>Server: Request + PAYMENT-SIGNATURE header Server->>Facilitator: /verify Facilitator->>Facilitator: Verify session keys ("burn", "order") Facilitator->>Chain: Check balance Chain-->>Facilitator: Balance alt invalid or insufficient permission Facilitator-->>Server: Verification FAILED Server-->>Client: 402 PAYMENT-FAILED end Facilitator-->>Server: Verification OK Server->>Server: Execute AI/compute workload Server->>Facilitator: /settle Facilitator->>Chain: Execute "order" (if needed) Facilitator->>Chain: Execute "burn" Chain-->>Facilitator: Tx result alt settlement success Facilitator-->>Server: Settlement OK Server-->>Client: Response + txHash end ``` ## Facilitator Responsibilities ### Verification The facilitator validates: * x402 envelope structure * EIP-712 signature * Session key authenticity (data or hash) * UserOperation validity (simulation) * Permission requirements (e.g., burn MUST be delegated) * Subscriber balance and plan state * If verification fails, server returns 402 PAYMENT-FAILED. ### Settlement Settlement runs after the server performs the work: * Execute order (if needed) to top up credits * Execute burn to deduct usage * Submit UserOps on-chain * Return tx hashes to the server ## Developer Guide: Subscriber Flow ### Step 1 — Discover payment requirements When the server returns 402 Payment Required, it includes the `payment-required` header (base64-encoded) with: * Supported schemes (`nvm:erc4337`) * Plan and agent IDs * Network information ### Step 2 — Build a payment payload Using Nevermined Payments libraries (Python or TS), you generate an x402 access token: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' import { X402_HEADERS } from '@nevermined-io/payments/express' const payments = Payments.getInstance({ nvmApiKey: 'nvm:subscriber-key', environment: 'sandbox', }) // Fetch protected resource to get the payment requirements const response = await fetch('https://api.example.com/protected') if (response.status === 402) { // Decode the payment-required header const paymentRequired = JSON.parse( Buffer.from(response.headers.get(X402_HEADERS.PAYMENT_REQUIRED)!, 'base64').toString() ) // Extract planId and agentId from accepts array const { planId, agentId } = paymentRequired.accepts[0] // Generate the x402 access token const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId) } ``` ```python theme={null} import base64 import json import requests from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:subscriber-key", environment="sandbox") ) # Fetch protected resource to get the payment requirements response = requests.get("https://api.example.com/protected") if response.status_code == 402: # Decode the payment-required header payment_required = json.loads( base64.b64decode(response.headers.get("payment-required")).decode() ) # Extract planId and agentId from accepts array plan_id = payment_required["accepts"][0]["planId"] agent_id = payment_required["accepts"][0].get("extra", {}).get("agentId") # Generate the x402 access token token_res = payments.x402.get_x402_access_token(plan_id, agent_id) access_token = token_res["accessToken"] ``` ### Step 3 — Submit with HTTP header Clients include the x402 access token in the `payment-signature` header: ``` payment-signature: ``` ## Developer Guide: Agent Flow ### Quick Integration: Framework Middleware Both TypeScript (Express.js) and Python (FastAPI) have built-in middleware that handles x402 automatically: For Express.js applications, use the `paymentMiddleware` from `@nevermined-io/payments/express`: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' import { paymentMiddleware } from '@nevermined-io/payments/express' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) // One line to protect routes app.use( paymentMiddleware(payments, { 'POST /ask': { planId: PLAN_ID, credits: 1 } }) ) // Route handler - no payment logic needed app.post('/ask', async (req, res) => { const result = await processRequest(req.body) res.json({ result }) }) ``` See the [Express.js Integration Guide](/docs/integrate/add-to-your-agent/express) for full details. For FastAPI applications, use the `PaymentMiddleware` from `payments_py.x402.fastapi`: ```python theme={null} from fastapi import FastAPI from payments_py import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware app = FastAPI() payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ["NVM_API_KEY"], environment="sandbox") ) # One line to protect routes app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": {"plan_id": PLAN_ID, "credits": 1} } ) # Route handler - no payment logic needed @app.post("/ask") async def ask(request: Request): body = await request.json() result = await process_request(body) return {"result": result} ``` See the [FastAPI Integration Guide](/docs/integrate/add-to-your-agent/fastapi) for full details. The middleware handles verification, settlement, and all x402 headers automatically. ### Manual Integration For other frameworks or custom implementations, follow these steps: ### Step 1 — Receive and parse * Read the x402 token from the `payment-signature` header (x402 v2). * If no token is present, return a 402 response with payment requirements. ### Step 2 — Verify with the facilitator ```typescript theme={null} import { Payments, decodeAccessToken } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: 'nvm:agent-key', environment: 'sandbox', }) // Get token from payment-signature header const x402Token = req.headers['payment-signature'] if (!x402Token) { // Return 402 with payment requirements return res.status(402).json({ error: 'Payment Required' }) } // Verify permissions - facilitator extracts planId and subscriberAddress from token const verification = await payments.facilitator.verifyPermissions({ x402AccessToken: x402Token, maxAmount: BigInt(creditsRequired), }) if (!verification.isValid) { return res.status(402).json({ error: 'Payment verification failed' }) } ``` ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) # Get token from payment-signature header x402_token = request.headers.get("payment-signature") if not x402_token: # Return 402 with payment requirements return {"error": "Payment Required"}, 402 # Verify permissions - facilitator extracts planId and subscriberAddress from token verification = payments.facilitator.verify_permissions( x402_access_token=x402_token, max_amount=credits_required, ) if not verification.is_valid: return {"error": "Payment verification failed"}, 402 ``` ### Step 3 — Execute your workload * Perform the paid operation only after verification succeeds. ### Step 4 — Settle ```typescript theme={null} // Settle after work is complete - burns the actual credits used const settlement = await payments.facilitator.settlePermissions({ x402AccessToken: x402Token, maxAmount: BigInt(actualCreditsUsed), }) // Return response with settlement receipt in payment-response header const settlementReceipt = Buffer.from(JSON.stringify({ success: true, creditsRedeemed: actualCreditsUsed, transactionHash: settlement.txHash, })).toString('base64') res.set('payment-response', settlementReceipt) res.json({ result }) ``` ```python theme={null} import base64 import json # Settle after work is complete - burns the actual credits used settlement = payments.facilitator.settle_permissions( x402_access_token=x402_token, max_amount=actual_credits_used, ) # Return response with settlement receipt in payment-response header settlement_receipt = base64.b64encode(json.dumps({ "success": True, "creditsRedeemed": actual_credits_used, "transactionHash": settlement.tx_hash, }).encode()).decode() response.headers["payment-response"] = settlement_receipt return {"result": result} ``` ## Summary This section provides a comprehensive guide for developers integrating Nevermined with x402: * **x402** gives a universal payment-required protocol * **Nevermined** enriches it with Smart Accounts, UserOps, and advanced billing models using the `nvm:erc4337` scheme * **Subscribers** delegate controlled permissions using session keys * **Servers** use `payment-signature` headers and verify/settle via the facilitator * **Facilitators** verify and settle on-chain * **Python & TypeScript libraries** provide turnkey developer tooling * **Express.js and FastAPI middleware** handle the entire flow automatically # Core Concepts Source: https://docs.nevermined.app/docs/getting-started/core-concepts Understand the core building blocks of Nevermined: monetizable services (agents, MCP tools, and assets) and payment plans. Nevermined helps you monetize **AI-enabled services** — whether that’s an agent, an MCP tool/server, or access to a protected resource (like a dataset or file). At the heart of the platform are two concepts: * **Services & Resources**: the thing you’re charging for (an API, a tool, or gated access) * **Payment Plans**: how customers pay and what they’re entitled to consume ## What is a Service or Resource? In the Nevermined ecosystem, a *service* is anything you expose for paid access. That might be: * **AI Agents**: autonomous or human-in-the-loop agents you call via HTTP. * **MCP Tools / Servers**: monetizable tool endpoints accessed via the MCP. * **Protected Assets**: gated resources like files, datasets, prompts, or static downloads. Regardless of the form factor, a monetizable service/resource typically has: * A **public interface** (HTTP endpoint, MCP tool, or download URL) * A **pricing + access policy** (a payment plan) * A **verification step** (validate entitlement before delivering) * **Usage tracking** (meter and redeem credits per request or per access) ### Service Components Name, description, tags, and identifiers for discovery. How clients access the service: HTTP endpoints, MCP tools, or gated assets. One or more plans that define pricing and access terms. Validate entitlement and automatically track/redemption of usage. ## Standards and protocols (x402) Nevermined supports modern payment standards used in AI and API commerce. In particular, **x402** standardizes how clients and servers negotiate payment-required requests, enabling smoother, interoperable monetization flows. If you're integrating with x402-compatible clients/servers, see the [x402 integration](/docs/development-guide/nevermined-x402). ## What is a Payment Plan? A Payment Plan is a set of rules and conditions that define how subscribers pay to access your service/resource. Plans are defined by two main components: their **price** and the **consumption** model. ### Key Attributes of a Payment Plan Choose whether customers pay in **Fiat** (via Stripe) or **Crypto** (including stablecoins). Define price, payout currency, and how revenue is distributed across one or more receivers. * **Time-based**: limited by duration (e.g., 30 days, 1 year) * **Credits-based**: limited by usage (e.g., 100 requests) * **Trial**: free limited access for testing Choose how usage is paid for: * **Credits**: prepaid balance redeemed per request/access * **Pay-as-you-go (PAYG)**: settle in USDC as it happens Perfect for usage-based pricing models: * **Fixed Credits**: set amount of credits (e.g., 100 credits) * **Credits per Request**: set how many credits each request consumes * **Flexible Redemption**: redeem different amounts based on usage Ideal when you want to avoid pre-purchased credits and settle usage as it occurs: * **Per-request settlement**: settle in USDC as each request happens * **No credit balance**: customers pay on demand rather than topping up credits Ideal for subscription models: * **Duration**: set expiration time (days, months, years) * **Unlimited Usage**: within the time period * **Auto-expiry**: plan expires after duration Combine time and credits: * **Credits with Expiry**: e.g., 1000 credits valid for 30 days * **Rate Limiting**: control usage within time periods ### Example Payment Plans * **Customer Support MCP Tool**: $49/month for unlimited usage, or $10 for 1,000 calls. * **Dataset Access**: \$99 for 30-day access to a protected resource. * **Agent API**: \$10 for 100 credits, with each request consuming 1 credit. ## How It All Works Together Define your service interface (API, MCP tool, or protected asset) and metadata. Create one or more payment plans: pricing, credits, duration, and consumption rules. Users buy plans using fiat or crypto and receive entitlements. Validate entitlement (tokens / headers / proxy validation) before serving requests or granting access. Automatically redeem credits per request/access and track consumption. By combining services/resources with flexible payment plans, Nevermined supports monetization across agents, MCP tools, and protected assets. # How It Works Source: https://docs.nevermined.app/docs/getting-started/how-it-works Understand how Nevermined enables paid access to services and resources Nevermined enables paid access to **services and resources** — agent APIs, MCP tools/servers, and protected assets — with a consistent flow for pricing, access control, and settlement. ## The Payment Flow Nevermined Payment Flow ## Three Steps to Monetization Define the thing you want to monetize — an API endpoint, an MCP tool, or a protected asset — plus metadata for discovery. **Examples**: Agent endpoint, MCP tool method, gated download URL. Define pricing and how customers pay: * **Credits** (prepaid) or **pay-as-you-go (PAYG)** * **Time-based** access, **credits-based** usage, or **trial** * Fiat (Stripe) or crypto (including stablecoins) At runtime, check entitlement before delivering the service/resource. * If entitled: deliver and meter usage (redeem credits or settle per request) * If not: return **HTTP 402 Payment Required** (especially for x402 flows) ## Two Integration Paths Configure services/resources and plans via the dashboard. **Best for**: Quick setup, testing, non-technical workflows Integrate in TypeScript/Python or call the REST API for full control. **Best for**: Production systems, automation, custom request flows ## Payment Settlement Options **HTTP 402 Payment Required** Use the x402 standard for payment-required requests. The facilitator can handle verification and settlement. ## What Nevermined Handles | Concern | Nevermined Handles | | ------------------ | ------------------------------------------------ | | Payment processing | Crypto (including stablecoins) and fiat (Stripe) | | Access control | Entitlement validation (plans/tokens/x402) | | Usage metering | Credits redemption or per-request settlement | | Settlement | Payouts to providers | ## Next Steps Learn the platform terminology Integrate quickly Explore the pillar use cases # Welcome Source: https://docs.nevermined.app/docs/getting-started/welcome Nevermined is building the financial rails for AI, making it simple for everyone, from solo-builders and startups to major enterprises, to transact with and monetize AI agents, tools, and services. Launch an integration in 5 minutes Explore the REST API and SDKs ## Start with a use case Nevermined documentation is organized around four common ways teams monetize AI systems: Meter and settle value between autonomous agents. Charge for MCP tools and servers with request-level billing. Protect paid files, datasets, and downloadable resources. Track cost, usage, and margins across agent requests. ### Trusted by Leading AI Companies Nevermined is working with leading AI companies and platforms to power the next generation of agentic commerce, including CrewAI, Olas, Naptha, Mother, and Helicone. Enable human-to-agent and autonomous agent-to-agent payment flows with native support for AI workflows. Usage-based, outcome-based, and value-based pricing. Each ID links every request, policy change, and payout to one immutable record for complete traceability. Integrate with developer-friendly libraries in Python and TypeScript, plus a visual dashboard for monitoring. ## How Nevermined Empowers Your AI Transform your AI agents into revenue-generating services with flexible monetization options tailored for the AI economy. Deploy AI customer service agents with per-ticket billing, resolution-based pricing, or monthly support packages for enterprise clients. Monetize AI legal research and document analysis agents with per-query pricing, hourly billing models, or case-based packages. Create AI sales assistants that qualify leads, schedule meetings, and nurture prospects with performance-based pricing tied to conversions. Build conversational AI agents with per-message billing, token packages, or subscription tiers based on usage volume. Offer AI-powered code reviews, security scans, and optimization suggestions with repository-based or subscription pricing. Provide specialized vision services with dynamic pricing based on image resolution, processing complexity, or analysis depth. Build AI translation and localization agents with pricing based on word count, language complexity, or turnaround time. Create AI analytics services that provide actionable insights with pricing based on dataset size and analysis complexity. Orchestrate complex AI workflows where multiple agents collaborate, with automatic settlement between agent services. ### Integration Options Nevermined integrates into your workflow through multiple options: * **Developer Libraries**: SDKs for TypeScript and Python * **Visual Dashboard**: no-code configuration and real-time monitoring * **REST API**: HTTP endpoints for any language Learn more in our [Integration guides](/docs/integrate/quickstart/5-minute-setup). *** **Ready to ship?** Start with the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). # Amazon Bedrock AgentCore Integration Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/agentcore Add Nevermined x402 payments to agents running on AWS Bedrock AgentCore **Start here:** need to register a service and create a plan first? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Add payment protection to agents running on [Amazon Bedrock AgentCore](https://docs.aws.amazon.com/bedrock-agentcore/) using the [x402 protocol](https://github.com/coinbase/x402). The `@requires_payment` decorator handles verification and settlement automatically, wrapping verify - work - settle in a single Lambda invocation. ## x402 Payment Flow ```mermaid theme={null} sequenceDiagram autonumber actor Client participant Agent as Agent (Lambda)
@requires_payment participant Facilitator as Nevermined Facilitator Client->>Agent: Call agent — no payment-signature Agent-->>Client: 402 PaymentRequired
(payment-required header, base64 JSON) Note over Client: Decode PaymentRequired,
extract planId from accepts[] Client->>Facilitator: get_x402_access_token(plan_id) Facilitator-->>Client: accessToken Client->>Agent: Call agent with payment-signature header Agent->>Facilitator: verify_permissions(token, credits) Facilitator-->>Agent: Verification OK Note over Agent: Execute handler (agent work) Agent->>Facilitator: settle_permissions(token, credits) Facilitator-->>Agent: Settlement OK (creditsRedeemed, tx) Agent-->>Client: Response + payment-response header
(settlement receipt, base64 JSON) ``` ## MCP Event Format AgentCore communicates using [MCP JSON-RPC 2.0](https://modelcontextprotocol.io/specification/2025-03-26). The decorator expects the AgentCore Gateway interceptor event format: ```json theme={null} { "mcp": { "gatewayRequest": { "httpMethod": "POST", "headers": { "payment-signature": "", "Content-Type": "application/json" }, "body": { "jsonrpc": "2.0", "id": "req-1", "method": "tools/call", "params": { "name": "TargetName___toolName", "arguments": { "patient_id": "123" } } } } } } ``` Key points: * The x402 token travels in the `payment-signature` HTTP header * Tool names follow AgentCore convention: `TargetName___toolName` * Only `tools/call` method is billable by default ## Installation ```bash theme={null} pip install payments-py ``` See the [Python SDK installation guide](/docs/api-reference/python/installation) for full setup details. No extra dependencies required. The AgentCore decorator uses only the core `payments-py` package. ## Quick Start: Protecting a Lambda Handler The `@requires_payment` decorator wraps a Lambda handler with x402 payment verification and settlement. ```python filename="lambda_function.py" theme={null} import json import os from payments_py import Payments, PaymentOptions from payments_py.x402.agentcore import requires_payment # Initialize Payments payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ) ) PLAN_ID = os.environ["NVM_PLAN_ID"] AGENT_ID = os.environ.get("NVM_AGENT_ID") @requires_payment( payments=payments, plan_id=PLAN_ID, agent_id=AGENT_ID, credits=1, endpoint="https://gateway.bedrock-agentcore.amazonaws.com/mcp", ) def lambda_handler(event, context=None): """Handler only runs when payment is verified.""" body = event["mcp"]["gatewayRequest"]["body"] tool = body["params"]["name"] args = body["params"]["arguments"] # Agent work result = process_request(tool, args) return { "content": [{"type": "text", "text": json.dumps(result)}], } ``` The decorator automatically: * Returns a `402 PaymentRequired` response when no token is provided * Verifies the x402 token via the Nevermined facilitator * Executes the handler on successful verification * Burns credits after successful execution * Adds `payment-response` header and `_meta.x402` transaction data to the response ## Response Formats The handler can return any of these formats: ### Bare MCP result (simplest) ```python theme={null} return { "content": [{"type": "text", "text": "Hello!"}], } ``` ### With credits override ```python theme={null} return { "content": [{"type": "text", "text": "Expensive result"}], "_meta": {"creditsToCharge": 5}, # Override the decorator's credits } ``` ### Full MCP response body ```python theme={null} return { "jsonrpc": "2.0", "id": "req-1", "result": { "content": [{"type": "text", "text": "Hello!"}], }, } ``` ### Complete InterceptorOutput ```python theme={null} return { "interceptorOutputVersion": "1.0", "mcp": { "transformedGatewayResponse": { "statusCode": 200, "headers": {"Content-Type": "application/json"}, "body": {"jsonrpc": "2.0", "id": "req-1", "result": {...}}, } }, } ``` The decorator wraps and enriches all formats with payment-response headers and `_meta.x402` transaction data. ## Credits Flow ```mermaid theme={null} flowchart TD A["Decorator config: credits=1"] --> B["VERIFY: does subscriber have at least 1 credit?"] B --> C["EXECUTE: handler runs, returns response"] C --> D{"Response has
_meta.creditsToCharge?"} D -->|"Yes (e.g. 5)"| E["SETTLE: burn 5 credits"] D -->|"No"| F["SETTLE: burn 1 credit (config default)"] ``` The `credits` parameter is used for both verify (pre-flight check) and settle (default burn amount). The handler can override the settle amount by returning `_meta.creditsToCharge` in its response. ## Decorator Configuration ### Basic ```python theme={null} @requires_payment(payments=payments, plan_id="plan-123", credits=1) def handler(event, context=None): ... ``` ### With Agent ID ```python theme={null} @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, agent_id=os.environ.get("NVM_AGENT_ID"), endpoint="https://my-gateway.amazonaws.com/mcp", ) def handler(event, context=None): ... ``` ### Dynamic Credits Calculate credits based on the event: ```python theme={null} def calc_credits(event): """Charge based on request complexity.""" args = ( event.get("mcp", {}) .get("gatewayRequest", {}) .get("body", {}) .get("params", {}) .get("arguments", {}) ) return 10 if args.get("detailed") else 1 @requires_payment(payments=payments, plan_id=PLAN_ID, credits=calc_credits) def handler(event, context=None): ... ``` ### Custom Token Header ```python theme={null} @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, token_header="X-Payment", # Or a list: ["X-Payment", "Authorization"] ) def handler(event, context=None): ... ``` ### Lifecycle Hooks ```python theme={null} def on_before_verify(payment_required): print(f"Verifying payment for plan {payment_required.accepts[0].plan_id}") def on_after_verify(verification): print(f"Verified! Payer: {verification.payer}") def on_after_settle(credits_used, settlement): print(f"Settled {credits_used} credits, tx: {settlement.transaction}") def on_payment_error(error): # Return custom error response or None for default 402 return None @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, on_before_verify=on_before_verify, on_after_verify=on_after_verify, on_after_settle=on_after_settle, on_payment_error=on_payment_error, ) def handler(event, context=None): ... ``` ## Client Implementation ```python filename="client.py" theme={null} import os from payments_py import Payments, PaymentOptions # Initialize Payments as subscriber payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_SUBSCRIBER_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ) ) PLAN_ID = os.environ["NVM_PLAN_ID"] AGENT_ID = os.environ.get("NVM_AGENT_ID") # Step 1: Check balance balance = payments.plans.get_plan_balance(plan_id=PLAN_ID) print(f"Balance: {balance.balance} credits") # Step 2: Get x402 access token token_result = payments.x402.get_x402_access_token( plan_id=PLAN_ID, agent_id=AGENT_ID, ) access_token = token_result["accessToken"] # Step 3: Call the agent with the token in payment-signature header import requests response = requests.post( "https://your-agent-endpoint.amazonaws.com/mcp", headers={ "Content-Type": "application/json", "payment-signature": access_token, }, json={ "jsonrpc": "2.0", "id": "1", "method": "tools/call", "params": { "name": "getPatient", "arguments": {"patient_id": "123"}, }, }, ) # Step 4: Check settlement receipt import base64, json payment_response = response.headers.get("payment-response") if payment_response: receipt = json.loads(base64.b64decode(payment_response)) print(f"Credits redeemed: {receipt['creditsRedeemed']}") print(f"Transaction: {receipt['transactionHash']}") ``` ## Decorator vs Interceptor `payments-py` provides two patterns for AgentCore payment protection: | | `@requires_payment` decorator | `AgentCoreInterceptor` class | | ------------------------- | -------------------------------- | ------------------------------------ | | **Invocations** | Single (verify + work + settle) | Two (REQUEST phase, RESPONSE phase) | | **Use case** | Agent IS the Lambda | Separate interceptor Lambda | | **Credits from response** | `_meta.creditsToCharge` override | `_meta.creditsToCharge` extraction | | **Complexity** | One decorator, one function | Separate interceptor + target Lambda | | **Gateway requirement** | Optional | Required (Gateway calls interceptor) | Use the **decorator** when your agent runs as a Lambda function and you want the simplest integration. Use the **interceptor** when you need a separate payment layer in front of an existing agent target (MCP server, OpenAPI, Lambda) via the AgentCore Gateway. ## Environment Variables ```bash filename=".env" theme={null} # Nevermined (required) NVM_API_KEY=sandbox:eyxxxx # sandbox:eyxxxx OR live:eyxxxx NVM_ENVIRONMENT=sandbox # sandbox OR live NVM_PLAN_ID=your-plan-id NVM_AGENT_ID=your-agent-id # Optional ``` ## Next Steps Advanced credit charging patterns Deep dive into x402 payment flows Decorator for Strands AI agent tools Configure credits, subscriptions, and dynamic pricing # Express.js Integration Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/express Add Nevermined x402 payments to your Express.js application with one line of code **Start here:** need to register a service and create a plan first? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Add payment protection to your Express.js API using the [x402 protocol](https://github.com/coinbase/x402). The `paymentMiddleware` handles verification and settlement automatically. ## x402 Payment Flow ``` ┌─────────┐ ┌─────────┐ │ Client │ │ Agent │ └────┬────┘ └────┬────┘ │ │ │ 1. POST /ask (no token) │ │───────────────────────────────────────>│ │ │ │ 2. 402 Payment Required │ │ Header: payment-required (base64) │ │<───────────────────────────────────────│ │ │ │ 3. Generate x402 token via SDK │ │ │ │ 4. POST /ask │ │ Header: payment-signature (token) │ │───────────────────────────────────────>│ │ │ │ - Verify permissions │ │ - Execute request │ │ - Settle (burn credits) │ │ │ │ 5. 200 OK + AI response │ │ Header: payment-response (base64) │ │<───────────────────────────────────────│ │ │ ``` ## Installation ```bash theme={null} npm install @nevermined-io/payments express ``` ## Quick Start: One-Line Payment Protection The `paymentMiddleware` from `@nevermined-io/payments/express` handles the entire x402 flow: ```typescript filename="src/agent.ts" theme={null} import express from 'express' import { Payments } from '@nevermined-io/payments' import { paymentMiddleware } from '@nevermined-io/payments/express' const app = express() app.use(express.json()) // Initialize Payments const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT === 'live' ? 'live' : 'sandbox' }) // Protect routes with one line app.use( paymentMiddleware(payments, { 'POST /ask': { planId: process.env.NVM_PLAN_ID!, credits: 1 } }) ) // Route handler - no payment logic needed! app.post('/ask', async (req, res) => { const { query } = req.body const response = await generateAIResponse(query) res.json({ response }) }) app.listen(3000, () => console.log('Server running on http://localhost:3000')) ``` That's it! The middleware automatically: * Returns `402` with `payment-required` header when no token is provided * Verifies the x402 token via the Nevermined facilitator * Burns credits after request completion * Returns `payment-response` header with settlement receipt ## x402 Headers The middleware follows the [x402 HTTP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md): | Header | Direction | Description | | ------------------- | --------------------- | ----------------------------------- | | `payment-signature` | Client → Server | Base64-encoded x402 access token | | `payment-required` | Server → Client (402) | Base64-encoded payment requirements | | `payment-response` | Server → Client (200) | Base64-encoded settlement receipt | ## Route Configuration ### Fixed Credits ```typescript theme={null} paymentMiddleware(payments, { 'POST /ask': { planId: PLAN_ID, credits: 1 }, 'POST /generate': { planId: PLAN_ID, credits: 5 } }) ``` ### Dynamic Credits Calculate credits based on request/response: ```typescript theme={null} paymentMiddleware(payments, { 'POST /generate': { planId: PLAN_ID, credits: (req, res) => { // Charge based on token count in response const tokens = res.locals.tokenCount || 100 return Math.ceil(tokens / 100) } } }) ``` ### Path Parameters ```typescript theme={null} paymentMiddleware(payments, { 'GET /users/:id': { planId: PLAN_ID, credits: 1 }, 'POST /agents/:agentId/task': { planId: PLAN_ID, credits: 2 } }) ``` ### With Agent ID ```typescript theme={null} paymentMiddleware(payments, { 'POST /task': { planId: PLAN_ID, agentId: AGENT_ID, // Required for plans with multiple agents credits: 5 } }) ``` ## Middleware Options ```typescript theme={null} paymentMiddleware(payments, routes, { // Custom token header(s) - default: 'payment-signature' (x402 v2) tokenHeader: 'payment-signature', // Hook before verification onBeforeVerify: (req, paymentRequired) => { console.log(`Verifying payment for ${req.path}`) }, // Hook after verification (for observability setup) onAfterVerify: (req, verification) => { // Access agentRequest for observability const agentRequest = verification.agentRequest if (agentRequest) { console.log(`Agent: ${agentRequest.agentName}`) } }, // Hook after settlement onAfterSettle: (req, creditsUsed, settlement) => { console.log(`Settled ${creditsUsed} credits, tx: ${settlement.txHash}`) }, // Custom error handler onPaymentError: (error, req, res) => { res.status(402).json({ error: error.message }) } }) ``` ## Complete Example See the complete working example in the [http-simple-agent tutorial](https://github.com/nevermined-io/tutorials/tree/main/http-simple-agent) on GitHub. ```typescript filename="src/agent.ts" theme={null} import express from 'express' import OpenAI from 'openai' import { Payments } from '@nevermined-io/payments' import { paymentMiddleware } from '@nevermined-io/payments/express' const app = express() app.use(express.json()) // Initialize services const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }) // Payment protection with logging app.use( paymentMiddleware(payments, { 'POST /ask': { planId: process.env.NVM_PLAN_ID!, credits: 1 } }, { onBeforeVerify: (req) => { console.log(`[Payment] Verifying request to ${req.path}`) }, onAfterSettle: (req, credits) => { console.log(`[Payment] Settled ${credits} credits`) } }) ) // Protected endpoint app.post('/ask', async (req, res) => { const { query } = req.body const completion = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: query }] }) res.json({ response: completion.choices[0]?.message?.content }) }) // Public endpoint (not in route config) app.get('/health', (req, res) => { res.json({ status: 'ok' }) }) const PORT = process.env.PORT || 3000 app.listen(PORT, () => { console.log(`Agent running on http://localhost:${PORT}`) }) ``` ## Client Implementation Here's how clients interact with your payment-protected API: ```typescript filename="src/client.ts" theme={null} import { Payments } from '@nevermined-io/payments' import { X402_HEADERS } from '@nevermined-io/payments/express' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) async function callProtectedAPI() { const SERVER_URL = 'http://localhost:3000' // Step 1: Request without token → 402 const response1 = await fetch(`${SERVER_URL}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'What is 2+2?' }) }) if (response1.status === 402) { // Step 2: Decode payment requirements const paymentRequired = JSON.parse( Buffer.from( response1.headers.get(X402_HEADERS.PAYMENT_REQUIRED)!, 'base64' ).toString() ) const { planId, extra } = paymentRequired.accepts[0] const agentId = extra?.agentId // Step 3: Generate x402 token const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId) // Step 4: Request with token → 200 const response2 = await fetch(`${SERVER_URL}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json', [X402_HEADERS.PAYMENT_SIGNATURE]: accessToken }, body: JSON.stringify({ query: 'What is 2+2?' }) }) const data = await response2.json() console.log('Response:', data.response) // Step 5: Decode settlement receipt const settlement = JSON.parse( Buffer.from( response2.headers.get(X402_HEADERS.PAYMENT_RESPONSE)!, 'base64' ).toString() ) console.log('Credits used:', settlement.creditsRedeemed) } } ``` ## Environment Variables ```bash filename=".env" theme={null} # Nevermined (required) NVM_API_KEY=nvm:your-api-key NVM_ENVIRONMENT=sandbox NVM_PLAN_ID=your-plan-id # Agent OPENAI_API_KEY=sk-your-openai-api-key PORT=3000 ``` ## Next Steps Advanced credit charging patterns Deep dive into x402 payment flows Configure credits, subscriptions, and dynamic pricing Complete working example on GitHub # FastAPI Integration Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/fastapi Add Nevermined x402 payments to your FastAPI application with one line of code **Start here:** need to register a service and create a plan first? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Add payment protection to your FastAPI application using the [x402 protocol](https://github.com/coinbase/x402). The `PaymentMiddleware` handles verification and settlement automatically. ## x402 Payment Flow ``` ┌─────────┐ ┌─────────┐ │ Client │ │ Agent │ └────┬────┘ └────┬────┘ │ │ │ 1. POST /ask (no token) │ │───────────────────────────────────────>│ │ │ │ 2. 402 Payment Required │ │ Header: payment-required (base64) │ │<───────────────────────────────────────│ │ │ │ 3. Generate x402 token via SDK │ │ │ │ 4. POST /ask │ │ Header: payment-signature (token) │ │───────────────────────────────────────>│ │ │ │ - Verify permissions │ │ - Execute request │ │ - Settle (burn credits) │ │ │ │ 5. 200 OK + AI response │ │ Header: payment-response (base64) │ │<───────────────────────────────────────│ │ │ ``` ## Installation ```bash theme={null} pip install payments-py[fastapi] fastapi uvicorn ``` The `[fastapi]` extra installs FastAPI and Starlette dependencies required for the middleware. ## Quick Start: One-Line Payment Protection The `PaymentMiddleware` from `payments_py.x402.fastapi` handles the entire x402 flow: ```python filename="src/agent.py" theme={null} import os from fastapi import FastAPI, Request from payments_py import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware app = FastAPI() # Initialize Payments payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment="live" if os.environ.get("ENV") == "production" else "sandbox" ) ) # Protect routes with one line app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": {"plan_id": os.environ["NVM_PLAN_ID"], "credits": 1} } ) # Route handler - no payment logic needed! @app.post("/ask") async def ask(request: Request): body = await request.json() response = await generate_ai_response(body.get("query")) return {"response": response} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=3000) ``` That's it! The middleware automatically: * Returns `402` with `payment-required` header when no token is provided * Verifies the x402 token via the Nevermined facilitator * Burns credits after request completion * Returns `payment-response` header with settlement receipt ## x402 Headers The middleware follows the [x402 HTTP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md): | Header | Direction | Description | | ------------------- | --------------------- | ----------------------------------- | | `payment-signature` | Client → Server | Base64-encoded x402 access token | | `payment-required` | Server → Client (402) | Base64-encoded payment requirements | | `payment-response` | Server → Client (200) | Base64-encoded settlement receipt | ## Route Configuration ### Fixed Credits ```python theme={null} app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": {"plan_id": PLAN_ID, "credits": 1}, "POST /generate": {"plan_id": PLAN_ID, "credits": 5} } ) ``` ### Path Parameters ```python theme={null} app.add_middleware( PaymentMiddleware, payments=payments, routes={ "GET /users/:id": {"plan_id": PLAN_ID, "credits": 1}, "POST /agents/:agentId/task": {"plan_id": PLAN_ID, "credits": 2} } ) ``` ### With Agent ID ```python theme={null} app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /task": { "plan_id": PLAN_ID, "agent_id": AGENT_ID, # Required for plans with multiple agents "credits": 5 } } ) ``` ### Using RouteConfig For more explicit configuration, use `RouteConfig`: ```python theme={null} from payments_py.x402.fastapi import PaymentMiddleware, RouteConfig app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": RouteConfig( plan_id=PLAN_ID, credits=1, agent_id=AGENT_ID, network="eip155:84532" # Base Sepolia ) } ) ``` ### Dynamic Credits Calculate credits based on request data: ```python theme={null} async def calculate_credits(request: Request) -> int: """Charge based on requested token count.""" body = await request.json() max_tokens = body.get("max_tokens", 100) return max(1, max_tokens // 100) # 1 credit per 100 tokens app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /generate": { "plan_id": PLAN_ID, "credits": calculate_credits # Pass function instead of int } } ) ``` Sync functions also work: ```python theme={null} app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /analyze": { "plan_id": PLAN_ID, # Simple lambda for sync calculation "credits": lambda req: 5 if req.headers.get("priority") == "high" else 1 } } ) ``` ## Middleware Options ```python theme={null} from payments_py.x402.fastapi import PaymentMiddleware, PaymentMiddlewareOptions async def before_verify(request, payment_required): print(f"Verifying payment for {request.url.path}") async def after_verify(request, verification): # Access agentRequest for observability if verification.agent_request: print(f"Agent: {verification.agent_request.agent_name}") async def after_settle(request, credits_used, settlement): print(f"Settled {credits_used} credits") async def payment_error(error, request): # Return custom response or None to use default return None app.add_middleware( PaymentMiddleware, payments=payments, routes={"POST /ask": {"plan_id": PLAN_ID, "credits": 1}}, options=PaymentMiddlewareOptions( # Custom token header(s) - default: 'payment-signature' (x402 v2) token_header=["payment-signature"], # Hook before verification on_before_verify=before_verify, # Hook after verification (for observability setup) on_after_verify=after_verify, # Hook after settlement on_after_settle=after_settle, # Custom error handler on_payment_error=payment_error ) ) ``` ## Accessing Payment Context After verification, the payment context is available in `request.state.payment_context`: ```python theme={null} from payments_py.x402.fastapi import PaymentContext @app.post("/ask") async def ask(request: Request): # Access payment context for observability or logging payment_context: PaymentContext = request.state.payment_context print(f"Token: {payment_context.token}") print(f"Credits to settle: {payment_context.credits_to_settle}") print(f"Agent request ID: {payment_context.agent_request_id}") # Use agent_request for observability integration if payment_context.agent_request: print(f"Agent: {payment_context.agent_request.agent_name}") print(f"Balance: {payment_context.agent_request.balance}") body = await request.json() response = await generate_ai_response(body.get("query")) return {"response": response} ``` ## Complete Example See the complete working example in the [http-simple-agent-py tutorial](https://github.com/nevermined-io/tutorials/tree/main/http-simple-agent-py) on GitHub. ```python filename="src/agent.py" theme={null} import os from dotenv import load_dotenv load_dotenv() # Load env vars BEFORE importing payments_py from fastapi import FastAPI, Request from openai import OpenAI from payments_py import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware, PaymentMiddlewareOptions app = FastAPI(title="AI Agent with Nevermined Payments") # Initialize services payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox") ) ) openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) PLAN_ID = os.environ["NVM_PLAN_ID"] # Payment protection with logging app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": {"plan_id": PLAN_ID, "credits": 1} }, options=PaymentMiddlewareOptions( on_before_verify=lambda req, pr: print(f"[Payment] Verifying request to {req.url.path}"), on_after_settle=lambda req, credits, settlement: print(f"[Payment] Settled {credits} credits") ) ) # Protected endpoint @app.post("/ask") async def ask(request: Request): body = await request.json() query = body.get("query", "") completion = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": query}] ) return {"response": completion.choices[0].message.content} # Public endpoint (not in route config) @app.get("/health") async def health(): return {"status": "ok"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 3000))) ``` ## With Observability For full observability integration with Helicone: ```python filename="src/agent_observability.py" theme={null} import os from dotenv import load_dotenv load_dotenv() # MUST be before payments_py import from fastapi import FastAPI, Request from openai import OpenAI from payments_py import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware, PaymentContext app = FastAPI() payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox") ) ) PLAN_ID = os.environ["NVM_PLAN_ID"] app.add_middleware( PaymentMiddleware, payments=payments, routes={"POST /ask": {"plan_id": PLAN_ID, "credits": 1}} ) @app.post("/ask") async def ask(request: Request): body = await request.json() query = body.get("query", "") # Get payment context for observability payment_context: PaymentContext = request.state.payment_context agent_request = payment_context.agent_request # Configure OpenAI client with observability headers openai_client = payments.observability.with_openai( OpenAI(api_key=os.environ["OPENAI_API_KEY"]), agent_request ) completion = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": query}] ) return { "response": completion.choices[0].message.content, "observability": { "agent_request_id": payment_context.agent_request_id, "agent_name": agent_request.agent_name if agent_request else None } } ``` ## Client Implementation Here's how clients interact with your payment-protected API: ```python filename="src/client.py" theme={null} import os import base64 import json import httpx from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox") ) ) def call_protected_api(): SERVER_URL = "http://localhost:3000" with httpx.Client(timeout=60.0) as client: # Longer timeout for settlement # Step 1: Request without token → 402 response1 = client.post( f"{SERVER_URL}/ask", json={"query": "What is 2+2?"} ) if response1.status_code == 402: # Step 2: Decode payment requirements payment_required = json.loads( base64.b64decode( response1.headers.get("payment-required") ).decode() ) plan_id = payment_required["accepts"][0]["planId"] agent_id = payment_required["accepts"][0].get("extra", {}).get("agentId") # Step 3: Generate x402 token token_result = payments.x402.get_x402_access_token(plan_id, agent_id) access_token = token_result["accessToken"] # Step 4: Request with token → 200 response2 = client.post( f"{SERVER_URL}/ask", headers={"payment-signature": access_token}, json={"query": "What is 2+2?"} ) data = response2.json() print(f"Response: {data['response']}") # Step 5: Decode settlement receipt settlement = json.loads( base64.b64decode( response2.headers.get("payment-response") ).decode() ) print(f"Credits used: {settlement.get('creditsRedeemed')}") if __name__ == "__main__": call_protected_api() ``` ## Environment Variables ```bash filename=".env" theme={null} # Nevermined (required) NVM_API_KEY=nvm:your-api-key NVM_ENVIRONMENT=sandbox NVM_PLAN_ID=your-plan-id # Agent OPENAI_API_KEY=sk-your-openai-api-key PORT=3000 ``` ## Alternative: Manual Dependency Injection For more control or complex scenarios, you can use FastAPI's dependency injection instead of middleware: ```python filename="src/dependencies/payment.py" theme={null} from fastapi import Request, HTTPException, Depends from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) async def get_payment_proof(request: Request) -> str: """Extract x402 payment proof from payment-signature header.""" payment_proof = request.headers.get('payment-signature') if not payment_proof: raise HTTPException( status_code=402, detail={ 'error': 'Payment Required', 'code': 'PAYMENT_REQUIRED', 'plans': [{'planId': os.environ['PLAN_ID']}] } ) return payment_proof async def validate_payment( request: Request, payment_proof: str = Depends(get_payment_proof) ): """Validate x402 payment proof.""" verification = payments.facilitator.verify_permissions( x402_access_token=payment_proof, max_amount="1" ) if not verification.is_valid: raise HTTPException( status_code=402, detail={'error': 'Payment verification failed'} ) return verification ``` ```python filename="src/routes/query.py" theme={null} from fastapi import APIRouter, Depends from src.dependencies.payment import validate_payment router = APIRouter() @router.post("/query") async def query(request: dict, payment = Depends(validate_payment)): # Your logic here - payment already verified return {"result": "..."} ``` ## Next Steps Advanced credit charging patterns Deep dive into x402 payment flows Configure credits, subscriptions, and dynamic pricing Complete working example on GitHub # Generic HTTP Integration Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/generic-http Add Nevermined payments to any HTTP server **Start here:** need to register a service and create a plan first? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Generic patterns for integrating Nevermined payments into any HTTP server or framework. ## The x402 Payment Flow When using x402, the client typically **doesn't start with a token**. Instead, the server advertises payment requirements via `402 Payment Required`, and the client retries with a payment proof. ```mermaid theme={null} sequenceDiagram participant Client participant YourServer participant Facilitator as Nevermined (Facilitator) Client->>YourServer: Request (no payment proof) YourServer-->>Client: 402 Payment Required + plans/requirements Client->>Client: Build x402 payment proof Client->>YourServer: Retry request + PAYMENT-SIGNATURE YourServer->>Facilitator: Verify/settle (via SDK) Facilitator-->>YourServer: OK (balance/receipt) YourServer-->>Client: 200 OK (response) ``` ## Implementation Steps ### 1. Extract the x402 token from the request x402 clients send the payment token in the `payment-signature` header (lowercase, per x402 v2 spec): ``` payment-signature: ``` ### 2. Return 402 if token is missing When no token is provided, return HTTP 402 with a base64-encoded `payment-required` header: ```typescript theme={null} import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const PLAN_ID = process.env.NVM_PLAN_ID! const AGENT_ID = process.env.NVM_AGENT_ID // Build the payment required object const paymentRequired = buildPaymentRequired(PLAN_ID, { endpoint: req.path, agentId: AGENT_ID, httpVerb: req.method }) // Check for token const token = req.headers['payment-signature'] if (!token) { const paymentRequiredBase64 = Buffer.from( JSON.stringify(paymentRequired) ).toString('base64') return res .status(402) .setHeader('payment-required', paymentRequiredBase64) .json({ error: 'Payment Required', message: 'Missing x402 access token' }) } ``` ```python theme={null} import base64 import json from payments_py.x402.helpers import build_payment_required PLAN_ID = os.environ["NVM_PLAN_ID"] AGENT_ID = os.environ.get("NVM_AGENT_ID") # Build the payment required object payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=request.path, agent_id=AGENT_ID, http_verb=request.method ) # Check for token token = request.headers.get("payment-signature") if not token: payment_required_json = payment_required.model_dump_json(by_alias=True) payment_required_base64 = base64.b64encode( payment_required_json.encode() ).decode() return JSONResponse( status_code=402, content={"error": "Payment Required", "message": "Missing x402 access token"}, headers={"payment-required": payment_required_base64} ) ``` ### 3. Verify permissions with the Facilitator Before processing the request, verify the subscriber has sufficient credits: ```typescript theme={null} const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: token, maxAmount: BigInt(creditsRequired) // e.g., 1n }) if (!verification.isValid) { const paymentRequiredBase64 = Buffer.from( JSON.stringify(paymentRequired) ).toString('base64') return res .status(402) .setHeader('payment-required', paymentRequiredBase64) .json({ error: 'Payment Required', message: verification.invalidReason }) } // Verification passed - process the request ``` ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ["NVM_API_KEY"], environment="sandbox") ) verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=token, max_amount=str(credits_required) # e.g., "1" ) if not verification.is_valid: payment_required_json = payment_required.model_dump_json(by_alias=True) payment_required_base64 = base64.b64encode( payment_required_json.encode() ).decode() return JSONResponse( status_code=402, content={"error": "Payment Required", "message": verification.invalid_reason}, headers={"payment-required": payment_required_base64} ) # Verification passed - process the request ``` ### 4. Process the request Execute your business logic after verification passes. ### 5. Settle (burn credits) after successful response After the request completes successfully, settle the credits and include the receipt in the response: ```typescript theme={null} // After processing the request successfully... const settlement = await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: token, maxAmount: BigInt(creditsUsed) }) // Include settlement receipt in response header const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString('base64') return res .setHeader('payment-response', settlementBase64) .json({ result: yourResponseData }) ``` ```python theme={null} # After processing the request successfully... settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=token, max_amount=str(credits_used) ) # Include settlement receipt in response header settlement_json = settlement.model_dump_json(by_alias=True) settlement_base64 = base64.b64encode(settlement_json.encode()).decode() return JSONResponse( content={"result": your_response_data}, headers={"payment-response": settlement_base64} ) ``` ## x402 Headers Summary | Header | Direction | Description | | ------------------- | --------------------- | ----------------------------------- | | `payment-signature` | Client → Server | x402 access token | | `payment-required` | Server → Client (402) | Base64-encoded payment requirements | | `payment-response` | Server → Client (200) | Base64-encoded settlement receipt | ## HTTP Response Codes | Code | Meaning | When to Use | | ----- | ---------------- | --------------------------------------------------- | | `200` | Success | Valid payment proof, request processed | | `402` | Payment Required | Missing/invalid payment proof, insufficient credits | | `500` | Server Error | Validation system failure | ## Using Framework Middleware (Recommended) For Express.js and FastAPI, use the built-in middleware that handles the entire x402 flow automatically: ```typescript theme={null} import { paymentMiddleware } from '@nevermined-io/payments/express' app.use(paymentMiddleware(payments, { 'POST /ask': { planId: PLAN_ID, credits: 1 } })) ``` See [Express.js Integration](/docs/integrate/add-to-your-agent/express) for details. ```python theme={null} from payments_py.x402.fastapi import PaymentMiddleware app.add_middleware( PaymentMiddleware, payments=payments, routes={"POST /ask": {"plan_id": PLAN_ID, "credits": 1}} ) ``` See [FastAPI Integration](/docs/integrate/add-to-your-agent/fastapi) for details. ## Best Practices For high-traffic endpoints, consider caching validation results briefly (5-30 seconds) to reduce API calls. Set reasonable timeouts for validation calls (5-10 seconds) and have a fallback strategy. Log all payment validations for debugging and analytics: - Token hash (not full token) - Validation result - Credits consumed - Timestamp Always include plan information in 402 responses so clients know how to get access. ## Security Considerations 1. **Never log full tokens** - Hash them if you need to identify requests 2. **Use HTTPS** - Tokens should only travel over encrypted connections 3. **Validate on server** - Never trust client-side validation 4. **Set token expiration** - Accept reasonable token ages ## Next Steps Advanced validation patterns Implement standard HTTP 402 flows # Strands Agent Integration Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/strands Add Nevermined x402 payments to your Strands AI agent tools **Start here:** need to register a service and create a plan first? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Add payment protection to your Strands AI agent tools using the [x402 protocol](https://github.com/coinbase/x402). The `@requires_payment` decorator handles verification and settlement automatically. ## x402 Payment Flow ```mermaid theme={null} sequenceDiagram participant Client participant Agent participant NVM as Nevermined Client->>Agent: agent(prompt) — no token Agent-->>Client: PaymentRequired error (tool result) Client->>Client: extract_payment_required(agent.messages) Client->>NVM: get_x402_access_token(plan_id, agent_id) NVM-->>Client: Access token Client->>Agent: agent(prompt, invocation_state=payment_token) Agent->>NVM: Verify permissions NVM-->>Agent: Valid Agent->>Agent: Execute tool Agent->>NVM: Settle (burn credits) NVM-->>Agent: Settled Agent-->>Client: Result ``` ## How Payment Errors Flow The `@requires_payment` decorator follows the [x402 MCP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v2/mcp.md) — payment errors are returned as **tool results** with `status: "error"`, not raised as exceptions. Each error includes: 1. A **human-readable text block** explaining the payment requirement 2. A **structured JSON block** containing the full `PaymentRequired` object In Strands, tool errors flow through the LLM. The LLM sees the error and relays it to the user in natural language (e.g., *"I need a payment token to use this tool"*). The structured `PaymentRequired` data is preserved in `agent.messages`. Clients use `extract_payment_required(agent.messages)` to get the structured `PaymentRequired` dict from the conversation history. The `PaymentRequired` contains the `accepts` array with plan IDs, schemes, and networks needed to acquire an x402 access token. ## Installation ```bash theme={null} pip install payments-py[strands] strands-agents ``` The `[strands]` extra installs the Strands SDK dependency required for the decorator. ## Quick Start: Protecting a Tool The `@requires_payment` decorator wraps a Strands `@tool` function with x402 payment verification and settlement. You **must** use `@tool(context=True)` instead of plain `@tool`. This tells Strands to inject `tool_context` into the function, which the decorator needs to access `invocation_state` for the payment token. ```python filename="agent.py" theme={null} import os from dotenv import load_dotenv from strands import Agent, tool from payments_py import Payments, PaymentOptions from payments_py.x402.strands import requires_payment load_dotenv() # Initialize Payments payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ) ) PLAN_ID = os.environ["NVM_PLAN_ID"] # Protect a tool with payment @tool(context=True) @requires_payment(payments=payments, plan_id=PLAN_ID, credits=1) def analyze_data(query: str, tool_context=None) -> dict: """Analyze data based on a query. Costs 1 credit per request. Args: query: The data analysis query to process. """ return { "status": "success", "content": [{"text": f"Analysis complete for: {query}"}], } # Create agent with payment-protected tools agent = Agent(tools=[analyze_data]) ``` That's it! The decorator automatically: * Returns a `PaymentRequired` error when no token is provided * Verifies the x402 token via the Nevermined facilitator * Executes the tool function on successful verification * Burns credits after successful execution ## Client-Side: Payment Discovery Clients discover payment requirements by calling the agent without a token, then extracting the `PaymentRequired` from the conversation history: ```python filename="client.py" theme={null} from payments_py import Payments, PaymentOptions from payments_py.x402.strands import extract_payment_required from agent import agent, payments # Step 1: Call agent without token — triggers PaymentRequired result = agent("Analyze the latest sales trends") # Step 2: Extract PaymentRequired from conversation history payment_required = extract_payment_required(agent.messages) if payment_required: # Step 3: Choose a plan and acquire token chosen_plan = payment_required["accepts"][0] plan_id = chosen_plan["planId"] agent_id = (chosen_plan.get("extra") or {}).get("agentId") token_response = payments.x402.get_x402_access_token( plan_id=plan_id, agent_id=agent_id, ) access_token = token_response["accessToken"] # Step 4: Call agent with payment token state = {"payment_token": access_token} result = agent("Analyze the latest sales trends", invocation_state=state) print(f"Result: {result}") # Step 5: Check settlement (stored in invocation_state after successful execution) settlement = state.get("payment_settlement") if settlement: print(f"Credits redeemed: {settlement.credits_redeemed}") ``` ## Decorator Configuration ### Single Plan ```python theme={null} @tool(context=True) @requires_payment(payments=payments, plan_id="plan-123", credits=1) def my_tool(query: str, tool_context=None) -> dict: ... ``` ### Multiple Plans Accept multiple payment plans (e.g., basic and premium tiers): ```python theme={null} @tool(context=True) @requires_payment( payments=payments, plan_ids=["plan-basic", "plan-premium"], credits=1, ) def my_tool(query: str, tool_context=None) -> dict: ... ``` ### Dynamic Credits Calculate credits based on tool arguments: ```python theme={null} def calc_credits(kwargs): """Charge based on complexity.""" return kwargs.get("complexity", 1) * 2 @tool(context=True) @requires_payment(payments=payments, plan_id=PLAN_ID, credits=calc_credits) def my_tool(query: str, complexity: int = 1, tool_context=None) -> dict: ... ``` ### With Agent ID ```python theme={null} @tool(context=True) @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, agent_id=os.environ.get("NVM_AGENT_ID"), # Required for plans with multiple agents ) def my_tool(query: str, tool_context=None) -> dict: ... ``` ### Custom Network ```python theme={null} @tool(context=True) @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, network="eip155:1", # Ethereum mainnet (default: eip155:84532 Base Sepolia) ) def my_tool(query: str, tool_context=None) -> dict: ... ``` ### Lifecycle Hooks ```python theme={null} def on_before_verify(payment_required): print(f"Verifying payment for {len(payment_required.accepts)} plans") def on_after_verify(verification): print(f"Verified! Request ID: {verification.agent_request_id}") def on_after_settle(credits_used, settlement): print(f"Settled {credits_used} credits") def on_payment_error(error): # Return custom error dict or None for default x402 error return None @tool(context=True) @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, on_before_verify=on_before_verify, on_after_verify=on_after_verify, on_after_settle=on_after_settle, on_payment_error=on_payment_error, ) def my_tool(query: str, tool_context=None) -> dict: ... ``` ## Accessing Payment Context After verification, the `PaymentContext` is available in `tool_context.invocation_state["payment_context"]`: ```python theme={null} from payments_py.x402.strands import PaymentContext @tool(context=True) @requires_payment(payments=payments, plan_id=PLAN_ID, credits=1) def my_tool(query: str, tool_context=None) -> dict: """Tool with payment context access.""" ctx = tool_context.invocation_state.get("payment_context") if ctx and isinstance(ctx, PaymentContext): print(f"Token: {ctx.token}") print(f"Credits: {ctx.credits_to_settle}") print(f"Request ID: {ctx.agent_request_id}") print(f"Verified: {ctx.verified}") return {"status": "success", "content": [{"text": "Done"}]} ``` ## Complete Example See the complete working example in the [strands-simple-agent](https://github.com/nevermined-io/hackathons/tree/main/agents/strands-simple-agent) directory on GitHub. It includes: * `agent.py` — Agent with payment-protected tools * `demo.py` — Full payment discovery and token acquisition flow ## Environment Variables ```bash filename=".env" theme={null} # Nevermined (required) NVM_API_KEY=nvm:your-api-key NVM_ENVIRONMENT=sandbox NVM_PLAN_ID=your-plan-id NVM_AGENT_ID=your-agent-id # Optional # LLM Provider OPENAI_API_KEY=sk-your-openai-key # Or configure your preferred model provider ``` ## Next Steps Advanced credit charging patterns Deep dive into x402 payment flows Configure credits, subscriptions, and dynamic pricing Complete working example on GitHub # Charge Credits Source: https://docs.nevermined.app/docs/integrate/patterns/charge-credits Patterns for charging credits when processing requests Copy-paste patterns for deducting credits from subscribers. ## Automatic Credit Deduction With the x402 flow, verification checks permissions without burning credits. After processing, settlement burns the credits: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' const paymentRequired = buildPaymentRequired({ planId: PLAN_ID, endpoint: '/query', agentId: AGENT_ID, httpVerb: 'POST' }) // 1. Verify permissions (does NOT burn credits) const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: token, maxAmount: BigInt(1) }) if (!verification.isValid) { throw new Error('Invalid payment') } // 2. Process request... const result = await processRequest() // 3. Settle (burn credits) after successful processing const settlement = await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: token, maxAmount: BigInt(1) }) console.log(`Remaining credits: ${settlement.remainingBalance}`) ``` ```python theme={null} from payments_py.x402.helpers import build_payment_required payment_required = build_payment_required( plan_id=PLAN_ID, endpoint="/query", agent_id=AGENT_ID, http_verb="POST" ) # 1. Verify permissions (does NOT burn credits) verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=token, max_amount="1" ) if not verification.is_valid: raise Exception('Invalid payment') # 2. Process request... result = process_request() # 3. Settle (burn credits) after successful processing settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=token, max_amount="1" ) print(f"Credits used: {settlement.credits_redeemed}") print(f"Remaining: {settlement.remaining_balance}") ``` ## Variable Credit Charges For operations with variable costs, use the x402 flow: ```typescript theme={null} // Calculate cost based on request function calculateCost(request: any): number { const baseCredits = 1 // Add cost for complexity if (request.options?.highQuality) { return baseCredits + 5 } if (request.prompt.length > 1000) { return baseCredits + 2 } return baseCredits } // Use in settlement const cost = calculateCost(req.body) await payments.facilitator.settlePermissions({ planId: PLAN_ID, maxAmount: BigInt(cost), x402AccessToken: token, subscriberAddress: address }) ``` ```python theme={null} def calculate_cost(request: dict) -> int: base_credits = 1 # Add cost for complexity if request.get('options', {}).get('high_quality'): return base_credits + 5 if len(request.get('prompt', '')) > 1000: return base_credits + 2 return base_credits # Use in settlement cost = calculate_cost(request) await facilitator.settle(payment_payload, PaymentRequirements( plan_id=PLAN_ID, agent_id=AGENT_ID, max_amount=cost, extra={'subscriber_address': address} )) ``` ## Credit Tracking Middleware Track credits used per request: ```typescript theme={null} interface CreditUsage { requestId: string subscriberAddress: string creditsUsed: number creditsBefore: number creditsAfter: number timestamp: Date endpoint: string } async function trackCredits( req: Request, res: Response, next: NextFunction ) { const startBalance = req.payment?.balance || 0 // Store original json method const originalJson = res.json.bind(res) // Override to track response res.json = (body: any) => { const creditsUsed = body.credits?.used || 1 const creditsAfter = body.credits?.remaining || startBalance - creditsUsed const usage: CreditUsage = { requestId: req.id, subscriberAddress: req.payment?.subscriberAddress || 'unknown', creditsUsed, creditsBefore: startBalance, creditsAfter, timestamp: new Date(), endpoint: req.path } // Log or store usage logCreditUsage(usage) return originalJson(body) } next() } function logCreditUsage(usage: CreditUsage) { console.log(`Credit usage: ${JSON.stringify(usage)}`) // Or send to analytics, database, etc. } ``` ```python theme={null} from dataclasses import dataclass from datetime import datetime import uuid @dataclass class CreditUsage: request_id: str subscriber_address: str credits_used: int credits_before: int credits_after: int timestamp: datetime endpoint: str async def track_credits(request: Request, call_next): # Get initial balance from payment validation start_balance = getattr(request.state, 'credits_balance', 0) request_id = str(uuid.uuid4()) response = await call_next(request) # Track usage after response if hasattr(request.state, 'credits_used'): usage = CreditUsage( request_id=request_id, subscriber_address=getattr(request.state, 'subscriber_address', 'unknown'), credits_used=request.state.credits_used, credits_before=start_balance, credits_after=start_balance - request.state.credits_used, timestamp=datetime.now(), endpoint=str(request.url.path) ) log_credit_usage(usage) return response def log_credit_usage(usage: CreditUsage): print(f"Credit usage: {usage}") # Or send to analytics, database, etc. ``` ## Tiered Pricing Charge different amounts based on usage tiers: ```typescript theme={null} interface PricingTier { name: string minTokens: number maxTokens: number creditsPerRequest: number } const PRICING_TIERS: PricingTier[] = [ { name: 'small', minTokens: 0, maxTokens: 100, creditsPerRequest: 1 }, { name: 'medium', minTokens: 101, maxTokens: 500, creditsPerRequest: 3 }, { name: 'large', minTokens: 501, maxTokens: 2000, creditsPerRequest: 5 }, { name: 'xlarge', minTokens: 2001, maxTokens: Infinity, creditsPerRequest: 10 } ] function getCreditsForTokens(tokenCount: number): number { const tier = PRICING_TIERS.find( t => tokenCount >= t.minTokens && tokenCount <= t.maxTokens ) return tier?.creditsPerRequest || 1 } // Usage async function processWithTieredPricing(prompt: string, payment: PaymentInfo) { const estimatedTokens = estimateTokens(prompt) const requiredCredits = getCreditsForTokens(estimatedTokens) if (payment.balance < requiredCredits) { throw new Error(`Insufficient credits. Need ${requiredCredits}, have ${payment.balance}`) } const result = await generateResponse(prompt) const actualTokens = countTokens(result) const actualCredits = getCreditsForTokens(actualTokens) return { result, credits: { estimated: requiredCredits, actual: actualCredits, remaining: payment.balance - actualCredits } } } ``` ```python theme={null} from dataclasses import dataclass from typing import List @dataclass class PricingTier: name: str min_tokens: int max_tokens: int credits_per_request: int PRICING_TIERS: List[PricingTier] = [ PricingTier('small', 0, 100, 1), PricingTier('medium', 101, 500, 3), PricingTier('large', 501, 2000, 5), PricingTier('xlarge', 2001, float('inf'), 10) ] def get_credits_for_tokens(token_count: int) -> int: for tier in PRICING_TIERS: if tier.min_tokens <= token_count <= tier.max_tokens: return tier.credits_per_request return 1 # Usage async def process_with_tiered_pricing(prompt: str, payment: dict) -> dict: estimated_tokens = estimate_tokens(prompt) required_credits = get_credits_for_tokens(estimated_tokens) if payment['balance'] < required_credits: raise ValueError( f"Insufficient credits. Need {required_credits}, have {payment['balance']}" ) result = await generate_response(prompt) actual_tokens = count_tokens(result) actual_credits = get_credits_for_tokens(actual_tokens) return { 'result': result, 'credits': { 'estimated': required_credits, 'actual': actual_credits, 'remaining': payment['balance'] - actual_credits } } ``` ## Pre-Authorization Pattern For long-running operations, pre-authorize credits before starting: ```typescript theme={null} async function processLongRunningTask( x402Token: string, paymentRequired: string, estimatedCredits: number ) { // Step 1: Verify permissions (does not burn credits) const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(estimatedCredits) }) if (!verification.isValid) { throw new Error('Invalid token') } if (verification.balance < estimatedCredits) { throw new Error(`Insufficient credits. Need ${estimatedCredits}, have ${verification.balance}`) } // Step 2: Start the long-running task const taskId = await startTask({}) try { // Step 3: Wait for completion const result = await waitForCompletion(taskId) // Step 4: Calculate actual cost and settle (burn credits) const actualCredits = calculateActualCost(result) const settlement = await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(actualCredits) }) return { result, credits: { estimated: estimatedCredits, actual: actualCredits, remaining: settlement.remainingBalance } } } catch (error) { // Task failed - don't settle (no credits burned) console.error('Task failed:', error) throw error } } ``` ```python theme={null} async def process_long_running_task( x402_token: str, payment_required: str, estimated_credits: int ) -> dict: # Step 1: Verify permissions (does not burn credits) verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=str(estimated_credits) ) if not verification.is_valid: raise ValueError('Invalid token') if verification.balance < estimated_credits: raise ValueError( f"Insufficient credits. Need {estimated_credits}, have {verification.balance}" ) # Step 2: Start the long-running task task_id = await start_task({}) try: # Step 3: Wait for completion result = await wait_for_completion(task_id) # Step 4: Calculate actual cost and settle (burn credits) actual_credits = calculate_actual_cost(result) settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=str(actual_credits) ) return { 'result': result, 'credits': { 'estimated': estimated_credits, 'actual': actual_credits, 'remaining': settlement.remaining_balance } } except Exception as error: # Task failed - don't settle (no credits burned) print(f'Task failed: {error}') raise ``` ## Credit Response Headers Include credit information in response headers: ```typescript theme={null} function addCreditHeaders(res: Response, credits: CreditInfo) { res.setHeader('X-Credits-Used', credits.used.toString()) res.setHeader('X-Credits-Remaining', credits.remaining.toString()) res.setHeader('X-Credits-Plan', credits.planId) } // Usage in middleware app.use((req, res, next) => { const originalJson = res.json.bind(res) res.json = (body: any) => { if (req.payment && body.credits) { addCreditHeaders(res, body.credits) } return originalJson(body) } next() }) ``` ```python theme={null} from fastapi import Response def add_credit_headers(response: Response, credits: dict): response.headers['X-Credits-Used'] = str(credits['used']) response.headers['X-Credits-Remaining'] = str(credits['remaining']) response.headers['X-Credits-Plan'] = credits['plan_id'] # Usage in endpoint @app.post("/query") async def query(request: dict, response: Response): result = await process_query(request) add_credit_headers(response, { 'used': 1, 'remaining': 99, 'plan_id': PLAN_ID }) return result ``` ## Next Steps Time-based access patterns Variable pricing strategies # Dynamic Pricing Source: https://docs.nevermined.app/docs/integrate/patterns/dynamic-pricing Patterns for variable and usage-based pricing Copy-paste patterns for implementing dynamic and usage-based pricing. ## Token-Based Pricing Charge based on input/output token count (common for LLM APIs): ```typescript theme={null} interface TokenPricing { inputTokenPrice: number // credits per 1K input tokens outputTokenPrice: number // credits per 1K output tokens minimumCharge: number // minimum credits per request } const PRICING: TokenPricing = { inputTokenPrice: 1, // 1 credit per 1K input tokens outputTokenPrice: 3, // 3 credits per 1K output tokens minimumCharge: 1 // minimum 1 credit } function calculateTokenCost( inputTokens: number, outputTokens: number, pricing: TokenPricing = PRICING ): number { const inputCost = Math.ceil((inputTokens / 1000) * pricing.inputTokenPrice) const outputCost = Math.ceil((outputTokens / 1000) * pricing.outputTokenPrice) const totalCost = inputCost + outputCost return Math.max(totalCost, pricing.minimumCharge) } // Usage async function processLLMRequest(prompt: string, payment: PaymentInfo) { const inputTokens = countTokens(prompt) const estimatedOutputTokens = inputTokens * 2 // Rough estimate const estimatedCost = calculateTokenCost(inputTokens, estimatedOutputTokens) if (payment.balance < estimatedCost) { throw new Error(`Insufficient credits. Estimated: ${estimatedCost}, Available: ${payment.balance}`) } const response = await generateResponse(prompt) const outputTokens = countTokens(response) const actualCost = calculateTokenCost(inputTokens, outputTokens) return { response, usage: { inputTokens, outputTokens, creditsUsed: actualCost, creditsRemaining: payment.balance - actualCost } } } ``` ```python theme={null} from dataclasses import dataclass import math @dataclass class TokenPricing: input_token_price: float # credits per 1K input tokens output_token_price: float # credits per 1K output tokens minimum_charge: int # minimum credits per request PRICING = TokenPricing( input_token_price=1, # 1 credit per 1K input tokens output_token_price=3, # 3 credits per 1K output tokens minimum_charge=1 # minimum 1 credit ) def calculate_token_cost( input_tokens: int, output_tokens: int, pricing: TokenPricing = PRICING ) -> int: input_cost = math.ceil((input_tokens / 1000) * pricing.input_token_price) output_cost = math.ceil((output_tokens / 1000) * pricing.output_token_price) total_cost = input_cost + output_cost return max(total_cost, pricing.minimum_charge) # Usage async def process_llm_request(prompt: str, payment: dict) -> dict: input_tokens = count_tokens(prompt) estimated_output_tokens = input_tokens * 2 # Rough estimate estimated_cost = calculate_token_cost(input_tokens, estimated_output_tokens) if payment['balance'] < estimated_cost: raise ValueError( f"Insufficient credits. Estimated: {estimated_cost}, Available: {payment['balance']}" ) response = await generate_response(prompt) output_tokens = count_tokens(response) actual_cost = calculate_token_cost(input_tokens, output_tokens) return { 'response': response, 'usage': { 'input_tokens': input_tokens, 'output_tokens': output_tokens, 'credits_used': actual_cost, 'credits_remaining': payment['balance'] - actual_cost } } ``` ## Complexity-Based Pricing Charge based on operation complexity: ```typescript theme={null} enum OperationType { SIMPLE_QUERY = 'simple_query', COMPLEX_ANALYSIS = 'complex_analysis', IMAGE_GENERATION = 'image_generation', BATCH_PROCESSING = 'batch_processing' } const OPERATION_COSTS: Record = { [OperationType.SIMPLE_QUERY]: 1, [OperationType.COMPLEX_ANALYSIS]: 5, [OperationType.IMAGE_GENERATION]: 10, [OperationType.BATCH_PROCESSING]: 20 } function detectOperationType(request: any): OperationType { if (request.type === 'image') { return OperationType.IMAGE_GENERATION } if (request.batch && request.batch.length > 1) { return OperationType.BATCH_PROCESSING } if (request.analysis || request.options?.detailed) { return OperationType.COMPLEX_ANALYSIS } return OperationType.SIMPLE_QUERY } function calculateComplexityCost(request: any): { operationType: OperationType baseCost: number multiplier: number totalCost: number } { const operationType = detectOperationType(request) const baseCost = OPERATION_COSTS[operationType] // Apply multipliers based on options let multiplier = 1 if (request.options?.highQuality) { multiplier *= 1.5 } if (request.options?.priority) { multiplier *= 2 } if (request.batch) { multiplier *= request.batch.length } return { operationType, baseCost, multiplier, totalCost: Math.ceil(baseCost * multiplier) } } ``` ```python theme={null} from enum import Enum from dataclasses import dataclass import math class OperationType(Enum): SIMPLE_QUERY = 'simple_query' COMPLEX_ANALYSIS = 'complex_analysis' IMAGE_GENERATION = 'image_generation' BATCH_PROCESSING = 'batch_processing' OPERATION_COSTS = { OperationType.SIMPLE_QUERY: 1, OperationType.COMPLEX_ANALYSIS: 5, OperationType.IMAGE_GENERATION: 10, OperationType.BATCH_PROCESSING: 20 } def detect_operation_type(request: dict) -> OperationType: if request.get('type') == 'image': return OperationType.IMAGE_GENERATION if request.get('batch') and len(request['batch']) > 1: return OperationType.BATCH_PROCESSING if request.get('analysis') or request.get('options', {}).get('detailed'): return OperationType.COMPLEX_ANALYSIS return OperationType.SIMPLE_QUERY @dataclass class ComplexityCost: operation_type: OperationType base_cost: int multiplier: float total_cost: int def calculate_complexity_cost(request: dict) -> ComplexityCost: operation_type = detect_operation_type(request) base_cost = OPERATION_COSTS[operation_type] # Apply multipliers based on options multiplier = 1.0 options = request.get('options', {}) if options.get('high_quality'): multiplier *= 1.5 if options.get('priority'): multiplier *= 2 if request.get('batch'): multiplier *= len(request['batch']) return ComplexityCost( operation_type=operation_type, base_cost=base_cost, multiplier=multiplier, total_cost=math.ceil(base_cost * multiplier) ) ``` ## Time-Based Dynamic Pricing Adjust pricing based on demand or time of day: ```typescript theme={null} interface DynamicPricing { baseCredits: number peakMultiplier: number offPeakDiscount: number } function getPricingMultiplier(): number { const hour = new Date().getUTCHours() // Peak hours: 9 AM - 6 PM UTC if (hour >= 9 && hour < 18) { return 1.5 // 50% premium } // Off-peak: 10 PM - 6 AM UTC if (hour >= 22 || hour < 6) { return 0.5 // 50% discount } // Standard hours return 1.0 } function calculateDynamicCost( baseCost: number, options?: { ignorePeakPricing?: boolean } ): number { if (options?.ignorePeakPricing) { return baseCost } const multiplier = getPricingMultiplier() return Math.ceil(baseCost * multiplier) } // Usage app.post('/query', requirePayment(1), async (req, res) => { const baseCost = 1 const actualCost = calculateDynamicCost(baseCost) if (req.payment!.balance < actualCost) { return res.status(402).json({ error: 'Insufficient credits', required: actualCost, available: req.payment!.balance, note: 'Prices are higher during peak hours (9 AM - 6 PM UTC)' }) } // Process request... }) ``` ```python theme={null} from datetime import datetime import math def get_pricing_multiplier() -> float: hour = datetime.utcnow().hour # Peak hours: 9 AM - 6 PM UTC if 9 <= hour < 18: return 1.5 # 50% premium # Off-peak: 10 PM - 6 AM UTC if hour >= 22 or hour < 6: return 0.5 # 50% discount # Standard hours return 1.0 def calculate_dynamic_cost( base_cost: int, ignore_peak_pricing: bool = False ) -> int: if ignore_peak_pricing: return base_cost multiplier = get_pricing_multiplier() return math.ceil(base_cost * multiplier) # Usage @app.post("/query") async def query(request: dict, payment: PaymentInfo = Depends(require_payment(1))): base_cost = 1 actual_cost = calculate_dynamic_cost(base_cost) if payment.balance < actual_cost: raise HTTPException( status_code=402, detail={ 'error': 'Insufficient credits', 'required': actual_cost, 'available': payment.balance, 'note': 'Prices are higher during peak hours (9 AM - 6 PM UTC)' } ) # Process request... ``` ## Usage-Based Tiers Implement tiered pricing based on monthly usage: ```typescript theme={null} interface UsageTier { name: string minUsage: number maxUsage: number pricePerCredit: number // Discount rate } const USAGE_TIERS: UsageTier[] = [ { name: 'starter', minUsage: 0, maxUsage: 100, pricePerCredit: 1.0 }, { name: 'growth', minUsage: 101, maxUsage: 1000, pricePerCredit: 0.8 }, { name: 'scale', minUsage: 1001, maxUsage: 10000, pricePerCredit: 0.6 }, { name: 'enterprise', minUsage: 10001, maxUsage: Infinity, pricePerCredit: 0.4 } ] async function getEffectivePrice( subscriberAddress: string, baseCost: number ): Promise<{ cost: number; tier: string; discount: number }> { // Get monthly usage from your tracking system const monthlyUsage = await getMonthlyUsage(subscriberAddress) const tier = USAGE_TIERS.find( t => monthlyUsage >= t.minUsage && monthlyUsage <= t.maxUsage ) || USAGE_TIERS[0] const effectiveCost = Math.ceil(baseCost * tier.pricePerCredit) const discount = Math.round((1 - tier.pricePerCredit) * 100) return { cost: effectiveCost, tier: tier.name, discount } } // Usage const pricing = await getEffectivePrice(subscriberAddress, 10) // { cost: 6, tier: 'scale', discount: 40 } ``` ```python theme={null} @dataclass class UsageTier: name: str min_usage: int max_usage: int price_per_credit: float # Discount rate USAGE_TIERS = [ UsageTier('starter', 0, 100, 1.0), UsageTier('growth', 101, 1000, 0.8), UsageTier('scale', 1001, 10000, 0.6), UsageTier('enterprise', 10001, float('inf'), 0.4) ] async def get_effective_price( subscriber_address: str, base_cost: int ) -> dict: # Get monthly usage from your tracking system monthly_usage = await get_monthly_usage(subscriber_address) tier = next( (t for t in USAGE_TIERS if t.min_usage <= monthly_usage <= t.max_usage), USAGE_TIERS[0] ) effective_cost = math.ceil(base_cost * tier.price_per_credit) discount = round((1 - tier.price_per_credit) * 100) return { 'cost': effective_cost, 'tier': tier.name, 'discount': discount } # Usage pricing = await get_effective_price(subscriber_address, 10) # {'cost': 6, 'tier': 'scale', 'discount': 40} ``` ## Cost Estimation Endpoint Provide a cost estimation endpoint: ```typescript theme={null} app.post('/estimate', async (req, res) => { const { request, subscriberAddress } = req.body // Calculate various cost components const tokenCost = calculateTokenCost( countTokens(request.prompt), estimateOutputTokens(request.prompt) ) const complexityCost = calculateComplexityCost(request) const dynamicMultiplier = getPricingMultiplier() // Get tier discount if subscriber is known let tierDiscount = 0 if (subscriberAddress) { const pricing = await getEffectivePrice(subscriberAddress, 1) tierDiscount = pricing.discount } const baseCost = Math.max(tokenCost, complexityCost.totalCost) const adjustedCost = Math.ceil(baseCost * dynamicMultiplier) const finalCost = Math.ceil(adjustedCost * (1 - tierDiscount / 100)) res.json({ estimate: { baseCost, adjustments: { dynamicPricing: { multiplier: dynamicMultiplier, reason: dynamicMultiplier > 1 ? 'peak_hours' : dynamicMultiplier < 1 ? 'off_peak' : 'standard' }, tierDiscount: { percent: tierDiscount, reason: tierDiscount > 0 ? 'volume_discount' : 'none' } }, finalCost, breakdown: { tokenBased: tokenCost, complexity: complexityCost.totalCost, operationType: complexityCost.operationType } } }) }) ``` ```python theme={null} @app.post("/estimate") async def estimate_cost(request: dict): prompt = request.get('request', {}).get('prompt', '') subscriber_address = request.get('subscriber_address') # Calculate various cost components input_tokens = count_tokens(prompt) estimated_output = estimate_output_tokens(prompt) token_cost = calculate_token_cost(input_tokens, estimated_output) complexity = calculate_complexity_cost(request.get('request', {})) dynamic_multiplier = get_pricing_multiplier() # Get tier discount if subscriber is known tier_discount = 0 if subscriber_address: pricing = await get_effective_price(subscriber_address, 1) tier_discount = pricing['discount'] base_cost = max(token_cost, complexity.total_cost) adjusted_cost = math.ceil(base_cost * dynamic_multiplier) final_cost = math.ceil(adjusted_cost * (1 - tier_discount / 100)) return { 'estimate': { 'base_cost': base_cost, 'adjustments': { 'dynamic_pricing': { 'multiplier': dynamic_multiplier, 'reason': 'peak_hours' if dynamic_multiplier > 1 else 'off_peak' if dynamic_multiplier < 1 else 'standard' }, 'tier_discount': { 'percent': tier_discount, 'reason': 'volume_discount' if tier_discount > 0 else 'none' } }, 'final_cost': final_cost, 'breakdown': { 'token_based': token_cost, 'complexity': complexity.total_cost, 'operation_type': complexity.operation_type.value } } } ``` ## Next Steps Implement variable settlement with x402 Credit charging patterns # Subscription Access Source: https://docs.nevermined.app/docs/integrate/patterns/subscription-access Patterns for time-based subscription access control Copy-paste patterns for implementing time-based subscription access. ## Basic Time-Based Validation For subscription plans, validate that the user's access hasn't expired: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' async function validateSubscription( x402Token: string, planId: string, agentId: string, endpoint: string ) { const paymentRequired = buildPaymentRequired({ planId, endpoint, agentId, httpVerb: 'POST' }) const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(1) }) if (!result.isValid) { return { valid: false, reason: result.reason } } // For time-based plans, check expiration if (result.expiresAt) { const now = new Date() const expiry = new Date(result.expiresAt) if (now > expiry) { return { valid: false, reason: 'SUBSCRIPTION_EXPIRED', expiredAt: expiry } } const daysRemaining = Math.ceil( (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) return { valid: true, expiresAt: expiry, daysRemaining } } return { valid: true } } ``` ```python theme={null} from datetime import datetime from payments_py.x402.helpers import build_payment_required def validate_subscription( x402_token: str, plan_id: str, agent_id: str, endpoint: str ) -> dict: payment_required = build_payment_required( plan_id=plan_id, endpoint=endpoint, agent_id=agent_id, http_verb="POST" ) result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1" ) if not result.is_valid: return { 'valid': False, 'reason': getattr(result, 'reason', None) } # For time-based plans, check expiration if hasattr(result, 'expires_at') and result.expires_at: now = datetime.now() expiry = datetime.fromisoformat(result.expires_at) if now > expiry: return { 'valid': False, 'reason': 'SUBSCRIPTION_EXPIRED', 'expired_at': expiry.isoformat() } days_remaining = (expiry - now).days return { 'valid': True, 'expires_at': expiry.isoformat(), 'days_remaining': days_remaining } return {'valid': True} ``` ## Subscription Middleware Express middleware for subscription-only endpoints: ```typescript filename="middleware/subscription.ts" theme={null} import { Request, Response, NextFunction } from 'express' import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) export interface SubscriptionInfo { valid: boolean expiresAt?: Date daysRemaining?: number planId: string } declare global { namespace Express { interface Request { subscription?: SubscriptionInfo } } } export function requireSubscription( planId: string, agentId: string, options?: { warnDaysBefore?: number } ) { const warnDays = options?.warnDaysBefore || 7 return async (req: Request, res: Response, next: NextFunction) => { const x402Token = req.headers['payment-signature'] as string if (!x402Token) { return res.status(402).json({ error: 'Subscription Required', code: 'MISSING_TOKEN' }) } try { const paymentRequired = buildPaymentRequired({ planId, endpoint: req.path, agentId, httpVerb: req.method }) const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(1) }) if (!result.isValid) { return res.status(402).json({ error: 'Subscription Required', code: result.reason || 'INVALID_TOKEN' }) } // Check expiration for time-based plans if (result.expiresAt) { const now = new Date() const expiry = new Date(result.expiresAt) if (now > expiry) { return res.status(402).json({ error: 'Subscription Expired', code: 'SUBSCRIPTION_EXPIRED', expiredAt: expiry.toISOString() }) } const daysRemaining = Math.ceil( (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) req.subscription = { valid: true, expiresAt: expiry, daysRemaining, planId } // Add warning header if expiring soon if (daysRemaining <= warnDays) { res.setHeader('X-Subscription-Warning', `Expires in ${daysRemaining} days`) } } else { req.subscription = { valid: true, planId } } next() } catch (error) { console.error('Subscription validation error:', error) return res.status(500).json({ error: 'Validation failed' }) } } } ``` For most use cases, the built-in `paymentMiddleware` from `@nevermined-io/payments/express` handles all of this automatically. Use this custom middleware only when you need fine-grained control over subscription expiration warnings. ## FastAPI Subscription Dependency ```python filename="dependencies/subscription.py" theme={null} from fastapi import Request, HTTPException from datetime import datetime from dataclasses import dataclass from typing import Optional from payments_py.x402.helpers import build_payment_required @dataclass class SubscriptionInfo: valid: bool expires_at: Optional[datetime] = None days_remaining: Optional[int] = None plan_id: Optional[str] = None def require_subscription( plan_id: str, agent_id: str, warn_days_before: int = 7 ): async def validate(request: Request) -> SubscriptionInfo: x402_token = request.headers.get('payment-signature', '') if not x402_token: raise HTTPException( status_code=402, detail={'error': 'Subscription Required', 'code': 'MISSING_TOKEN'} ) payment_required = build_payment_required( plan_id=plan_id, endpoint=str(request.url.path), agent_id=agent_id, http_verb=request.method ) result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1" ) if not result.is_valid: raise HTTPException( status_code=402, detail={ 'error': 'Subscription Required', 'code': getattr(result, 'reason', 'INVALID_TOKEN') } ) # Check expiration for time-based plans if hasattr(result, 'expires_at') and result.expires_at: now = datetime.now() expiry = datetime.fromisoformat(result.expires_at) if now > expiry: raise HTTPException( status_code=402, detail={ 'error': 'Subscription Expired', 'code': 'SUBSCRIPTION_EXPIRED', 'expired_at': expiry.isoformat() } ) days_remaining = (expiry - now).days return SubscriptionInfo( valid=True, expires_at=expiry, days_remaining=days_remaining, plan_id=plan_id ) return SubscriptionInfo(valid=True, plan_id=plan_id) return validate ``` For most use cases, the built-in `PaymentMiddleware` from `payments_py.x402.fastapi` handles all of this automatically. Use this custom dependency only when you need fine-grained control over subscription expiration warnings. ## Subscription Status Endpoint Provide an endpoint for clients to check their subscription status: ```typescript theme={null} app.get('/subscription/status', async (req, res) => { const x402Token = req.headers['payment-signature'] as string if (!x402Token) { return res.status(402).json({ error: 'Missing payment-signature header' }) } try { const paymentRequired = buildPaymentRequired({ planId: PLAN_ID, endpoint: '/subscription/status', agentId: AGENT_ID, httpVerb: 'GET' }) const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(0) // status check, no credits consumed }) if (!result.isValid) { return res.json({ active: false, reason: result.reason }) } const response: any = { active: true, planId: PLAN_ID } if (result.expiresAt) { const expiry = new Date(result.expiresAt) const now = new Date() const daysRemaining = Math.ceil( (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) response.expiresAt = expiry.toISOString() response.daysRemaining = daysRemaining response.status = daysRemaining <= 7 ? 'expiring_soon' : 'active' } if (result.balance !== undefined) { response.creditsRemaining = result.balance } res.json(response) } catch (error) { res.status(500).json({ error: 'Failed to check status' }) } }) ``` ```python theme={null} @app.get("/subscription/status") async def subscription_status(request: Request): x402_token = request.headers.get('payment-signature', '') if not x402_token: raise HTTPException(status_code=402, detail='Missing payment-signature header') try: payment_required = build_payment_required( plan_id=PLAN_ID, endpoint="/subscription/status", agent_id=AGENT_ID, http_verb="GET" ) result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="0" # status check, no credits consumed ) if not result.is_valid: return { 'active': False, 'reason': getattr(result, 'reason', None) } response = { 'active': True, 'plan_id': PLAN_ID } if hasattr(result, 'expires_at') and result.expires_at: expiry = datetime.fromisoformat(result.expires_at) now = datetime.now() days_remaining = (expiry - now).days response['expires_at'] = expiry.isoformat() response['days_remaining'] = days_remaining response['status'] = 'expiring_soon' if days_remaining <= 7 else 'active' if hasattr(result, 'balance') and result.balance is not None: response['credits_remaining'] = result.balance return response except Exception as e: raise HTTPException(status_code=500, detail='Failed to check status') ``` ## Graceful Expiration Handling Handle subscription expiration gracefully with warnings: ```typescript theme={null} interface ExpirationResponse { allowed: boolean warning?: string action?: string daysRemaining?: number } function handleExpiration(expiresAt: Date): ExpirationResponse { const now = new Date() const daysRemaining = Math.ceil( (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) // Already expired if (daysRemaining <= 0) { return { allowed: false, warning: 'Your subscription has expired', action: 'renew', daysRemaining: 0 } } // Grace period (allow access but warn) if (daysRemaining <= 3) { return { allowed: true, warning: `Your subscription expires in ${daysRemaining} day(s). Please renew to avoid interruption.`, action: 'renew_soon', daysRemaining } } // Warning period if (daysRemaining <= 7) { return { allowed: true, warning: `Your subscription expires in ${daysRemaining} days`, daysRemaining } } // All good return { allowed: true, daysRemaining } } ``` ```python theme={null} from dataclasses import dataclass from typing import Optional @dataclass class ExpirationResponse: allowed: bool warning: Optional[str] = None action: Optional[str] = None days_remaining: Optional[int] = None def handle_expiration(expires_at: datetime) -> ExpirationResponse: now = datetime.now() days_remaining = (expires_at - now).days # Already expired if days_remaining <= 0: return ExpirationResponse( allowed=False, warning='Your subscription has expired', action='renew', days_remaining=0 ) # Grace period (allow access but warn) if days_remaining <= 3: return ExpirationResponse( allowed=True, warning=f'Your subscription expires in {days_remaining} day(s). Please renew to avoid interruption.', action='renew_soon', days_remaining=days_remaining ) # Warning period if days_remaining <= 7: return ExpirationResponse( allowed=True, warning=f'Your subscription expires in {days_remaining} days', days_remaining=days_remaining ) # All good return ExpirationResponse( allowed=True, days_remaining=days_remaining ) ``` ## Hybrid Plans (Time + Credits) Handle plans that have both time limits and credit limits: ```typescript theme={null} interface HybridValidation { valid: boolean reason?: string timeRemaining?: number // days creditsRemaining?: number } async function validateHybridPlan( x402Token: string, paymentRequired: string, maxAmount: bigint ): Promise { const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount }) if (!result.isValid) { return { valid: false, reason: result.reason } } const issues: string[] = [] // Check time if (result.expiresAt) { const now = new Date() const expiry = new Date(result.expiresAt) const daysRemaining = Math.ceil( (expiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) if (daysRemaining <= 0) { return { valid: false, reason: 'TIME_EXPIRED' } } } // Check credits if (result.balance !== undefined && result.balance <= 0) { return { valid: false, reason: 'NO_CREDITS' } } return { valid: true, timeRemaining: result.expiresAt ? Math.ceil((new Date(result.expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : undefined, creditsRemaining: result.balance } } ``` ```python theme={null} @dataclass class HybridValidation: valid: bool reason: Optional[str] = None time_remaining: Optional[int] = None # days credits_remaining: Optional[int] = None def validate_hybrid_plan( x402_token: str, payment_required: str, max_amount: str ) -> HybridValidation: result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=max_amount ) if not result.is_valid: return HybridValidation(valid=False, reason=getattr(result, 'reason', None)) # Check time expiry = None if hasattr(result, 'expires_at') and result.expires_at: now = datetime.now() expiry = datetime.fromisoformat(result.expires_at) days_remaining = (expiry - now).days if days_remaining <= 0: return HybridValidation(valid=False, reason='TIME_EXPIRED') # Check credits if hasattr(result, 'balance') and result.balance is not None and result.balance <= 0: return HybridValidation(valid=False, reason='NO_CREDITS') return HybridValidation( valid=True, time_remaining=(expiry - datetime.now()).days if expiry else None, credits_remaining=getattr(result, 'balance', None) ) ``` ## Next Steps Variable pricing strategies Request validation patterns # Validate Requests Source: https://docs.nevermined.app/docs/integrate/patterns/validate-requests Patterns for validating payment tokens in your API, agent, or protected resource Copy-paste patterns for validating payment requests for APIs, agents/tools, and protected resources. Looking for framework-specific implementations? * [Express.js Integration](/docs/integrate/add-to-your-agent/express) * [FastAPI Integration](/docs/integrate/add-to-your-agent/fastapi) * [Generic HTTP Integration](/docs/integrate/add-to-your-agent/generic-http) ## Basic validation (framework-agnostic) At a minimum, validation has three steps: 1. Extract x402 token from the `payment-signature` header 2. Verify permissions using the facilitator 3. Return `402 Payment Required` when missing/invalid ```typescript theme={null} import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) export async function validateRequest( x402Token: string, planId: string, agentId: string, endpoint: string, httpVerb: string ) { // Build payment requirements const paymentRequired = buildPaymentRequired({ planId, endpoint, agentId, httpVerb }) // Verify permissions const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(1) }) return { isValid: verification.isValid, balance: verification.balance, subscriberAddress: verification.subscriberAddress } } // Pseudo-usage // - Extract token from payment-signature header // - Call validateRequest(token, planId, agentId, endpoint, httpVerb) // - If invalid, return 402 ``` ```python theme={null} import os from payments_py import Payments, PaymentOptions from payments_py.x402.helpers import build_payment_required payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) def validate_request( x402_token: str, plan_id: str, agent_id: str, endpoint: str, http_verb: str ) -> dict: # Build payment requirements payment_required = build_payment_required( plan_id=plan_id, endpoint=endpoint, agent_id=agent_id, http_verb=http_verb ) # Verify permissions verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1" ) return { 'is_valid': verification.is_valid, 'balance': verification.balance, 'subscriber_address': verification.subscriber_address } # Pseudo-usage: # - Extract token from payment-signature header # - Call validate_request(token, plan_id, agent_id, endpoint, http_verb) # - If invalid, return 402 ``` ## Validation with minimum credits Check that the user has enough credits for the operation: ```typescript theme={null} import { buildPaymentRequired } from '@nevermined-io/payments' export async function validateWithMinimum( x402Token: string, planId: string, agentId: string, endpoint: string, minCredits: number ) { const paymentRequired = buildPaymentRequired({ planId, endpoint, agentId, httpVerb: 'POST' }) const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: BigInt(minCredits) }) if (!result.isValid) { return { valid: false, reason: 'invalid_token' as const } } if (result.balance < minCredits) { return { valid: false, reason: 'insufficient_credits' as const, required: minCredits, available: result.balance } } return { valid: true, balance: result.balance } } // Example: expensive operation requiring 10 credits const validation = await validateWithMinimum(token, PLAN_ID, AGENT_ID, '/query', 10) if (!validation.valid) { // return 402 } ``` ```python theme={null} from payments_py.x402.helpers import build_payment_required async def validate_with_minimum( x402_token: str, plan_id: str, agent_id: str, endpoint: str, min_credits: int ) -> dict: payment_required = build_payment_required( plan_id=plan_id, endpoint=endpoint, agent_id=agent_id, http_verb="POST" ) result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=str(min_credits) ) if not result.is_valid: return {'valid': False, 'reason': 'invalid_token'} if result.balance < min_credits: return { 'valid': False, 'reason': 'insufficient_credits', 'required': min_credits, 'available': result.balance } return {'valid': True, 'balance': result.balance} # Example: expensive operation requiring 10 credits validation = await validate_with_minimum(token, PLAN_ID, AGENT_ID, "/query", 10) if not validation['valid']: # return 402 pass ``` ## Validation response handling Handle all possible validation outcomes: ```typescript theme={null} async function handleValidation( x402Token: string, paymentRequired: string, maxAmount: bigint ) { try { const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount }) if (!result.isValid) { // Determine specific error switch (result.reason) { case 'TOKEN_EXPIRED': return { status: 402, error: 'Access token has expired', code: 'TOKEN_EXPIRED', action: 'refresh_token' } case 'INSUFFICIENT_BALANCE': return { status: 402, error: 'Insufficient credits', code: 'INSUFFICIENT_BALANCE', action: 'purchase_credits' } case 'PLAN_EXPIRED': return { status: 402, error: 'Plan has expired', code: 'PLAN_EXPIRED', action: 'renew_plan' } default: return { status: 402, error: 'Invalid token', code: 'INVALID_TOKEN', action: 'get_new_token' } } } return { status: 200, balance: result.balance } } catch (error) { console.error('Validation error:', error) return { status: 500, error: 'Validation service unavailable', code: 'SERVICE_ERROR' } } } ``` ```python theme={null} async def handle_validation( x402_token: str, payment_required: str, max_amount: str ) -> dict: try: result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=max_amount ) if not result.is_valid: reason = getattr(result, 'reason', 'UNKNOWN') error_map = { 'TOKEN_EXPIRED': { 'status': 402, 'error': 'Access token has expired', 'code': 'TOKEN_EXPIRED', 'action': 'refresh_token' }, 'INSUFFICIENT_BALANCE': { 'status': 402, 'error': 'Insufficient credits', 'code': 'INSUFFICIENT_BALANCE', 'action': 'purchase_credits' }, 'PLAN_EXPIRED': { 'status': 402, 'error': 'Plan has expired', 'code': 'PLAN_EXPIRED', 'action': 'renew_plan' } } return error_map.get(reason, { 'status': 402, 'error': 'Invalid token', 'code': 'INVALID_TOKEN', 'action': 'get_new_token' }) return {'status': 200, 'balance': result.balance} except Exception as e: print(f'Validation error: {e}') return { 'status': 500, 'error': 'Validation service unavailable', 'code': 'SERVICE_ERROR' } ``` ## Caching validation results For high-traffic endpoints, cache validation briefly: ```typescript theme={null} import { LRUCache } from 'lru-cache' const validationCache = new LRUCache({ max: 1000, ttl: 1000 * 30 // 30 seconds }) async function validateWithCache( x402Token: string, paymentRequired: string, maxAmount: bigint ) { // Create cache key from token hash const cacheKey = hashToken(x402Token) const cached = validationCache.get(cacheKey) if (cached) { return cached } const result = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount }) if (result.isValid) { validationCache.set(cacheKey, result) } return result } function hashToken(token: string): string { return crypto.createHash('sha256').update(token).digest('hex').slice(0, 16) } ``` ```python theme={null} from datetime import datetime, timedelta import hashlib validation_cache = {} CACHE_TTL = timedelta(seconds=30) async def validate_with_cache( x402_token: str, payment_required: str, max_amount: str ) -> dict: cache_key = hash_token(x402_token) # Check cache if cache_key in validation_cache: cached, timestamp = validation_cache[cache_key] if datetime.now() - timestamp < CACHE_TTL: return cached result = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount=max_amount ) if result.is_valid: validation_cache[cache_key] = (result, datetime.now()) return result def hash_token(token: str) -> str: return hashlib.sha256(token.encode()).hexdigest()[:16] ``` ## Next steps Deduct credits for requests Time-based access patterns # 5-Minute Setup Source: https://docs.nevermined.app/docs/integrate/quickstart/5-minute-setup Get a paid endpoint working in 5 minutes (agent API, MCP tool, or protected resource) This guide gets you from zero to a working payment integration in 5 minutes. By the end, you’ll have a **monetizable service** set up with Nevermined — typically an **agent API**, but the same flow applies to **MCP tools/servers** and **protected assets**. > In the code below we use “agent” terminology because it maps to the underlying object model, but you can think of it as *the service/resource you’re charging for*. ## Prerequisites 1. Go to [nevermined.app](https://nevermined.app) and sign in 2. Navigate to **Settings > API Keys** 3. Generate a new key and copy it ```bash theme={null} export NVM_API_KEY="nvm:your-api-key-here" ``` ```bash theme={null} npm install @nevermined-io/payments ``` ```bash theme={null} pip install payments-py ``` ## Register Your Agent Create a scrupt to register your agent and a payment plan. You can register your agent and any payment plan using the following code OR using the [Nevermined App](https://nevermined.app). So not need to code this, just follow the steps in the app to create your agents and plans. If you’re monetizing an agent, MCP tool/server or a protected resource, you’ll still register the monetizable unit and attach a plan — only the **delivery step** changes. ```typescript filename="register-agent.ts" theme={null} import { Payments } from '@nevermined-io/payments' // In this example we require a payment a payment of 10 USDC for 100 requests // For that USDC payment we use USDC on Base Sepolia, so we need its contract address: const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' async function main() { // 1. Initialize the SDK const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) // 2. Register service + payment plan ("agent" in the SDK) const { agentId, planId } = await payments.agents.registerAgentAndPlan( // Service metadata { name: 'My AI Assistant', description: 'A paid service (agent API / MCP tool / protected resource)', tags: ['ai', 'payments'], dateCreated: new Date() }, // Service interface - replace with your endpoint { endpoints: [{ POST: 'https://your-api.com/query' }] }, // Plan metadata { name: 'Starter Plan', description: '100 requests for $10', dateCreated: new Date() }, // Price: 10 USDC payments.plans.getERC20PriceConfig( 10_000_000n, // 10 USDC (6 decimals) USDC_ADDRESS, process.env.BUILDER_ADDRESS! // Your wallet address ), // Credits: 100 requests, 1 credit each payments.plans.getFixedCreditsConfig(100n, 1n) ) console.log('Registered!') console.log(`Service (agent) ID: ${agentId}`) console.log(`Plan ID: ${planId}`) console.log('\nSave these IDs for your integration.') } main().catch(console.error) ``` ```python filename="register_agent.py" theme={null} import os from payments_py import Payments, PaymentOptions from payments_py.plans import get_erc20_price_config, get_fixed_credits_config # In this example we require a payment a payment of 10 USDC for 100 requests # For that USDC payment we use USDC on Base Sepolia, so we need its contract address: USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' def main(): payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox' ) ) result = payments.agents.register_agent_and_plan( agent_metadata={ 'name': 'My AI Assistant', 'description': 'A paid service (agent API / MCP tool / protected resource)', 'tags': ['ai', 'payments'] }, agent_api={ 'endpoints': [{'POST': 'https://your-api.com/query'}] }, plan_metadata={ 'name': 'Starter Plan', 'description': '100 requests for $10' }, price_config=get_erc20_price_config( 10_000_000, # 10 USDC (6 decimals) USDC_ADDRESS, os.environ['BUILDER_ADDRESS'] ), credits_config=get_fixed_credits_config(100, 1), access_limit='credits' ) print('Registered!') print(f"Service (agent) ID: {result['agentId']}") print(f"Plan ID: {result['planId']}") print('\nSave these IDs for your integration.') if __name__ == '__main__': main() ``` Run it: ```bash theme={null} npx ts-node register-agent.ts ``` ```bash theme={null} python register_agent.py ``` ## Add Payment Validation in the Agent Add this middleware to your agent to validate if a request is valid (have a payment attached) before delivering your response/resource. The example shows an HTTP API route; the same check can be used before calling an MCP tool handler or before serving a protected asset: ```typescript filename="middleware/payments.ts" theme={null} import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: 'nvm:agent-key', environment: 'sandbox', }) // Get token from payment-signature header const x402Token = req.headers['payment-signature'] if (!x402Token) { // Return 402 with payment requirements return res.status(402).json({ error: 'Payment Required' }) } // Build payment required specification const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) // Verify permissions - facilitator extracts planId and subscriberAddress from token const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: 1n, }) if (!verification.isValid) { return res.status(402).json({ error: 'Payment verification failed' }) } ////////// // Here you can execute your logic knowing payment is valid ////////// ``` ```python filename="middleware/payments.py" theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) # Get token from payment-signature header x402_token = request.headers.get("payment-signature") if not x402_token: # Return 402 with payment requirements return {"error": "Payment Required"}, 402 # Verify permissions - facilitator extracts planId and subscriberAddress from token verification = payments.facilitator.verify_permissions( x402_access_token=x402_token, max_amount=credits_required, ) if not verification.is_valid: return {"error": "Payment verification failed"}, 402 ###### # Here you can execute your logic knowing payment is valid ###### ``` ## Integrate the validation code in your agent/server Integrate the above validation code into your agent/server code at the very beginning of your request handling logic. The payment validation should occur before any processing of the request and can be complemented by any other authorization logic you may have. ## Test It **1) Try without payment (should fail)** ```bash theme={null} curl -X POST http://localhost:3000/query \ -H "Content-Type: application/json" \ -d '{"prompt": "Hello"}' ``` Response: ```json theme={null} { "error": "Payment Required", "message": "Purchase a plan to access this API", "plans": [{"planId": "did:nv:...", "agentId": "did:nv:..."}] } ``` **2) Purchase a plan and get an access token** ```typescript theme={null} // As a subscriber const payments = Payments.getInstance({ nvmApiKey: subscriberKey, environment: 'sandbox' }) // Order the plan await payments.plans.orderPlan(PLAN_ID) // Get your balance about the plan you just ordered const balance = await payments.plans.getPlanBalance(PLAN_ID) // Generate the x402 access token const { accessToken } = await payments.x402.getX402AccessToken(PLAN_ID, AGENT_ID) // Use this token in the next request // HTTP header: 'PAYMENT-SIGNATURE' // value: `${accessToken}` ``` ```python theme={null} import os from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) # Order the plan order_result = payments.plans.order_plan(plan_id) # Get your balance about the plan you just ordered balance = payments.plans.get_plan_balance(plan_id) # Generate the x402 access token token_res = payments.x402.get_x402_access_token(plan_id, agent_id) access_token = token_res['accessToken'] ``` **3) Query with payment** ```bash theme={null} curl -X POST http://localhost:3000/query \ -H "Content-Type: application/json" \ -H "PAYMENT-SIGNATURE: ${agentAccessParams.accessToken}" \ -d '{"prompt": "Hello"}' ``` Response: ```json theme={null} { "result": "Hello! How can I help you today?", "creditsRemaining": 99 } ``` ## What's Next? Complete Express.js integration guide Complete FastAPI integration guide Advanced validation and charging patterns Implement HTTP 402 payment flows # Python Quick Start Source: https://docs.nevermined.app/docs/integrate/quickstart/python Get started with Nevermined payments in Python **Start here:** need to register a service and create a plan first or get your Nevermined API Key? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Complete Python quick start for adding payments to your AI agent or service. ## Installation ```bash theme={null} pip install payments-py fastapi uvicorn ``` ## Environment Setup ```bash theme={null} # Your Nevermined API key from nevermined.app export NVM_API_KEY="nvm:your-api-key" # Your wallet address to receive payments export BUILDER_ADDRESS="0xYourWalletAddress" # After registration, set these export AGENT_ID="did:nv:your-agent-id" export PLAN_ID="did:nv:your-plan-id" ``` ## Complete Example ```python filename="main.py" theme={null} import os from fastapi import FastAPI, Request, HTTPException, Depends from pydantic import BaseModel from payments_py import Payments, PaymentOptions from payments_py.plans import get_erc20_price_config, get_fixed_credits_config app = FastAPI(title="My AI Agent") # In this example we require a payment a payment of 10 USDC for 100 requests # For that USDC payment we use USDC on Base Sepolia, so we need its contract address: USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' # Initialize payments payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox' ) ) # Store IDs after registration agent_id = os.environ.get('AGENT_ID', '') plan_id = os.environ.get('PLAN_ID', '') # ============================================ # MODELS # ============================================ class QueryRequest(BaseModel): prompt: str class QueryResponse(BaseModel): result: str credits_remaining: int # ============================================ # SETUP: Run once to register your agent # ============================================ @app.post("/setup") async def register_agent(): global agent_id, plan_id result = payments.agents.register_agent_and_plan( agent_metadata={ 'name': 'My Python AI Agent', 'description': 'AI assistant built with Python', 'tags': ['ai', 'python'] }, agent_api={ 'endpoints': [{'POST': 'http://localhost:8000/query'}] }, plan_metadata={ 'name': 'Starter Plan', 'description': '100 queries' }, price_config=get_erc20_price_config( 10_000_000, # 10 USDC USDC_ADDRESS, os.environ['BUILDER_ADDRESS'] ), credits_config=get_fixed_credits_config(100, 1), access_limit='credits' ) agent_id = result['agentId'] plan_id = result['planId'] return { 'agent_id': agent_id, 'plan_id': plan_id, 'message': 'Add these to your .env file!' } # ============================================ # MIDDLEWARE: Validate payments # ============================================ async def validate_payment(request: Request) -> int: """Validate payment and return remaining credits.""" from payments_py.x402.helpers import build_payment_required x402_token = request.headers.get("payment-signature") if not x402_token: raise HTTPException(status_code=402, detail="Payment required") # Build payment requirements payment_required = build_payment_required( plan_id=plan_id, endpoint=str(request.url), agent_id=agent_id, http_verb=request.method ) # Verify permissions verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1" ) if not verification.get("is_valid"): raise HTTPException(status_code=402, detail="Invalid payment") return verification.get("remaining_credits", 0) # ============================================ # ROUTES # ============================================ @app.get("/health") async def health(): """Health check (public).""" return { 'status': 'ok', 'agent_id': agent_id, 'plan_id': plan_id } @app.post("/query", response_model=QueryResponse) async def query( request: QueryRequest, credits: int = Depends(validate_payment) ): """Protected AI endpoint.""" # Your AI logic here result = f'You asked: "{request.prompt}". Here\'s my response...' return QueryResponse( result=result, credits_remaining=credits ) # ============================================ # MAIN # ============================================ if __name__ == '__main__': import uvicorn print(f"Agent ID: {agent_id or 'Not registered'}") print(f"Plan ID: {plan_id or 'Not registered'}") if not agent_id or not plan_id: print("No agent registered yet. POST to /setup to register.") uvicorn.run(app, host='0.0.0.0', port=8000) ``` ## Run the Example ```bash theme={null} # Run the server python main.py # Or with uvicorn directly uvicorn main:app --reload --port 8000 ``` ## Test the Flow **1) Register your agent (first time only)** ```bash theme={null} curl -X POST http://localhost:8000/setup ``` Save the returned `agent_id` and `plan_id` to your environment. **2) Try without payment** ```bash theme={null} curl -X POST http://localhost:8000/query \ -H "Content-Type: application/json" \ -d '{"prompt": "Hello"}' ``` **3) As a subscriber, purchase and query** ```python filename="subscriber.py" theme={null} import os import requests from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ['SUBSCRIBER_API_KEY'], environment='sandbox' ) ) def purchase_and_query(): plan_id = os.environ['PLAN_ID'] agent_id = os.environ['AGENT_ID'] # 1. Order the plan print('Purchasing plan...') order_result = payments.plans.order_plan(plan_id) # 2. Get access token print('Getting access token...') token_res = payments.x402.get_x402_access_token(plan_id, agent_id) access_token = token_res['accessToken'] # 3. Query the agent print('Querying agent...') response = requests.post( 'http://localhost:8000/query', headers={ 'Content-Type': 'application/json', 'payment-signature': f'{access_token}' }, json={'prompt': 'What is 2+2?'} ) result = response.json() print(f'Result: {result}') if __name__ == '__main__': purchase_and_query() ``` ## Flask Alternative ```python filename="flask_app.py" theme={null} import os from flask import Flask, request, jsonify from functools import wraps from payments_py import Payments, PaymentOptions from payments_py.plans import get_erc20_price_config, get_fixed_credits_config app = Flask(__name__) USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox' ) ) agent_id = os.environ.get('AGENT_ID', '') plan_id = os.environ.get('PLAN_ID', '') def require_payment(f): """Decorator to validate payment.""" from payments_py.x402.helpers import build_payment_required @wraps(f) def decorated(*args, **kwargs): x402_token = request.headers.get("payment-signature") if not x402_token: return {"error": "Payment Required"}, 402 # Get request body for validation (Flask synchronous method) body = request.get_json() # Build payment requirements payment_required = build_payment_required( plan_id=plan_id, endpoint=request.url, agent_id=agent_id, http_verb=request.method ) # Verify permissions verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1" ) if not verification.get("is_valid"): return {"error": "Invalid payment"}, 402 # Get balance balance = payments.plans.get_plan_balance(plan_id) # Pass credits to the handler kwargs['credits'] = balance.get('balance', 0) return f(*args, **kwargs) return decorated @app.route('/health') def health(): return jsonify({'status': 'ok', 'agent_id': agent_id, 'plan_id': plan_id}) @app.route('/query', methods=['POST']) @require_payment def query(credits=0): prompt = request.json.get('prompt', '') # Your AI logic here result = f'You asked: "{prompt}". Here\'s my response...' return jsonify({ 'result': result, 'credits_remaining': credits }) if __name__ == '__main__': app.run(port=8000, debug=True) ``` ## Project Structure ``` my-ai-agent/ ├── main.py # FastAPI server ├── requirements.txt ├── .env └── src/ ├── middleware/ │ └── payments.py # Payment validation ├── routes/ │ └── query.py # AI endpoints └── services/ └── ai.py # Your AI logic ``` ## Requirements File ```text filename="requirements.txt" theme={null} payments-py>=1.0.0 fastapi>=0.100.0 uvicorn>=0.23.0 python-dotenv>=1.0.0 ``` ## Next Steps Complete FastAPI patterns and best practices Full API documentation Advanced patterns for production Implement HTTP 402 payment flows # TypeScript Quick Start Source: https://docs.nevermined.app/docs/integrate/quickstart/typescript Get started with Nevermined payments in TypeScript/Node.js **Start here:** need to register a service and create a plan first or get your Nevermined API Key? Follow the [5-minute setup](/docs/integrate/quickstart/5-minute-setup). Complete TypeScript quick start for adding payments to your AI agent or service. ## Installation ```bash theme={null} npm install @nevermined-io/payments ``` ## Environment Setup ```bash theme={null} # Your Nevermined API key from nevermined.app export NVM_API_KEY="nvm:your-api-key" # Your wallet address to receive payments export BUILDER_ADDRESS="0xYourWalletAddress" # After registration, set these export AGENT_ID="did:nv:your-agent-id" export PLAN_ID="did:nv:your-plan-id" ``` ## Complete Example ```typescript filename="index.ts" theme={null} import express from 'express' import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const app = express() app.use(express.json()) // In this example we require a payment a payment of 10 USDC for 100 requests // For that USDC payment we use USDC on Base Sepolia, so we need its contract address: const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // Initialize payments const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' // or 'live' }) // Store IDs after registration let agentId: string let planId: string // ============================================ // SETUP: Run once to register your agent // ============================================ async function registerAgent() { const result = await payments.agents.registerAgentAndPlan( { name: 'My TypeScript AI Agent', description: 'AI assistant built with TypeScript', tags: ['ai', 'typescript'], dateCreated: new Date() }, { endpoints: [{ POST: `http://localhost:3000/query` }] }, { name: 'Starter Plan', description: '100 queries', dateCreated: new Date() }, payments.plans.getERC20PriceConfig(10_000_000n, USDC_ADDRESS, process.env.BUILDER_ADDRESS!), payments.plans.getFixedCreditsConfig(100n, 1n) ) agentId = result.agentId planId = result.planId console.log(`Agent ID: ${agentId}`) console.log(`Plan ID: ${planId}`) console.log('Add these to your .env file!') return result } // ============================================ // MIDDLEWARE: Validate payments // ============================================ async function validatePayment( req: express.Request, res: express.Response, next: express.NextFunction ) { // Get token from payment-signature header const x402Token = req.headers['payment-signature'] if (!x402Token) { // Return 402 with payment requirements return res.status(402).json({ error: 'Payment Required' }) } // Build payment required specification const paymentRequired = buildPaymentRequired(planId, { endpoint: req.url, agentId: agentId, httpVerb: req.method, }) // Verify permissions - facilitator extracts planId and subscriberAddress from token const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: 1n, }) if (!verification.isValid) { return res.status(402).json({ error: 'Payment verification failed' }) } const balance = await payments.plans.getPlanBalance(planId) // Attach balance to request ;(req as any).credits = balance next() } // ============================================ // ROUTES // ============================================ // Health check (public) app.get('/health', (req, res) => { res.json({ status: 'ok', agentId, planId }) }) // Protected AI endpoint app.post('/query', validatePayment, async (req, res) => { const { prompt } = req.body // Your AI logic here const result = `You asked: "${prompt}". Here's my response...` res.json({ result, creditsRemaining: (req as any).credits }) }) // Setup endpoint (call once) app.post('/setup', async (req, res) => { try { const result = await registerAgent() res.json(result) } catch (error: any) { res.status(500).json({ error: error.message }) } }) // ============================================ // START SERVER // ============================================ const PORT = process.env.PORT || 3000 app.listen(PORT, () => { // Load IDs from environment if available agentId = process.env.AGENT_ID || '' planId = process.env.PLAN_ID || '' console.log(`Server running on http://localhost:${PORT}`) if (!agentId || !planId) { console.log('No agent registered yet. POST to /setup to register.') } else { console.log(`Agent: ${agentId}`) console.log(`Plan: ${planId}`) } }) ``` ## Run the Example ```bash theme={null} # Install dependencies npm install express @nevermined-io/payments # Run in development npx ts-node index.ts ``` ## Test the Flow **1) Register your agent (first time only)** ```bash theme={null} curl -X POST http://localhost:3000/setup ``` Save the returned `agentId` and `planId` to your environment. **2) Try without payment** ```bash theme={null} curl -X POST http://localhost:3000/query \ -H "Content-Type: application/json" \ -d '{"prompt": "Hello"}' ``` **3) As a subscriber, purchase and query** ```typescript theme={null} // subscriber.ts import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' }) async function purchaseAndQuery() { // 1. Order the plan console.log('Purchasing plan...') await payments.plans.orderPlan(process.env.PLAN_ID!) // 2. Get access token console.log('Getting access token...') const { accessToken } = await payments.x402.getX402AccessToken( process.env.PLAN_ID!, process.env.AGENT_ID! ) // 3. Query the agent console.log('Querying agent...') const response = await fetch('http://localhost:3000/query', { method: 'POST', headers: { 'Content-Type': 'application/json', 'PAYMENT-SIGNATURE': `${accessToken}` }, body: JSON.stringify({ prompt: 'What is 2+2?' }) }) const result = await response.json() console.log('Result:', result) } purchaseAndQuery() ``` ## Project Structure ``` my-ai-agent/ ├── src/ │ ├── index.ts # Main server │ ├── middleware/ │ │ └── payments.ts # Payment validation │ ├── routes/ │ │ └── query.ts # AI endpoints │ └── services/ │ └── ai.ts # Your AI logic ├── package.json ├── tsconfig.json └── .env ``` ## TypeScript Configuration ```json filename="tsconfig.json" theme={null} { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"] } ``` ## Next Steps Complete Express.js patterns and best practices Full API documentation Advanced patterns for production Deep dive into x402 payment flows # BuildShip Integration Source: https://docs.nevermined.app/docs/integrations/buildship-integration Protect your BuildShip workflows and monetize access with Nevermined Payments Integrating Nevermined's access control and payment system with your existing BuildShip workflows is the quickest way to turn your low-code backend logic into a fully monetized API. This process requires setting up an **AI Agent** in Nevermined and configuring the **Nevermined Trigger** in BuildShip. This guide assumes you have an active BuildShip account and have signed up for a Nevermined account. ## The Nevermined BuildShip Trigger The Nevermined Trigger is a specialized node in BuildShip that is placed at the beginning of your workflow. Its function is to act as a security layer and payment middleware, validating subscriber access before your workflow logic executes. To secure HTTP access to a BuildShip workflow endpoint for monetization. It validates the user's **Access Token** (Bearer Token) and verifies their subscription status against a Nevermined plan. ## Step-by-step Tutorial: Monetizing a BuildShip Workflow The goal is to secure a BuildShip endpoint so it is only accessible by users who have purchased a **Payment Plan** and provide a valid **Access Token**. To interact with the Nevermined API, you need an API key. 1. Go to the [Nevermined App](https://nevermined.app/). Nevermined Login Page 2. Log in with your preferred method. 3. In the dashboard, click on the **API Keys** menu item. Nevermined API Key Generation (1) 4. Click on **New API Key** button, generate a new key, give it a descriptive name, and copy it. Nevermined API Key Generation (2) 5. **Copy the generated key** and store it securely. Nevermined API Key Generation (3) You must configure the API key as a secure integration in your BuildShip workspace. 1. In your BuildShip Workspace, go to **Settings** > **Integrations**. 2. Search for **Nevermined** and click to add a new integration. Buildship Settings > Integrations 3. Provide a **Key Name** (e.g., `NVM_API_KEY`). This name will be selectable in the Trigger later. 4. Paste your Nevermined API Key (from Step 1) into the **Value** field. Buildship New Integration 5. Save the integration to activate it in your workspace. Now, set up the entry point of your workflow using the configured integration. * Add the **Nevermined Trigger** node as the first node in your workflow. * **Authentication Key**: In the trigger's configuration, select the **Key Name** you defined in Step 2 (e.g., `NVM_API_KEY`) from the dropdown menu. * Define your desired endpoint: set the **`Path`** (e.g., `/my-backend-logic`) and the **`Method`** (e.g., `POST`). * Connect the **`prompt`** output of the trigger to the input of the next node (your core logic). The user's JSON request body, including the prompt, will be passed through the **`prompt`** output. Leave the **`Agent ID`** parameter empty for now. Define the monetization rules and register your service as an AI Agent in Nevermined. 1. **Create Plan**: Create a **Payment Plan** that defines the pricing and consumption model for your service. Nevermined Plan Creation (1) Nevermined Plan Creation (2) Nevermined Plan Creation (3) 2. **Register Agent**: Create a new **AI Agent**. Nevermined Agent Registration 3. **Register Endpoint**: In the Agent configuration, register the BuildShip endpoint. Ensure the **Path** and **Method** match exactly what you configured in BuildShip (Step 3). Nevermined Agent Endpoint Registration 4. **Associate Plan**: Attach the new Payment Plan to this Agent. Nevermined Agent and Plan Link the configured Agent back to your BuildShip workflow. * Return to your BuildShip workflow and open the **Nevermined Payment Trigger** configuration. * In the **`Agent Name`** field, select the **Agent Name** you just registered in Nevermined from the dropdown list. Buildship Trigger configuration *If the ID does not appear*, close the configuration panel and reopen it to refresh the list of available Agents. * Click **Ship 🚀** to deploy the workflow. ## User Access Flow Once deployed, this is the complete process a subscriber must follow to successfully access your secured workflow. The user must purchase the Payment Plan associated with your AI Agent. Upon purchase, they **generate an Access Token** (Bearer Token) from their Nevermined profile. The user sends an HTTP request to the deployed BuildShip endpoint with two key components: * **Header**: The **Access Token** must be included in the `Authorization` header: ` Authorization: Bearer [User's Access Token]  ` * **Body**: The input data (*prompt*) for your workflow must be sent in the request body as a **JSON object** using the key **`prompt`**: `json { "prompt": "PROMPT_TO_THE_BUILDSHIP_AGENT" } ` The **Nevermined Trigger** intercepts the request, validates the token, checks the subscription status, deducts the necessary credits, and allows the JSON `prompt` data to flow into your BuildShip logic for processing. # Google A2A Source: https://docs.nevermined.app/docs/integrations/google-a2a Understand how to integrate Nevermined with Google A2A. Nevermined Payments integrates with [Google A2A](https://a2a-protocol.org/) to enable heterogenous multi-agent sytems to authorize and charge per request between agents: * Discovery: publish your AI Agent Card at `/.well-known/agent.json`. * Streaming and re-subscribe: set `capabilities.streaming: true` for `message/stream` and `tasks/resubscribe`. * Authentication: credentials are sent in HTTP headers (e.g., `payment-signature: `), not in the JSON‑RPC payload. * Authorization/charging: the agent emits a final event with `metadata.creditsUsed`; Nevermined validates and burns credits accordingly. ## Architecture ```mermaid theme={null} sequenceDiagram participant Consumer participant Provider participant Nevermined Consumer->>Provider: Discover agent card Consumer->>Nevermined: Order plan and get token Consumer->>Provider: Send message with Bearer token Provider->>Nevermined: Verify permissions Provider->>Provider: Execute agent logic Provider->>Nevermined: Settle credits Provider-->>Consumer: Return response ``` ## Features The libraries provide an `a2a` module that enables seamless integration into new or existing A2A agents, including bearer token authentication, asynchronous task management, and push notification support. Main features: * **Payment Signature Authentication**: The server extracts access tokens from the `payment-signature` header and injects them into the task context. * **Credits Validation**: Validates that the user has sufficient credits before executing a task. * **Credits Burning/Redemption**: Redeem the credits specified in the result after successful execution. * **Multi-Plan Support**: Agent cards can advertise multiple plans via `planIds`, allowing consumers to choose which plan to subscribe to. * **Push Notifications**: Supports the A2A standard flow for push notification configuration and delivery. * **Asynchronous Task Handling**: Supports intermediate and final state events, compatible with polling and streaming. * **Unified SDK**: Provides both agent and client integration. ## Quickstart If you already have a Google A2A agent, or you are building a new one, add the Payments Library to your agent and obtain an API key: To interact with the Nevermined API, you need an API key. 1. Go to the [Nevermined App](https://nevermined.app/). 2. Log in via Web3Auth. 3. Navigate to the **Settings** section in the user menu. 4. Click on the **API Keys** tab. 5. Generate a new key, give it a descriptive name, and copy it. 6. Store this key securely as an environment variable (e.g., `NVM_API_KEY`). Install the Payments Library and initialize the `Payments` client with your API key. ```bash theme={null} npm install @nevermined-io/payments ``` ```bash theme={null} pip install payments-py ``` ## Initialize the Payments Library ```typescript theme={null} import { Payments } from "@nevermined-io/payments" const payments = Payments.getInstance({ nvmApiKey, environment: 'sandbox', }) ``` ```python theme={null} from payments_py import Payments from payments_py.common.types import PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="", environment="sandbox") ) ``` ## A2A Server ### Add the Payment Extension to the Agent Card Because your AI agent charges for requests, add a payment extension to your agent card. Add a payment extension under `capabilities.extensions` carrying Nevermined metadata: #### Single Plan ```json theme={null} { "capabilities": { "streaming": true, "pushNotifications": true, "extensions": [ { "uri": "urn:nevermined:payment", "description": "Dynamic cost per request", "required": false, "params": { "paymentType": "dynamic", "credits": 1, "planId": "", "agentId": "" } } ] }, "url": "https://your-agent.example.com/a2a/" } ``` #### Multiple Plans When your agent supports multiple plans, use `planIds` (array) instead of `planId` (string): ```json theme={null} { "capabilities": { "extensions": [ { "uri": "urn:nevermined:payment", "params": { "paymentType": "dynamic", "credits": 5, "planIds": ["", ""], "agentId": "", "costDescription": "1-5 credits depending on request complexity" } } ] } } ``` Provide either `planId` or `planIds`, not both. When multiple plans are configured, the 402 Payment Required response includes all plans in `accepts[]`, allowing consumers to choose. Important notes: * The `url` must match exactly the URL registered in Nevermined for the agent/plan. * The final streaming event must include `metadata.creditsUsed` with the consumed cost. ### Define the Payment Agent Card in Your A2A Agent ```typescript theme={null} const baseAgentCard = { name: 'My A2A Server', description: 'A2A test server that requires payment', capabilities: { streaming: true, pushNotifications: true, stateTransitionHistory: true, }, defaultInputModes: ['text'], defaultOutputModes: ['text'], skills: [], url: 'http://localhost:3005/a2a/', version: '1.0.0', } const agentCard = payments.a2a.buildPaymentAgentCard(baseAgentCard, { paymentType: "dynamic", credits: 1, planId: process.env.PLAN_ID, agentId: process.env.AGENT_ID, }) ``` ```python theme={null} from payments_py.a2a import build_payment_agent_card base_agent_card = { "name": "Py A2A Agent", "description": "A2A test agent", "capabilities": { "streaming": True, "pushNotifications": True, "stateTransitionHistory": True, }, "defaultInputModes": ["text"], "defaultOutputModes": ["text"], "skills": [], "url": "https://your-agent.example.com/a2a/", "version": "1.0.0", } # Single plan agent_card = build_payment_agent_card( base_card=base_agent_card, payment_metadata={ "paymentType": "dynamic", "credits": 1, "costDescription": "Dynamic cost per request", "planId": "", "agentId": "", }, ) # Or with multiple plans agent_card = build_payment_agent_card( base_card=base_agent_card, payment_metadata={ "paymentType": "dynamic", "credits": 5, "costDescription": "1-5 credits per request", "planIds": ["", ""], "agentId": "", }, ) ``` ### Start the A2A Server The agent is initialized using the Nevermined Payments Library and the A2A protocol. #### Using the Decorator (Simple) The `@a2a_requires_payment` decorator is the quickest way to create a payment-protected A2A agent in Python: ```typescript theme={null} // Start server on port 3005 for A2A class Executor implements AgentExecutor { async handleTask(context, eventBus) { // Returns { result: TaskHandlerResult, expectsMoreUpdates: boolean } } async cancelTask(taskId) { /* ... */ } // Publishes the final status-update event when no more updates are expected async execute(requestContext, eventBus) { const { result, expectsMoreUpdates } = await this.handleTask(requestContext, eventBus) if (expectsMoreUpdates) return // Publish final status-update event... } } serverResult = await paymentsBuilder.a2a.start({ port: 3005, basePath: '/a2a/', agentCard: agentCard, executor: A2AE2EFactory.createResubscribeStreamingExecutor(), }) serverManager.addServer(serverResult) ``` ```python theme={null} from payments_py.a2a import AgentResponse, a2a_requires_payment @a2a_requires_payment( payments=payments, agent_card=agent_card, default_credits=1, ) async def my_agent(context) -> AgentResponse: text = context.get_user_input() return AgentResponse(text=f"Echo: {text}", credits_used=1) # Start serving (blocking) my_agent.serve(port=8080) ``` #### Using `PaymentsA2AServer` (Advanced) For more control over the executor and event lifecycle: ```python theme={null} from payments_py.a2a.server import PaymentsA2AServer from a2a.server.agent_execution import AgentExecutor from a2a.server.events.event_queue import EventQueue class MyExecutor(AgentExecutor): async def execute(self, ctx, event_queue: EventQueue): # Your agent logic — publish events to event_queue ... async def cancel(self, ctx, event_queue: EventQueue): ... result = PaymentsA2AServer.start( agent_card=agent_card, executor=MyExecutor(), payments_service=payments, port=8080, base_path="/", ) import asyncio asyncio.run(result.server.serve()) ``` ## A2A Client The client interacts with the agent using JSON-RPC requests. It discovers available plans from the agent card, purchases a plan, obtains an access token, and sends messages. ### Discovering Plans from the Agent Card ```mermaid theme={null} sequenceDiagram participant Consumer participant Provider participant Nevermined Consumer->>Provider: GET /.well-known/agent.json Provider-->>Consumer: Agent Card (planIds, agentId) Consumer->>Nevermined: order_plan(selected_plan_id) Nevermined-->>Consumer: Order confirmed Consumer->>Nevermined: get_x402_access_token(plan_id, agent_id) Nevermined-->>Consumer: accessToken Consumer->>Provider: POST / message/send (Bearer token) Provider-->>Consumer: JSON-RPC response ``` ```typescript theme={null} const paymentsSubscriber = Payments.getInstance({ nvmApiKey, environment: 'sandbox', }) const client = paymentsSubscriber.a2a.getClient({ agentBaseUrl: 'http://localhost:3005/a2a/', agentId: process.env.AGENT_ID, planId: process.env.PLAN_ID }) ``` ```python theme={null} import httpx from payments_py import Payments from payments_py.common.types import PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="", environment="sandbox") ) # 1. Discover plans from the agent card async with httpx.AsyncClient() as client: resp = await client.get("http://agent-url/.well-known/agent.json") card = resp.json() extensions = card["capabilities"]["extensions"] payment_ext = next(e for e in extensions if e["uri"] == "urn:nevermined:payment") plan_ids = payment_ext["params"].get("planIds") or [payment_ext["params"]["planId"]] agent_id = payment_ext["params"]["agentId"] ``` ### Sending a Task After [purchasing access to the payment plan](/docs/development-guide/order-plans) associated with the AI agent, a client [can generate an access token](/docs/development-guide/query-agents) and start sending tasks: ```typescript theme={null} // Purchase the Plan const orderResult = await paymentsSubscriber.plans.orderPlan(planId) // Get the X402 access token associated to the agent and plan const { accessToken } = await paymentsSubscriber.x402.getX402AccessToken(planId, agentId) // Test sending an A2A message with correct format const response = await client.sendMessage( "Testing push notification!", accessToken ); const taskId = response?.result?.id ``` ```python theme={null} from payments_py.a2a import PaymentsClient # 2. Order (purchase) the plan payments.plans.order_plan(plan_ids[0]) # 3. Create the A2A client (handles token acquisition automatically) client = PaymentsClient( agent_base_url="http://agent-url/", payments=payments, agent_id=agent_id, plan_id=plan_ids[0], ) # 4. Send a simple request result = await client.send_message({ "message": { "messageId": "msg-1", "role": "user", "parts": [{"kind": "text", "text": "Hello"}], } }) # 5. Or stream events async for event in client.send_message_stream({ "message": { "messageId": "msg-2", "role": "user", "parts": [{"kind": "text", "text": "Stream this"}], } }): print(event) if getattr(event, "final", False): break ``` ## Full example code Find a complete working example in the repository: [nevermined-io/a2a-agent-client-sample](https://github.com/nevermined-io/a2a-agent-client-sample). # Model Context Protocol (MCP) Source: https://docs.nevermined.app/docs/integrations/mcp Learn how to protect your MCP servers with Nevermined Payments Library. **Are you building with an LLM?** If you are using an AI assistant for development, this page provides comprehensive context to streamline the integration process. Simply share this documentation URL with your LLM or copy the page content as a knowledge base. Nevermined Payments Library provides robust tools to add a paywall to your [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/) server. This allows you to monetize your AI tools, resources, and prompts by restricting access to subscribers with valid payment plans. The integration is designed to be seamless, whether you are using a high-level framework like the official MCP SDKs or a custom low-level JSON-RPC router. * **Paywall Protection**: Wrap your handlers with `withPaywall` to automatically verify `Authorization` tokens and check for valid subscriptions. * **Credit Burning**: Automatically burn credits after a successful API call, with support for both fixed and dynamic costs. * **Declarative Registration**: Use the `attach` method to register and protect your tools in a single, clean step. * **Framework Agnostic**: Works with both high-level servers (like the official TypeScript SDK's `McpServer` or Python's `FastMCP`) and custom low-level ASGI/Express routers. ## What is MCP? As Large Language Models (LLMs) and AI agents become more sophisticated, their greatest limitation is their isolation. By default, they lack access to real-time information, private data sources, or the ability to perform actions in the outside world. The Model Context Protocol (MCP) was designed to solve this problem by creating a standardized communication layer for AI. Think of MCP as a universal language that allows any AI agent to ask a server, "What can you do?" and "How can I use your capabilities?". It turns a closed-off model into an agent that can interact with the world through a secure and discoverable interface. An MCP server essentially publishes a "menu" of its services, which can include: * **Tools**: These are concrete actions the agent can request, like sending an email, querying a database, or fetching a weather forecast. The agent provides specific arguments (e.g., `city="Paris"`) and the server executes the action. * **Resources**: These are stable pointers to data, identified by a URI. While a tool call might give a human-readable summary, a resource link (`weather://today/Paris`) provides the raw, structured data (like a JSON object) that an agent can parse and use for further tasks. * **Prompts**: These are pre-defined templates that help guide an agent's behavior, ensuring it requests information in the correct format or follows a specific interaction pattern. ## Why integrate MCP with Nevermined Payments Library? While MCP provides a powerful standard for *what* an AI agent can do, it doesn't specify *who* is allowed to do it or *how* those services are paid for. This is where Nevermined Payments Library comes in. By integrating Nevermined, you can transform your open MCP server into a secure, monetizable platform. The core idea is to place a "paywall" in front of your MCP handlers. This paywall acts as a gatekeeper, intercepting every incoming request to a tool, resource, or prompt. Before executing your logic, it checks the user's `Authorization` header to verify they have a valid subscription and sufficient credits through the Nevermined protocol. If they don't, the request is blocked. If they do, the request proceeds, and after your handler successfully completes, the paywall automatically deducts the configured number of credits. This integration allows you to build a sustainable business model around your AI services. You can offer different subscription tiers (plans), charge dynamically based on usage, and maintain a complete audit trail of every transaction, all without cluttering your core application logic with complex payment code. ## OAuth 2.1 Support for Remote MCP Servers Nevermined Payments Library implements **complete OAuth 2.1 authentication** for MCP servers, acting as both: * **OAuth Authorization Server** (RFC 8414) * **OAuth Protected Resource Server** (RFC 8414) * **OpenID Connect Provider** (OIDC Discovery 1.0) All OAuth discovery endpoints (`/.well-known/oauth-authorization-server`, `/.well-known/oauth-protected-resource`, `/.well-known/openid-configuration`) are **automatically generated and configured**. Your MCP server becomes instantly compatible with any OAuth-enabled MCP client (Claude Desktop, custom agents, etc.) without any manual OAuth setup. ## Simplified API: Complete MCP Server in Minutes The Nevermined Payments Library provides a high-level API that handles everything for you: * ✅ MCP Server creation * ✅ Express.js setup with CORS * ✅ OAuth 2.1 endpoints (auto-generated) * ✅ HTTP transport (POST/GET/DELETE `/mcp`) * ✅ Session management for streaming * ✅ Paywall protection for all tools/resources/prompts ### Quick Example Here's how to create a complete, production-ready MCP server with OAuth authentication: ```typescript theme={null} import { Payments } from "@nevermined-io/payments" import { z } from "zod" // 1. Initialize Payments const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: "sandbox" }) // 2. Register tools with built-in paywall payments.mcp.registerTool( "weather.today", { title: "Today's Weather", description: "Get weather for a city", inputSchema: z.object({ city: z.string().min(2).max(80).describe("City name") }) }, async (args, extra, context) => { // Access authentication context for observability console.log(`Request ID: ${context?.authResult.requestId}`) console.log(`Credits charged: ${context?.credits}`) // Your business logic here const weather = await fetchWeather(args.city) return { content: [{ type: "text", text: `Weather in ${args.city}: ${weather.description}, ${weather.temp}°C` }] } }, { credits: 5n } // Fixed credits (5 per call) ) // 3. Start everything (MCP Server + Express + OAuth) const { info, stop } = await payments.mcp.start({ port: 3000, agentId: process.env.NVM_AGENT_ID!, serverName: "my-weather-server", version: "1.0.0", description: "Weather MCP server with OAuth authentication" }) console.log(`🚀 Server running at ${info.baseUrl}/mcp`) console.log(`🔐 OAuth endpoints: ${info.baseUrl}/.well-known/`) console.log(`🛠️ Tools: ${info.tools.join(", ")}`) // Graceful shutdown process.on("SIGINT", async () => { await stop() process.exit(0) }) ``` ```python theme={null} # Python SDK support coming soon # For now, use the TypeScript API shown in the advanced sections ``` **That's it!** Your MCP server is now: * ✅ Running with OAuth 2.1 authentication * ✅ Protected by Nevermined paywall * ✅ Compatible with Claude Desktop and any MCP client * ✅ Monetizable with automatic credit deduction ### What `payments.mcp.start()` Does for You This single function call handles: 1. **Express Server Setup**: Creates and configures an Express.js application 2. **OAuth Endpoints**: Auto-generates RFC-compliant discovery endpoints: * `/.well-known/oauth-authorization-server` * `/.well-known/oauth-protected-resource` * `/.well-known/openid-configuration` * `/register` (Dynamic Client Registration - RFC 7591) 3. **MCP Transport**: Sets up HTTP transport endpoints (POST/GET/DELETE `/mcp`) 4. **Session Management**: Handles SSE streaming and session lifecycle 5. **CORS & Middleware**: Configures CORS, JSON parsing, and HTTP logging 6. **Graceful Shutdown**: Returns a `stop()` function for clean server termination ### Complete Working Example See the full production-ready example with advanced features: * **GitHub**: [weather-mcp/src/server/main.ts](https://github.com/nevermined-io/tutorials/tree/main/mcp-examples/weather-mcp) This example includes: * Dynamic credit calculation based on response size * Resources with URI templates (`weather://today/{city}`) * Prompts for LLM guidance * OpenAI observability integration ### Dynamic Credits Instead of fixed credits, you can calculate them dynamically based on the handler's result: ```typescript theme={null} // Dynamic credits based on response complexity const weatherCreditsCalculator = (ctx: CreditsContext): bigint => { const result = ctx.result as { structuredContent?: { forecast?: string } } const forecast = result?.structuredContent?.forecast || "" // Charge 1 credit for short forecasts, 2-19 for longer ones return forecast.length <= 100 ? 1n : BigInt(Math.floor(Math.random() * 18) + 2) } payments.mcp.registerTool( "weather.today", config, handler, { credits: weatherCreditsCalculator } // Function instead of fixed value ) ``` **Important:** * **Fixed credits**: Calculated BEFORE handler execution (available in `context.credits`) * **Dynamic credits**: Calculated AFTER handler execution (based on `ctx.result`) ### Client Usage **Step 1: Get Access Token** Users need to subscribe to your agent's plan and obtain an access token: ```typescript theme={null} // As a subscriber const { accessToken } = await paymentsClient.x402.getX402AccessToken( planId, // The plan they subscribed to agentId // Your MCP server's agent ID ) ``` **Step 2: Connect with MCP Client** ```typescript theme={null} import { Client } from "@modelcontextprotocol/sdk/client" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp" const transport = new StreamableHTTPClientTransport( new URL("http://localhost:3000/mcp"), { requestInit: { headers: { 'payment-signature': accessToken } } } ) const client = new Client({ name: "my-client" }) await client.connect(transport) // Call tool const result = await client.callTool({ name: "weather.today", arguments: { city: "Madrid" } }) ``` **Step 3: Use in Claude Desktop** Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json theme={null} { "mcpServers": { "weather": { "url": "http://localhost:3000/mcp", "auth": { "type": "bearer", "token": "YOUR_ACCESS_TOKEN_HERE" } } } } ``` *** ## Advanced Usage: Low-Level APIs The sections below show **advanced, low-level APIs** for users who need custom server implementations or fine-grained control. **For most use cases, we recommend using the simplified API above** with `payments.mcp.start()`. This advanced tutorial covers low-level APIs for building custom MCP servers. These approaches give you full control over the server implementation, but require more manual setup compared to the simplified API. ### 0) Requirements & Installation * Node.js >= 18 * `@nevermined-io/payments` (includes everything needed) * `zod` for schema validation ```bash theme={null} npm install @nevermined-io/payments zod dotenv npm install -D typescript @types/node ts-node ``` * Python >= 3.10 * FastMCP (`mcp` package) + Uvicorn * `payments-py` (SDK de Nevermined) ```bash theme={null} pip install mcp uvicorn python-dotenv pip install -e payments-py ``` ### 1) Create a Minimal MCP Server First, let's create a basic MCP server without any paywall to ensure the core setup is working. This server uses the official MCP SDK and exposes a single tool, `weather.today`. ```typescript theme={null} // server.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; export function createMcpServer() { const server = new McpServer({ name: "weather-mcp", version: "1.0.0", }); server.tool( "weather.today", { title: "Today's Weather", inputSchema: z.object({ city: z.string() }) }, async (args) => ({ content: [{ type: "text", text: `Weather for ${args.city}: Sunny, 25C.` }], }) ); return server; } ``` This server uses `FastMCP` from the `mcp` Python library to expose the `weather.today` tool. ```python theme={null} # app_fastmcp.py from mcp.server.fastmcp import FastMCP fastmcp = FastMCP(name="weather-mcp", json_response=True) @fastmcp.tool(name="weather.today", title="Today's Weather") async def weather_today(city: str) -> str: return f"Weather for {city}: Sunny, 25C." ``` ### 2) Initialize Nevermined Payments Initialize the Nevermined Payments SDK with your builder/agent owner API key and environment. ```typescript theme={null} // payments-setup.ts import { Payments, EnvironmentName } from "@nevermined-io/payments"; const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: (process.env.NVM_ENVIRONMENT as EnvironmentName) || "sandbox", }); ``` When using low-level APIs, you may need to call `payments.mcp.configure()` to set defaults. With the simplified API (`payments.mcp.start()`), configuration is handled automatically. ```python theme={null} # payments_setup.py from payments_py.payments import Payments payments = Payments({ "nvm_api_key": os.environ["NVM_API_KEY"], "environment": os.environ.get("NVM_ENV", "sandbox"), }) ``` ### 3) Dynamic Credits (Optional) Instead of charging fixed credits, you can calculate them dynamically based on input arguments or response content. #### Understanding CreditsContext When using a function for `credits`, it receives a `CreditsContext` object with: ```typescript theme={null} interface CreditsContext { args: unknown // Input arguments passed to the tool/resource/prompt result: any // The response returned by your handler request: { authHeader: string // Authorization Bearer token logicalUrl: string // Full MCP logical URL (e.g., "mcp://server/tools/weather") toolName: string // Name of the tool/resource/prompt } } ``` **Key Properties:** * **`ctx.args`**: The input arguments the user sent (e.g., `{ city: "London" }`) * **`ctx.result`**: The complete response your handler returned * **`ctx.request.toolName`**: The name of the tool being called * **`ctx.request.logicalUrl`**: The full logical URL for observability * **`ctx.request.authHeader`**: The auth token (for advanced logging) #### Examples Charge credits proportional to the response length: ```typescript theme={null} import type { CreditsContext } from "@nevermined-io/payments" const dynamicCredits = (ctx: CreditsContext): bigint => { // Access the result returned by your handler const result = ctx.result as { content: Array<{ text: string }> } const text = result.content[0]?.text || "" // Charge 1 credit per 100 characters const credits = Math.ceil(text.length / 100) return BigInt(credits) } payments.mcp.registerTool( "weather.today", config, handler, { credits: dynamicCredits } ) ``` Charge different rates based on input complexity: ```typescript theme={null} const dynamicCredits = (ctx: CreditsContext): bigint => { // Access input arguments const args = ctx.args as { city: string; forecast_days?: number } // Base cost: 5 credits let credits = 5n // Add 2 credits per additional forecast day if (args.forecast_days && args.forecast_days > 1) { credits += BigInt((args.forecast_days - 1) * 2) } return credits } payments.mcp.registerTool( "weather.forecast", config, handler, { credits: dynamicCredits } ) ``` Apply different pricing models based on result type: ```typescript theme={null} const dynamicCredits = (ctx: CreditsContext): bigint => { const result = ctx.result as { structuredContent?: any rawData?: any } // Structured responses cost more if (result.structuredContent) { return 10n } // Raw data costs less if (result.rawData) { return 3n } // Default return 5n } ``` ```python theme={null} def dynamic_credits(ctx: Dict[str, Any]) -> int: # Access arguments args = ctx.get("args", {}) city = args.get("city", "") # Access result result = ctx.get("result", {}) content = result.get("content", []) text = content[0].get("text", "") if content else "" # Calculate based on response length return max(1, len(text) // 100) payments.mcp.registerTool( "weather.today", config, handler, {"credits": dynamic_credits} ) ``` **Important:** * ✅ **Fixed credits** (`credits: 5n`): Calculated BEFORE handler execution, available in `context.credits` * ✅ **Dynamic credits** (function): Calculated AFTER handler execution, based on `ctx.args` and `ctx.result` * ✅ **Return type**: Must return `bigint` (TypeScript) or `int` (Python) * ⚠️ **Performance**: Keep calculations lightweight to avoid delays ### 4) Connecting MCP Clients Once your server is running with `payments.mcp.start()`, connecting clients is straightforward. **Nevermined handles OAuth authentication automatically** - clients just need to know the server URL. #### Claude Desktop Add to your Claude Desktop config file: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={null} { "mcpServers": { "weather": { "url": "http://localhost:3000/mcp", "type": "http" } } } ``` #### Cursor IDE Add to your Cursor MCP config file: * **Location**: `~/.cursor/mcp.json` ```json theme={null} { "mcpServers": { "weather": { "url": "http://localhost:3000/mcp", "type": "http" } } } ``` #### Custom MCP Client (TypeScript/JavaScript) ```typescript theme={null} import { Client } from "@modelcontextprotocol/sdk/client" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp" // Simple connection - no auth headers needed const transport = new StreamableHTTPClientTransport( new URL("http://localhost:3000/mcp") ) const client = new Client({ name: "my-client", version: "1.0.0" }) await client.connect(transport) // Use tools const result = await client.callTool({ name: "weather.today", arguments: { city: "Madrid" } }) console.log(result) ``` #### How OAuth Works Behind the Scenes When a client connects to your MCP server: 1. **Discovery**: Client reads `/.well-known/oauth-authorization-server` to find OAuth endpoints 2. **Registration**: Client registers itself via `/register` (dynamic client registration) 3. **Authorization**: Client obtains access token via OAuth 2.1 flow 4. **Authenticated Requests**: All subsequent MCP requests include the token automatically **You don't need to handle any of this manually** - `payments.mcp.start()` configures everything automatically! **For production servers**, replace `http://localhost:3000` with your public domain (e.g., `https://weather-mcp.yourdomain.com`). *** ## Advanced Usage The methods below are for advanced use cases requiring custom server setup. For most applications, use the simplified `payments.mcp.start()` approach shown above. ### Using `withPaywall` for Custom Servers If you're building a custom MCP server and want granular control, use `withPaywall`: ```typescript theme={null} const protectedHandler = payments.mcp.withPaywall( myHandler, { kind: "tool", name: "my.tool", credits: 5n } ) ``` ### Using `attach` for Declarative Registration ```typescript theme={null} const server = new McpServer({ name: "my-server", version: "1.0.0" }) const registrar = payments.mcp.attach(server) registrar.registerTool( "weather.today", config, handler, { credits: 1n } ) ``` ### Custom Low-Level Server **Only for advanced use cases.** Most users should use `payments.mcp.start()`. For custom Express servers, you can use `payments.mcp.createApp()`: ```typescript theme={null} // custom-server.ts import { Payments } from "@nevermined-io/payments" const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: "sandbox" }) payments.mcp.configure({ agentId: process.env.NVM_AGENT_ID!, serverName: "custom-server" }) const app = payments.mcp.createApp({ baseUrl: "http://localhost:3000", serverName: "custom-server", tools: ["weather.today"] }) // Add your custom JSON-RPC routing app.post("/mcp", async (req, res) => { const { method, params } = req.body // Build extra object from request const extra = { requestInfo: { headers: req.headers, method: req.method, url: req.url } } // Route to handler and pass extra if (method === "tools/call") { const handler = getToolHandler(params.name) const result = await handler(params.arguments, extra) res.json({ jsonrpc: "2.0", id: req.body.id, result }) } }) app.listen(3000, () => { console.log("Custom server running on http://localhost:3000") }) ``` For complete custom control with ASGI: ```python theme={null} # lowlevel_app.py from payments_py.mcp import build_extra_from_http_headers async def app(scope, receive, send): # Parse request headers = {k.decode(): v.decode() for k, v in scope.get("headers", [])} extra = build_extra_from_http_headers(headers) # Read body... body = await receive() req = json.loads(body.get("body", b"{}")) # Route and call handler if req.get("method") == "tools/call": handler = get_tool_handler(req["params"]["name"]) result = await handler(req["params"]["arguments"], extra) # Send response... ``` *** ## Error Handling | Error Code | Description | | ---------- | ------------------------------------------------------------------ | | `-32003` | Payment Required - No token / Invalid token / Insufficient credits | | `-32002` | Misconfiguration - Server setup error | | `-32603` | Internal Error - Handler execution failed | *** ## Full Code Examples ### Production-Ready Example The **Weather MCP Server** is a complete, production-ready example that demonstrates all features: * **Repository**: [tutorials/mcp-examples/weather-mcp](https://github.com/nevermined-io/tutorials/tree/main/mcp-examples/weather-mcp) * **Main Server**: [weather-mcp/src/server/main.ts](https://github.com/nevermined-io/tutorials/blob/main/mcp-examples/weather-mcp/src/server/main.ts) **Features demonstrated:** * ✅ Complete OAuth 2.1 authentication with `payments.mcp.start()` * ✅ Tools with dynamic credit calculation based on response size * ✅ Resources with URI templates (`weather://today/{city}`) * ✅ Prompts for LLM guidance * ✅ OpenAI integration with Nevermined observability * ✅ Graceful shutdown handling * ✅ Environment-based configuration ### Additional Examples Advanced and low-level API examples: * **TypeScript (Low-level)**: [nevermined-io/weather-mcp-demo](https://github.com/nevermined-io/weather-mcp-demo) - Examples using `withPaywall` and `attach` for custom servers * **Python**: [nevermined-io/weather-mcp-demo-py](https://github.com/nevermined-io/weather-mcp-demo-py) - Python implementation with FastMCP # Organizations Source: https://docs.nevermined.app/docs/integrations/organizations Transform your account into an organization to share revenue and customize the checkout page. Upgrade your account to an organization to unlock powerful monetization and revenue sharing capabilities. Make it easy for your builders to earn revenue and add access control to their agents. As an organization owner, you gain full control over your community's success and your branding. ## Benefits ### Upgrade to Organization Transform your account into an organization to manage teams, share revenue, and customize the checkout page your clients use to purchase your plans. As an organization owner, you'll have full control over your team's success. ### Revenue Sharing Set your own commission rate. When any builder in your organization sells a plan, you automatically receive your defined cut. ### Custom Checkout Brand the checkout experience for all builders in your organization — add your logo, colors, and messaging to the page where clients buy your plans. ## How to Upgrade The flow to upgrade your account is straightforward: 1. Go to your [Profile Page](https://nevermined.app/profile) and click the **Upgrade now** button. 2. You will be directed to the checkout flow, where you can subscribe to the Organization plan for **\$50/month**. 3. After completing the payment, you will be redirected to the organizations page. 4. In the organizations page, you can: * Enter your Organization Name. * Set up crypto and fiat fees (your cut for every purchase made by your organization's members). * Customize the checkout appearance for your organization's members. ## Flow of Funds When a builder within your organization sells a payment plan, the distribution of funds works as follows (specifically for fiat payments): 1. **Builder sells a payment plan**: A client purchases a plan from a builder in your organization. 2. **Fee Deduction**: The total value of the plan is calculated, and the following fees are deducted: * Nevermined Fees * Stripe Fees * **Organization Fees**: The commission rate set by the organization admin. 3. **Builder Payout**: The builder receives the remaining amount: `Plan Value - (Nevermined Fees + Stripe Fees + Organization Fees)`. 4. **Organization Disbursement**: Nevermined disburses the collected Organization Fees to the organization admin. ## Onboarding Members The process of onboarding members to your organization is currently handled manually. Please contact us at [contact@nevermined.ai](mailto:contact@nevermined.ai) to get your team members added. # Manage Payment Plans Source: https://docs.nevermined.app/docs/products/nevermined-app/manage-plans Create and configure payment plans for your AI agents through the Nevermined App Payment plans define how subscribers pay to access your AI agents. Use the Nevermined App to create, edit, and manage plans visually. ## Creating a Payment Plan From the Agents tab, click on the agent you want to add a plan to, then click **Create Plan**. Select how access will be limited: | Type | Description | Best For | | ----------------- | ------------------------------------------- | ------------------ | | **Credits-Based** | Users purchase credits, consume per request | API calls, queries | | **Time-Based** | Access expires after a duration | Subscriptions | | **Dynamic** | Variable charge per request | Complex workloads | Set the price and currency: **Cryptocurrency**: * Amount (e.g., 10) * Token (USDC, USDT, or custom ERC-20) * Network (Base, Ethereum, etc.) **Fiat**: * Amount (e.g., \$10.00) * Currency (USD, EUR, etc.) * Payment via Stripe checkout Configure what subscribers get: **For Credits-Based**: * Total credits included * Credits consumed per request **For Time-Based**: * Access duration (days, weeks, months) **For Dynamic**: * Minimum credits per request * Maximum credits per request | Field | Description | | --------------- | ----------------------------------------------- | | **Name** | Plan display name (e.g., "Pro Plan") | | **Description** | What's included (e.g., "100 queries per month") | Review your configuration and click **Create Plan**. The plan will be immediately available for purchase. ## Plan Types in Detail ### Credits-Based Plans Charge per usage - subscribers buy credits that are deducted with each request. **Example Configuration**: ``` Name: Pro Plan Price: 10 USDC Credits: 100 Credits per Request: 1 → $0.10 per query ``` **Use When**: * Each request has similar cost * You want pay-as-you-go pricing * Usage varies significantly between users ### Time-Based Plans Subscription model - unlimited access for a fixed duration. **Example Configuration**: ``` Name: Monthly Subscription Price: 50 USDC Duration: 30 days → Unlimited queries for one month ``` **Use When**: * Users need continuous access * Usage is predictable * You want recurring revenue ### Dynamic Plans Variable pricing - charge different amounts based on request complexity. **Example Configuration**: ``` Name: Pay As You Go Price: 20 USDC (for 100 credits) Min Credits per Request: 1 Max Credits per Request: 10 → Simple queries: 1 credit → Complex queries: up to 10 credits ``` **Use When**: * Requests have varying costs (e.g., LLM token count) * You want fair usage-based pricing * Processing time varies significantly ## Multiple Plans Per Agent Create tiers to serve different customer segments: ``` Agent: Legal Document Analyzer Plans: 1. Free Trial (0 USDC, 5 credits) 2. Starter ($5, 25 credits) 3. Pro ($15, 100 credits) 4. Enterprise ($99, 1000 credits) ``` Subscribers can purchase multiple plans or repurchase to add more credits. ## Editing Plans From the Plans tab or agent detail page: 1. Click on the plan you want to edit 2. Modify settings (name, description, pricing) 3. Click **Save Changes** Changing plan pricing affects new purchases only. Existing subscribers keep their original terms. ## Plan Analytics Track each plan's performance: | Metric | Description | | ---------------------- | ----------------------------------- | | **Purchases** | Total times the plan was ordered | | **Revenue** | Total earnings from this plan | | **Active Subscribers** | Users with remaining credits/time | | **Usage Rate** | Average credits used per subscriber | ## Free Trial Plans Attract new users with free trials: 1. Create a new plan with **Price: 0** 2. Set limited credits (e.g., 5 queries) 3. Name it clearly (e.g., "Free Trial - 5 Queries") Users can claim the trial once per account, then must purchase a paid plan. ## Best Practices Create at least 3 tiers (e.g., Starter, Pro, Enterprise) to capture different customer segments and budgets. Let users try before they buy. A small free trial (5-10 credits) can significantly increase conversions. Consider the value your agent provides, not just your costs. Premium AI services can command premium prices. Explain exactly what subscribers get. Include credits, duration, and any limitations. ## Managing Subscribers View who has purchased each plan: 1. Go to the plan detail page 2. Click **Subscribers** tab 3. See purchase date, credits remaining, and usage history You can also: * Export subscriber data * View individual usage patterns * Contact subscribers (if they've opted in) ## Next Steps Implement access control in your agent Manage plans programmatically # Nevermined App Overview Source: https://docs.nevermined.app/docs/products/nevermined-app/overview No-code interface for registering AI agents, managing payment plans, and monetizing your services The [Nevermined App](https://nevermined.app) is a visual interface for managing your AI agents and payment infrastructure. Register agents, create payment plans, and track revenue without writing code. ## What You Can Do Add your AI agents with metadata, endpoints, and access control settings Define pricing with credits, subscriptions, or dynamic models Generate API keys, view subscribers, and control permissions Monitor usage, payments, and analytics in real-time ## Getting Started Go to [nevermined.app](https://nevermined.app) and log in using Web3Auth. You can use social logins (Google, GitHub) or connect a crypto wallet. Navigate to **Settings > API Keys** and generate a new key. Store it securely - you'll need it for SDK integration. ```bash theme={null} export NVM_API_KEY="nvm:your-api-key-here" ``` Click **Agents > Register New Agent** and fill in: * Agent name and description * API endpoints (the URLs your agent exposes) * Optional: Link to OpenAPI spec, MCP manifest, or A2A agent card Click **Create Plan** on your agent and configure: * Plan name and description * Pricing (amount and currency) * Access type (credits or time-based) * Credits per request (for credit-based plans) ## Dashboard Overview ### Agents Tab View and manage all your registered AI agents: * **Agent Details**: Name, description, endpoints, and linked plans * **Status**: Active, paused, or pending * **Analytics**: Total queries, revenue, and active subscribers ### Plans Tab Manage payment plans for your agents: * **Plan Configuration**: Price, credits, duration * **Subscribers**: View who has purchased each plan * **Revenue**: Track earnings per plan ### Settings Configure your account and API access: * **API Keys**: Generate and manage keys for SDK integration * **Payment Settings**: Configure payout addresses * **Notifications**: Set up alerts for new subscribers and payments ## When to Use the App vs SDK | Use Case | Nevermined App | Payment Libraries | | ----------------------- | -------------- | ----------------- | | Quick setup and testing | Best | Good | | Non-technical users | Best | Not recommended | | CI/CD automation | Not possible | Best | | Custom workflows | Limited | Best | | Bulk operations | Manual | Automated | ## Key Features ### No-Code Agent Registration Register agents without writing code: 1. Enter your agent's metadata (name, description, tags) 2. Add API endpoints (supports REST, OpenAPI, MCP, A2A) 3. Configure access control settings 4. Click "Register" - done! ### Visual Plan Builder Create payment plans through a guided interface: * Select plan type (credits, time, or dynamic) * Set pricing in crypto (USDC, USDT) or fiat (via Stripe) * Configure usage limits and terms * Preview how subscribers will see the plan ### Real-Time Analytics Track your agent's performance: * **Queries**: Total requests, successful vs failed * **Revenue**: Earnings by day, week, month * **Subscribers**: Active users, new signups, churn * **Credits**: Usage patterns, top consumers ## Integration with SDKs The App works seamlessly with the Payment Libraries: ```typescript theme={null} // Agents registered in the App can be accessed via SDK const agent = await payments.agents.getAgent('did:nv:your-agent-id') // Plans created in the App work with SDK ordering await payments.plans.orderPlan(agent.plans[0].planId) // Credentials work the same way const { accessToken } = await payments.x402.getX402AccessToken( planId, agentId ) ``` ## Security * **Web3Auth**: Secure authentication without managing private keys * **Non-Custodial**: You control your funds - payments go directly to your address * **API Key Scoping**: Create keys with limited permissions for different use cases ## Next Steps Step-by-step guide to registering your first agent Create and configure payment plans Integrate programmatically with the SDKs Complete quickstart guide # Register Agents Source: https://docs.nevermined.app/docs/products/nevermined-app/register-agents Step-by-step guide to registering AI agents through the Nevermined App Register your AI agents through the Nevermined App's visual interface. This guide walks through each step of the registration process. ## Prerequisites * A Nevermined account (sign up at [nevermined.app](https://nevermined.app)) * Your AI agent's API endpoint(s) ready to receive requests * Optional: OpenAPI spec, MCP manifest, or A2A agent card URL ## Registration Steps From the Nevermined App dashboard, click **Agents** in the sidebar, then click **Register New Agent**. Fill in your agent's metadata: | Field | Description | Example | | --------------- | --------------------------- | ------------------------------------------------------ | | **Name** | Display name for your agent | "Legal Document Analyzer" | | **Description** | What your agent does | "AI-powered legal document analysis and summarization" | | **Tags** | Categories for discovery | `legal`, `ai`, `documents` | Add the endpoints your agent exposes: * **HTTP Method**: GET, POST, PUT, DELETE * **URL**: The full endpoint URL (e.g., `https://api.myservice.com/analyze`) * **Description**: What this endpoint does You can add multiple endpoints for a single agent. Your endpoints must be publicly accessible. Nevermined will route requests to these URLs with the subscriber's access token. Link to a machine-readable definition of your agent: | Type | URL Format | | ------------------ | -------------------------------------------------- | | **OpenAPI** | `https://api.myservice.com/openapi.json` | | **MCP Manifest** | `https://api.myservice.com/mcp.json` | | **A2A Agent Card** | `https://api.myservice.com/.well-known/agent.json` | This enables automatic discovery and integration with AI frameworks. Review your agent configuration and click **Register Agent**. Your agent will be assigned a unique ID (`did:nv:...`). ## Agent Configuration Examples ### REST API Agent For a standard REST API: ``` Name: Data Analysis API Description: Statistical analysis and visualization service Endpoints: - POST https://api.dataservice.com/analyze - GET https://api.dataservice.com/status/{job_id} - GET https://api.dataservice.com/results/{job_id} Definition URL: https://api.dataservice.com/openapi.json ``` ### MCP Tool Agent For an MCP-compatible agent: ``` Name: Code Review Assistant Description: AI-powered code review and suggestions Endpoints: - POST https://mcp.codereviewer.com/review Definition URL: https://mcp.codereviewer.com/mcp.json ``` ### A2A Agent For Google Agent-to-Agent compatible agents: ``` Name: Travel Booking Agent Description: Autonomous travel planning and booking Endpoints: - POST https://a2a.travelbot.com/tasks Definition URL: https://a2a.travelbot.com/.well-known/agent.json ``` ## After Registration Once your agent is registered, you can: 1. **Create Payment Plans**: Add pricing to your agent 2. **View Agent ID**: Copy your `did:nv:...` for SDK integration 3. **Monitor Activity**: Track queries and usage 4. **Edit Settings**: Update endpoints or metadata ## Managing Multiple Agents The Agents tab shows all your registered agents: | Column | Description | | ----------- | -------------------------------- | | **Name** | Agent display name | | **ID** | Unique identifier (did:nv:...) | | **Plans** | Number of payment plans attached | | **Queries** | Total requests received | | **Status** | Active, Paused, or Pending | Click any agent to view details, edit configuration, or create payment plans. ## Best Practices Choose names that clearly describe what your agent does. This helps subscribers find and understand your service. Provide clear descriptions for each endpoint. Include expected inputs, outputs, and any rate limits. Link to an OpenAPI spec, MCP manifest, or A2A card. This enables automatic integration with AI frameworks and improves discoverability. All endpoints should use HTTPS for secure communication. HTTP endpoints are not supported. ## Troubleshooting Verify your endpoint URL is correct and publicly accessible. Test with `curl` or a similar tool. Check that all required fields are filled and your endpoint URLs are valid HTTPS URLs. Refresh the Agents page. New agents may take a few seconds to appear. ## Next Steps Create payment plans for your agent Register agents programmatically # Payment Libraries Source: https://docs.nevermined.app/docs/products/payment-libraries/overview TypeScript and Python SDKs for programmatic integration with Nevermined payment infrastructure The Nevermined Payment Libraries are SDKs for programmatically integrating Nevermined payments into your application (create agents & plans, validate requests, and interact with the x402 flow). This page is intentionally a lightweight hub. For method-by-method documentation, use the API Reference. For end-to-end integration examples, use the Quick Starts and framework guides. ## SDKs Install and use `@nevermined-io/payments`. Install and use `payments-py`. ## Where to go next Register a service, create a plan, and get your first paid request working. End-to-end TypeScript example. End-to-end Python example. Detailed SDK and REST documentation. ## Notes * The standalone SDK guides previously linked from here (TypeScript / Python) largely duplicate the API Reference pages and the Quick Starts. * If you want, we can keep those pages on disk but de-emphasize them (current setup), or delete them entirely. # How the x402 Facilitator Works Source: https://docs.nevermined.app/docs/products/x402-facilitator/how-it-works Technical deep-dive into x402 verification and programmable settlement This guide explains the end-to-end x402 flow from both the subscriber (client) and resource server (API/agent) perspectives. For the complete technical specification, see the [x402 Smart Accounts Extension Spec](/docs/specs/x402-smart-accounts). If you're new to the programmable extension concepts, see: * [Making x402 programmable](https://nevermined.ai/blog/making-x402-programmable) * [Building Agentic Payments with Nevermined, x402, A2A, and AP2](https://nevermined.ai/blog/building-agentic-payments-with-nevermined-x402-a2a-and-ap2) ## The Nevermined x402 programmable extension Nevermined extends x402 with the `nvm:erc4337` scheme, enabling **programmable settlement** (credits/subscriptions/PAYG) using ERC-4337 smart accounts and session keys. ### PaymentRequired Response (402) When a server requires payment, it returns a `402` response with a `payment-required` header: ```json theme={null} { "x402Version": 2, "error": "Payment required to access resource", "resource": { "url": "/api/v1/agents/80918427.../tasks", "description": "AI agent task execution" }, "accepts": [{ "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015" } }], "extensions": {} } ``` ### PaymentPayload (Client Response) The client responds with a `payment-signature` header containing the x402 access token: ```json theme={null} { "x402Version": 2, "accepted": { "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015" } }, "payload": { "signature": "0x01845ADb2C711129d4f3966735eD98a9F09fC4cE...", "authorization": { "from": "0xD4f58B60330bC59cB0A07eE6A1A66ad64244eC8c", "sessionKeysProvider": "zerodev", "sessionKeys": [ { "id": "order", "data": "0x20a13d82dd9ee289..." }, { "id": "redeem", "data": "0x68e8e34d65914908..." } ] } }, "extensions": {} } ``` ## Subscriber flow (client side) ### Step 1: Discover payment requirements (HTTP 402) When calling a protected endpoint, the server returns a `402 Payment Required` response with the `payment-required` header containing the payment requirements. ```typescript theme={null} import { X402_HEADERS } from '@nevermined-io/payments/express' // Call protected endpoint - get 402 response const response = await fetch('https://api.example.com/protected') if (response.status === 402) { // Decode the payment-required header const paymentRequired = JSON.parse( Buffer.from(response.headers.get(X402_HEADERS.PAYMENT_REQUIRED)!, 'base64').toString() ) // Extract planId and agentId from accepts array const { planId, extra } = paymentRequired.accepts[0] const agentId = extra?.agentId } ``` ```python theme={null} import base64 import json import requests response = requests.get('https://api.example.com/protected') if response.status_code == 402: # Decode the payment-required header payment_required = json.loads( base64.b64decode(response.headers.get('payment-required')).decode() ) # Extract planId and agentId from accepts array plan_id = payment_required['accepts'][0]['planId'] agent_id = payment_required['accepts'][0].get('extra', {}).get('agentId') ``` ### Step 2: Generate x402 access token Use the Nevermined SDK to generate an x402 access token with the required session keys: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY, environment: 'sandbox' }) // Generate the x402 access token const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId) ``` ```python theme={null} import os from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) # Generate the x402 access token token_res = payments.x402.get_x402_access_token(plan_id, agent_id) access_token = token_res['accessToken'] ``` ### Step 3: Retry request with payment header Send the x402 access token in the `payment-signature` header: ```typescript theme={null} import { X402_HEADERS } from '@nevermined-io/payments/express' const result = await fetch('https://api.example.com/protected', { method: 'POST', headers: { 'Content-Type': 'application/json', [X402_HEADERS.PAYMENT_SIGNATURE]: accessToken }, body: JSON.stringify({ prompt: 'Hello' }) }) // Decode the settlement receipt from payment-response header if (result.ok) { const settlement = JSON.parse( Buffer.from(result.headers.get(X402_HEADERS.PAYMENT_RESPONSE)!, 'base64').toString() ) console.log('Credits used:', settlement.creditsRedeemed) } ``` ```python theme={null} result = requests.post( 'https://api.example.com/protected', headers={ 'Content-Type': 'application/json', 'payment-signature': access_token }, json={'prompt': 'Hello'} ) # Decode the settlement receipt from payment-response header if result.ok: settlement = json.loads( base64.b64decode(result.headers.get('payment-response')).decode() ) print('Credits used:', settlement['creditsRedeemed']) ``` ## Resource server flow (API/agent side) **Recommended:** For Express.js applications, use the [`paymentMiddleware`](/docs/integrate/add-to-your-agent/express) which handles all of this automatically with one line of code. ### Step 1: Return 402 when payment is missing If the payment header is not present, respond with `402` and set the `payment-required` header with your payment requirements: ```typescript theme={null} import { X402_HEADERS } from '@nevermined-io/payments/express' const x402Token = req.headers['payment-signature'] if (!x402Token) { const paymentRequired = { x402Version: 2, error: 'Payment required to access resource', resource: { url: req.path }, accepts: [{ scheme: 'nvm:erc4337', network: 'eip155:84532', planId: process.env.NVM_PLAN_ID, extra: { version: '1', agentId: process.env.NVM_AGENT_ID } }], extensions: {} } res.set(X402_HEADERS.PAYMENT_REQUIRED, Buffer.from(JSON.stringify(paymentRequired)).toString('base64')) return res.status(402).json({ error: 'Payment Required' }) } ``` ```python theme={null} x402_token = request.headers.get('payment-signature') if not x402_token: payment_required = { 'x402Version': 2, 'error': 'Payment required to access resource', 'resource': {'url': request.path}, 'accepts': [{ 'scheme': 'nvm:erc4337', 'network': 'eip155:84532', 'planId': os.environ['NVM_PLAN_ID'], 'extra': { 'version': '1', 'agentId': os.environ.get('NVM_AGENT_ID') } }], 'extensions': {} } response = jsonify({'error': 'Payment Required'}) response.headers['payment-required'] = base64.b64encode( json.dumps(payment_required).encode() ).decode() return response, 402 ``` ### Step 2: Verify with the facilitator Verify the x402 token with the facilitator. The facilitator extracts `planId` and subscriber address from the token: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY, environment: 'sandbox' }) // Verify with facilitator - extracts planId/subscriberAddress from token const verification = await payments.facilitator.verifyPermissions({ x402AccessToken: x402Token, maxAmount: BigInt(creditsRequired) }) if (!verification.isValid) { return res.status(402).json({ error: 'Payment verification failed' }) } ``` ```python theme={null} from payments_py import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) # Verify with facilitator - extracts planId/subscriberAddress from token verification = payments.facilitator.verify_permissions( x402_access_token=x402_token, max_amount=credits_required ) if not verification.is_valid: return jsonify({'error': 'Payment verification failed'}), 402 ``` ### Step 3: Execute Workload ```typescript theme={null} // Only execute after verification succeeds const result = await processAIRequest(req.body) ``` ### Step 4: Settle Payment Settle after work is complete and return the settlement receipt in the `payment-response` header: ```typescript theme={null} import { X402_HEADERS } from '@nevermined-io/payments/express' // Settle after work is complete const settlement = await payments.facilitator.settlePermissions({ x402AccessToken: x402Token, maxAmount: BigInt(actualCreditsUsed) }) // Set payment-response header with settlement receipt const settlementReceipt = { success: true, creditsRedeemed: actualCreditsUsed, transactionHash: settlement.txHash } res.set(X402_HEADERS.PAYMENT_RESPONSE, Buffer.from(JSON.stringify(settlementReceipt)).toString('base64')) // Return response res.json({ result }) ``` ```python theme={null} # Settle after work is complete settlement = payments.facilitator.settle_permissions( x402_access_token=x402_token, max_amount=actual_credits_used ) # Set payment-response header with settlement receipt settlement_receipt = { 'success': True, 'creditsRedeemed': actual_credits_used, 'transactionHash': settlement.tx_hash } response = jsonify({'result': result}) response.headers['payment-response'] = base64.b64encode( json.dumps(settlement_receipt).encode() ).decode() return response ``` ## Complete Request Lifecycle ```mermaid theme={null} sequenceDiagram autonumber actor Client participant Agent as AI Agent participant Facilitator participant Chain as Blockchain Client->>Agent: GET /resource (no payment) Agent->>Client: 402 + payment-required header Client->>Client: Generate x402 token via SDK Client->>Agent: POST /resource + payment-signature header Agent->>Facilitator: POST /verify Facilitator->>Facilitator: Validate signature & session keys Facilitator->>Chain: Check balance Chain-->>Facilitator: Balance OK Facilitator-->>Agent: Verification OK Agent->>Agent: Execute AI workload Agent->>Facilitator: POST /settle Facilitator->>Chain: Execute burn operation Chain-->>Facilitator: Tx confirmed Facilitator-->>Agent: Settlement OK + txHash Agent-->>Client: Response + payment-response header ``` ## Error Handling | Error | HTTP Status | Cause | | ---------------------- | ----------- | ----------------------------------- | | Missing payment header | 402 | No `payment-signature` header | | Invalid signature | 402 | Signature verification failed | | Insufficient balance | 402 | User needs to purchase more credits | | Expired session | 402 | Session key has expired | | Settlement failed | 500 | On-chain transaction failed | ## Next Steps One-line payment protection with Express middleware Configure credits, subscriptions, and dynamic pricing Complete integration guide with code examples Full x402 Smart Accounts specification # x402 Facilitator Overview Source: https://docs.nevermined.app/docs/products/x402-facilitator/overview The Nevermined x402 Facilitator verifies and settles x402 payments with smart accounts and programmable contracts The Nevermined x402 Facilitator is an **enforcement and settlement engine** for x402. It lets any API, agent, MCP tool, or protected resource accept **HTTP-native payments** without running on-chain infrastructure. It supports both: * **Standard x402 settlement** ("pay-per-request" style) * **Nevermined's programmable x402 extension** (`nvm:erc4337` scheme with smart accounts + session keys + contract settlement) For the complete technical specification, see the [x402 Smart Accounts Extension Spec](/docs/specs/x402-smart-accounts). ## What is x402? x402 standardizes a payment-enforced HTTP flow: 1. Client calls an endpoint normally 2. Server responds with **HTTP 402 Payment Required** and `payment-required` header 3. Client builds a payment authorization (locally signed) 4. Client retries the request including the x402 token in the `payment-signature` header 5. Server delegates verification and settlement to the **Facilitator** 6. Server returns response with `payment-response` header (settlement receipt) For more background and examples: * [Making x402 programmable](https://nevermined.ai/blog/making-x402-programmable) * [Building Agentic Payments with Nevermined, x402, A2A, and AP2](https://nevermined.ai/blog/building-agentic-payments-with-nevermined-x402-a2a-and-ap2) ## Why use a Facilitator? A facilitator is the third party that: * verifies payment proofs * simulates/enforces what is allowed (amount, plan, merchant/agent binding) * executes settlement on-chain * returns a canonical receipt (e.g., transaction hash) This is particularly important for **programmable x402**, where settlement may be more than a single ERC-20 transfer (credits, subscriptions, policy-based settlement). ## Facilitator API ### Environments | Environment | URL | Purpose | | -------------- | -------------------------------------------- | ------------------------------------- | | **Sandbox** | `https://facilitator.sandbox.nevermined.app` | Testing and development with testnets | | **Production** | `https://facilitator.live.nevermined.app` | Live mainnet transactions | Use the sandbox environment for development and testing. Switch to production only when you're ready to process real payments. ### Core Endpoints The Nevermined x402 Facilitator exposes two core endpoints for payment verification and settlement: **POST** `/api/v1/x402/verify` Validates payment authorization, checks permissions, and simulates on-chain settlement before workload execution. **POST** `/api/v1/x402/settle` Executes on-chain settlement after workload completion and returns transaction receipt. ## How it works ```mermaid theme={null} sequenceDiagram participant Client participant Server as Resource Server (API/Agent) participant Facilitator as Nevermined x402 Facilitator participant Chain as Blockchain Client->>Server: Request Server-->>Client: 402 + payment-required header Client->>Client: Generate x402 token via SDK Client->>Server: Retry + payment-signature header Server->>Facilitator: /verify (simulate + validate) Facilitator->>Chain: Policy + balance checks Facilitator-->>Server: Verification OK Server->>Server: Execute workload Server->>Facilitator: /settle (execute settlement) Facilitator->>Chain: Execute contract settlement Facilitator-->>Server: Receipt + txHash Server-->>Client: Response + payment-response header ``` ## Nevermined's programmable x402 extension Standard x402 is often implemented as an "exact transfer" authorization (e.g., EIP-3009). Nevermined extends x402 to support: * **Smart Accounts (ERC-4337)** and delegated **session keys** * **Smart-contract settlement** (credits, subscriptions, PAYG, dynamic charging) * **Policy enforcement** (merchant allowlists, spend caps, validity windows) This keeps the HTTP handshake the same, but upgrades settlement from "transfer" to **programmable execution**. ## Facilitator responsibilities ### Verification * x402 envelope structure/version * signature authenticity * session key validity + scoped permissions * plan state + subscriber balance * simulation of allowed on-chain actions (UserOps) ### Settlement After the server completes its work, the facilitator can execute the settlement action permitted by the payment payload, such as: * `order` (purchase/top-up) * `redeem` / `burn` (consume credits) * "exact" transfers (when using standard x402) ## Getting started One-line payment protection with Express middleware End-to-end flow (client + server) with x402 headers and facilitator calls Credits, subscriptions, and dynamic pricing using programmable settlement Integrate x402 into your API/agent Use x402 with A2A + AP2-style payment intent messaging # Payment Models Source: https://docs.nevermined.app/docs/products/x402-facilitator/payment-models Configure credits-based, time-based, and dynamic pricing using programmable x402 settlement The x402 Facilitator supports multiple payment models to match your product needs across APIs, agents/tools, and protected resources. These models are enabled by Nevermined's **programmable x402 settlement** layer (smart accounts + policies + contracts), not just single-transfer pay-per-request flows. If you’re looking for the HTTP handshake details (402 discovery → retry with `PAYMENT-SIGNATURE`), see: * [Nevermined x402](/docs/development-guide/nevermined-x402) Further reading: * [Making x402 programmable](https://nevermined.ai/blog/making-x402-programmable) * [Building Agentic Payments with Nevermined, x402, A2A, and AP2](https://nevermined.ai/blog/building-agentic-payments-with-nevermined-x402-a2a-and-ap2) ## Payment Model Types Charge per request or API call. Users purchase credits and consume them with each query. Subscription access for specific durations. Unlimited usage within the time period. Variable pricing based on request complexity, token count, or custom metrics. Combine time-based access with credit limits for balanced monetization. ## Credits-Based Plans Charge users based on actual usage. Each request consumes a configurable number of credits. ```typescript theme={null} import { Payments, getERC20PriceConfig, getFixedCreditsConfig } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY, environment: 'sandbox' }) // Price: 10 USDC for 100 credits const priceConfig = getERC20PriceConfig( 10_000_000n, // 10 USDC (6 decimals) '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC address process.env.BUILDER_ADDRESS // Your receiving address ) // Credits: 100 total, 1 per request const creditsConfig = getFixedCreditsConfig( 100n, // Total credits 1n // Credits per request ) const { planId } = await payments.plans.createPlan({ agentId: 'did:nv:agent-123', name: 'Pro Plan - 100 Queries', description: 'Access to AI assistant with 100 queries', priceConfig, creditsConfig, accessLimit: 'credits' }) ``` ```python theme={null} from payments_py import Payments, PaymentOptions from payments_py.plans import get_erc20_price_config, get_fixed_credits_config payments = Payments.get_instance( PaymentOptions(nvm_api_key=os.environ['NVM_API_KEY'], environment='sandbox') ) # Price: 10 USDC for 100 credits price_config = get_erc20_price_config( 10_000_000, # 10 USDC (6 decimals) '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', # USDC address os.environ['BUILDER_ADDRESS'] # Your receiving address ) # Credits: 100 total, 1 per request credits_config = get_fixed_credits_config( 100, # Total credits 1 # Credits per request ) plan = payments.plans.create_plan( agent_id='did:nv:agent-123', name='Pro Plan - 100 Queries', description='Access to AI assistant with 100 queries', price_config=price_config, credits_config=credits_config, access_limit='credits' ) ``` ## Time-Based Plans Provide unlimited access for a specific duration. Great for subscription models. ```typescript theme={null} import { getTimeBasedConfig } from '@nevermined-io/payments' // Price: 50 USDC for 30 days access const priceConfig = getERC20PriceConfig( 50_000_000n, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', process.env.BUILDER_ADDRESS ) // Duration: 30 days (in seconds) const timeConfig = getTimeBasedConfig( 30 * 24 * 60 * 60 // 30 days in seconds ) const { planId } = await payments.plans.createPlan({ agentId: 'did:nv:agent-123', name: 'Monthly Subscription', description: 'Unlimited access for 30 days', priceConfig, timeConfig, accessLimit: 'time' }) ``` ```python theme={null} from payments_py.plans import get_time_based_config # Price: 50 USDC for 30 days access price_config = get_erc20_price_config( 50_000_000, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', os.environ['BUILDER_ADDRESS'] ) # Duration: 30 days (in seconds) time_config = get_time_based_config( 30 * 24 * 60 * 60 # 30 days in seconds ) plan = payments.plans.create_plan( agent_id='did:nv:agent-123', name='Monthly Subscription', description='Unlimited access for 30 days', price_config=price_config, time_config=time_config, access_limit='time' ) ``` ## Dynamic Pricing Charge variable amounts based on request complexity. The x402 `max_amount` field controls the maximum charge per request. ```typescript theme={null} import { getDynamicCreditsConfig } from '@nevermined-io/payments' // Dynamic: charge 1-10 credits based on request const creditsConfig = getDynamicCreditsConfig( 1n, // Minimum credits per request 10n // Maximum credits per request ) const { planId } = await payments.plans.createPlan({ agentId: 'did:nv:agent-123', name: 'Pay As You Go', description: 'Variable pricing based on complexity', priceConfig, creditsConfig, accessLimit: 'credits' }) // During settlement, specify actual amount await payments.facilitator.settlePermissions({ planId, maxAmount: BigInt(actualCreditsUsed), // 1-10 based on request x402AccessToken: token, subscriberAddress: address }) ``` ```python theme={null} from payments_py.plans import get_dynamic_credits_config # Dynamic: charge 1-10 credits based on request credits_config = get_dynamic_credits_config( 1, # Minimum credits per request 10 # Maximum credits per request ) plan = payments.plans.create_plan( agent_id='did:nv:agent-123', name='Pay As You Go', description='Variable pricing based on complexity', price_config=price_config, credits_config=credits_config, access_limit='credits' ) # During settlement, specify actual amount await facilitator.settle(payment_payload, PaymentRequirements( plan_id=plan_id, agent_id=agent_id, max_amount=actual_credits_used, # 1-10 based on request extra={'subscriber_address': address} )) ``` ## Trial Plans Offer free trials to attract new users. ```typescript theme={null} // Free credits trial const { planId } = await payments.plans.registerCreditsTrialPlan({ agentId: 'did:nv:agent-123', name: 'Free Trial', description: '10 free queries to try the service', credits: 10n, creditsPerRequest: 1n }) // Free time-based trial const { planId: timePlanId } = await payments.plans.registerTimeTrialPlan({ agentId: 'did:nv:agent-123', name: '7-Day Trial', description: 'Unlimited access for 7 days', duration: 7 * 24 * 60 * 60 // 7 days in seconds }) ``` ```python theme={null} # Free credits trial plan = payments.plans.register_credits_trial_plan( agent_id='did:nv:agent-123', name='Free Trial', description='10 free queries to try the service', credits=10, credits_per_request=1 ) # Free time-based trial time_plan = payments.plans.register_time_trial_plan( agent_id='did:nv:agent-123', name='7-Day Trial', description='Unlimited access for 7 days', duration=7 * 24 * 60 * 60 # 7 days in seconds ) ``` ## Multi-Tier Pricing Create multiple plans for different user segments: ```typescript theme={null} // Basic tier const basicPlan = await payments.plans.createPlan({ agentId, name: 'Basic', priceConfig: getERC20PriceConfig(5_000_000n, usdcAddress, builderAddress), creditsConfig: getFixedCreditsConfig(50n, 1n) }) // Pro tier const proPlan = await payments.plans.createPlan({ agentId, name: 'Pro', priceConfig: getERC20PriceConfig(20_000_000n, usdcAddress, builderAddress), creditsConfig: getFixedCreditsConfig(250n, 1n) // Better value }) // Enterprise tier const enterprisePlan = await payments.plans.createPlan({ agentId, name: 'Enterprise', priceConfig: getERC20PriceConfig(100_000_000n, usdcAddress, builderAddress), creditsConfig: getFixedCreditsConfig(2000n, 1n) // Best value }) ``` ## Payment Currencies ### Cryptocurrency (ERC-20) Accept any ERC-20 token on supported networks: ```typescript theme={null} // USDC const usdcConfig = getERC20PriceConfig( 10_000_000n, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', builderAddress ) // USDT const usdtConfig = getERC20PriceConfig( 10_000_000n, '0xdAC17F958D2ee523a2206206994597C13D831ec7', builderAddress ) ``` ### Fiat (Stripe) Accept credit card payments via Stripe integration: ```typescript theme={null} const fiatConfig = await payments.plans.getFiatPriceConfig({ amount: 1000, // $10.00 currency: 'USD' }) // Users purchase via Stripe checkout const { url } = await payments.plans.orderFiatPlan(planId) // Redirect user to Stripe checkout URL ``` ## Choosing the Right Model | Model | Best For | Example | | ------------- | -------------------------------- | ------------------------------- | | Credits-based | API calls, queries, transactions | AI chatbot, image generation | | Time-based | Continuous access, subscriptions | SaaS tools, monitoring | | Dynamic | Variable workloads, token-based | LLM inference, batch processing | | Hybrid | Balanced usage with limits | Enterprise subscriptions | ## Next Steps Set up your agent with a payment plan Implement request validation in your agent # Static Resources Protection & Monetization Source: https://docs.nevermined.app/docs/solutions/access-control-monetization-static-resources Gate and monetize datasets, models, or APIs using Nevermined’s transparent HTTP Proxy with flexible payment plans. Monetize static assets—like datasets, files, pre-trained models, or HTTP URLs without restructuring your infrastructure. Nevermined provides a transparent HTTP Proxy and access control, enabling you to define who can access which resources and under what conditions. ## How It Works * **Transparent Proxy**: Requests pass through Nevermined’s proxy, which validates access tokens and credit balances before forwarding to your asset. * **Payment Plans**: Offer subscriptions or credit-based plans priced in stablecoins or fiat. * **Fine-Grained Access**: Control access per resource, route, or method; enforce rate limits and usage caps. ## Benefits * **Minimal changes**: Keep your existing storage/API; add a proxy in front. * **Secure access**: Bearer-token validation and metered usage. * **Flexible payments**: Stablecoin and fiat support with configurable pricing. * **Operational clarity**: Audit logs and usage metrics per resource. ## Implementation Steps 1. **Model your Resource as an Agent**: Register a resource-representing agent with endpoints/paths. 2. **Create a Payment Plan**: Choose subscription or credits and set pricing. 3. **Enable the HTTP Proxy**: Configure the proxy to protect your resource and validate incoming requests. 4. **Publish & Monitor**: Make the resource available; track usage and revenue in the App. ## Learn More * Development Guide: [Register Plans & Agents](/docs/development-guide/registration) # x402 Agent Monetization Source: https://docs.nevermined.app/docs/solutions/agent-to-agent-monetization Monetize AI agents using Nevermined’s x402 protocol with stablecoin and fiat payments—no bespoke integrations required. Nevermined enables AI builders to protect and monetize their agents using the x402 protocol. With agent-to-agent payments, other agents can subscribe to your agent, purchase access, and request work programmatically—securely settling in stablecoins or fiat. ## How It Works * **Register your Agent & Plan**: Create a payment plan in the [Nevermined App](https://nevermined.app) and link it to your AI agent (endpoints, metadata, pricing). * **Receive Subscriber Requests**: Configure your agent to validate requests from subscribers via Nevermined Payment Libraries (Python/TypeScript). No additional servers or custom gateways needed. * **Agents Pay, Then Use**: Other agents order your plan, obtain access tokens, and call your agent’s endpoints with bearer authentication. * **Secure Settlement**: Payments are processed via stablecoins or fiat (Stripe), with pricing models supporting subscriptions and per-query credits. ## Benefits * **Simple setup**: Create a plan, link your agent—done. * **Programmable access**: Agents can buy and use services autonomously. * **Flexible pricing**: Subscriptions, credits, and metered usage. * **Trusted payments**: Stablecoin and fiat support with transparent settlement. ## Implementation Steps 1. **Create a Payment Plan** in the Nevermined App (price, duration, credits). 2. **Register your Agent**: Provide endpoint URLs and capabilities. 3. **Link Plan → Agent**: Ensure the plan grants access to your agent. 4. **Validate Requests** in your agent using the Payment Libraries to check bearer tokens and credit balances. 5. **Publish** your agent so other agents can discover and subscribe. ## Learn More * Development Guide: [Register Plans & Agents](/docs/development-guide/registration) * Development Guide: [Purchase Plans](/docs/development-guide/order-plans) * Development Guide: [Query Agents](/docs/development-guide/query-agents) * Blog: [Making x402 Programmable](https://nevermined.ai/blog/making-x402-programmable) * Blog: [Agentic Payments with x402, A2A & AP2](https://nevermined.ai/blog/building-agentic-payments-with-nevermined-x402-a2a-and-ap2) # AI Agents Observability Source: https://docs.nevermined.app/docs/solutions/ai-agents-observability Track incoming requests, usage, and revenue for your AI agents using Nevermined’s observability tools. Nevermined provides end-to-end observability for your AI agents. Track incoming requests, measure credit redemption, and monitor revenue to understand how your services are used and optimize performance. ## How It Works * **Request Tracking**: Each call is validated and logged (success/failure, credits used, caller identity). * **Usage Metrics**: Monitor per-agent and per-plan usage, remaining balances, and redemption history. * **Revenue Analytics**: See plan purchases, settlements, and trends. ## Benefits * **Operational visibility**: Clear overview of traffic and usage. * **Optimization**: Identify hot endpoints and improve performance. * **Compliance & auditing**: Traceable records for paid access. ## Implementation Steps 1. **Enable Observability** via Payment Libraries configuration in your agent. 2. **Create Plans & Link Agents** to ensure paid calls are tracked. 3. **Monitor in the App**: Use dashboards to review usage and revenue. 4. **Export Data**: Pull metrics via API for custom dashboards. ## Learn More * Development Guide: [Observability Development Guide](/docs/development-guide/observability) # MCP Point of Sales Source: https://docs.nevermined.app/docs/solutions/mcp-point-of-sales Monetize Model Context Protocol tools across web and IDEs with Nevermined’s MCP integration. Set up a point of sale for MCP tools. With Nevermined’s MCP integration, builders can automatically expose tools and allow users to pay for access—whether in web apps or non-web environments like Cursor and VS Code. ## How It Works * **Tool Discovery**: Your MCP tools are registered as agent capabilities. * **Payment Plans**: Users purchase subscriptions or credits to invoke tools. * **Access Tokens**: Nevermined issues bearer tokens for authorized tool calls. * **Multi-Channel**: Works in browsers, CLIs, and IDEs supporting MCP. ## Benefits * **Immediate monetization**: Start charging for MCP tool usage. * **Broad distribution**: Reach users in web and IDE environments. * **Flexible pricing**: Subscriptions and metered credits. * **Unified access**: One access model across channels. ## Implementation Steps 1. **Create a Payment Plan** in the Nevermined App. 2. **Register your MCP Tools as an Agent** with endpoints representing tool invocations. 3. **Integrate Nevermined MCP**: Configure MCP clients to include Nevermined-issued bearer tokens. 4. **Publish** your MCP-enabled agent; share with users. ## Learn More * Integration Guide: [MCP Integration](/docs/integrations/mcp) # Technical Specifications Source: https://docs.nevermined.app/docs/specs/introduction Formal specifications for Nevermined protocol, extensions, and integration standards This section contains the **technical specifications** that define how Nevermined protocol and extensions work. These documents are intended for developers building integrations, protocol implementers, and anyone who needs to understand the precise behavior of Nevermined systems. ## Purpose Specifications in this section: * **Define normative behavior** using precise, unambiguous language * **Follow RFC 2119 conventions** for requirement levels (MUST, SHOULD, MAY) * **Include data structures** with exact field definitions and types * **Document protocol flows** with sequence diagrams and state transitions * **Specify error handling** and edge cases ## Audience These specifications are written for: * **Protocol implementers** building x402 facilitators or clients * **SDK developers** creating libraries that interact with Nevermined * **Integration engineers** connecting existing systems to Nevermined * **Security auditors** reviewing protocol designs ## Relationship to Other Documentation | Documentation Type | Purpose | | ---------------------- | ------------------------------------------ | | **Getting Started** | Quickstart guides and tutorials | | **Use Cases** | Business scenarios and solution patterns | | **Integration Guides** | Step-by-step implementation instructions | | **API Reference** | SDK methods and REST endpoints | | **Specifications** | Formal protocol definitions (this section) | Specifications are the **source of truth** for protocol behavior. Other documentation may simplify or summarize, but specifications define what implementations MUST do. ## Conventions ### Requirement Levels This documentation uses RFC 2119 terminology: * **MUST** / **REQUIRED** / **SHALL** — absolute requirement * **MUST NOT** / **SHALL NOT** — absolute prohibition * **SHOULD** / **RECOMMENDED** — there may be valid reasons to ignore, but implications must be understood * **SHOULD NOT** / **NOT RECOMMENDED** — there may be valid reasons to do this, but implications must be understood * **MAY** / **OPTIONAL** — truly optional behavior ### Data Types * JSON structures use TypeScript-style type annotations * Hexadecimal values are prefixed with `0x` * Base64 encoding uses standard alphabet with `=` padding * Timestamps use ISO 8601 format unless otherwise specified ## Available Specifications Extends x402 with ERC-4337 smart accounts, session keys, and programmable smart contract settlement ## Contributing Specifications evolve based on implementation experience and community feedback. If you identify ambiguities, errors, or improvements, please open an issue in the [Nevermined GitHub repository](https://github.com/nevermined-io). # x402 Smart Accounts Extension Source: https://docs.nevermined.app/docs/specs/x402-smart-accounts Specification for extending x402 with ERC-4337 smart accounts, session keys, and programmable smart contract settlement ## Abstract This specification defines a **smart-account extension** to the [x402 protocol](https://x402.org/) that enables programmable payment settlement using ERC-4337 smart accounts and session keys. While standard x402 settles payments via EIP-3009 ERC-20 token transfers, this extension allows settlement through arbitrary smart contract interactions—enabling subscription plans, credit-based metering, time-based access, and other programmable payment models. The extension is designed to be **fully compatible** with existing x402 clients and servers, requiring only the addition of the `nvm:erc4337` extension payload. ``` Version: 0.3 Status: Draft Last Updated: January 2026 ``` ## 1. Introduction ### 1.1 Background The x402 protocol standardizes HTTP-native payments where: 1. A client requests a protected resource 2. The server responds with HTTP 402 and payment requirements 3. The client builds and signs a payment authorization 4. The client retries the request with the payment payload 5. A facilitator verifies and settles the payment Standard x402 implementations use EIP-3009 signatures to authorize ERC-20 token transfers. This works well for simple pay-per-request scenarios but cannot express more complex payment relationships. ### 1.2 Motivation Many real-world payment scenarios require more than direct token transfers: * **Subscription plans** — pay once, access many times * **Credit packages** — purchase credits in bulk, consume over time * **Time-based access** — pay for access windows (hourly, daily, monthly) * **Tiered pricing** — different rates based on usage volume * **Spending limits** — caps on how much can be spent per period This extension enables these use cases by replacing the EIP-3009 transfer authorization with ERC-4337 UserOperations that can execute arbitrary smart contract calls. Also, end-user scenarios where clients are not experienced with blockchain wallets can benefit of using smart accounts. Smart Accounts bring a superior user experience by enabling social logins, meta-transactions, and session keys that delegate limited permissions to facilitators. This extension allows clients to leverage smart accounts for payment settlement in x402 flows. Finally, Smart Contracts enable more complex business logic, especially for payments. By using smart accounts and session keys, this extension allows for programmable payment models that can adapt to various commercial requirements. ### 1.3 Design Goals This extension: * **MUST** be compatible with the existing x402 HTTP handshake * **MUST** use the standard x402 payload structure with an extension field * **SHOULD** be generic enough to support any smart contract interaction * **SHOULD** leverage established standards (ERC-4337, EIP-712) * **MUST** allow facilitators to verify and execute operations on behalf of clients ## 2. Terminology The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). ### 2.1 Roles | Role | Description | | ----------------- | ---------------------------------------------------------------------------------------------------- | | **Client** | The entity requesting access to a protected resource. Signs payment authorizations locally. | | **Server** | The entity providing the protected resource (API, agent, service). Coordinates with the facilitator. | | **Facilitator** | A third-party service that verifies payment authorizations and executes settlement on-chain. | | **Smart Account** | An ERC-4337 compliant smart contract wallet controlled by the client. | ### 2.2 Definitions | Term | Definition | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | **UserOperation** | An ERC-4337 structure representing an intent to execute a transaction from a smart account. | | **Session Key** | A delegated signing key with scoped permissions, allowing a third party to execute specific actions on behalf of the smart account owner. | | **Order** | A UserOperation that purchases access (e.g., buying a subscription or credit package). | | **Redeem** / **Burn** | A UserOperation that consumes access (e.g., deducting credits for a request). | ## 3. Extension Structure ### 3.1 Scheme Identifier The scheme identifier is: `nvm:erc4337` ### 3.2 Payload Schema #### PaymentRequired Response (402) When a server requires payment, it returns: ```json theme={null} { "x402Version": 2, "error": "Payment required to access resource", "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepts": [{ "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015", "httpVerb": "POST" } }], "extensions": {} } ``` #### PaymentPayload (Client Response) The client responds with a PaymentPayload containing the selected scheme and authorization: ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepted": { "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015", "httpVerb": "POST" } }, "payload": { "signature": "0x01845ADb2C711129d4f3966735eD98a9F09fC4cE57d4e89a9255457dd052fe0b95b6357b1c74783f490f542c25a2ffa3833432c3e4603b710e01f1f110be3f3a578b0698c81beaf6ccaa63fdc9a32842ed29eeaa531b", "authorization": { "from": "0xD4f58B60330bC59cB0A07eE6A1A66ad64244eC8c", "sessionKeysProvider": "zerodev", "sessionKeys": [ { "id": "order", "data": "0x20a13d82dd9ee289fc5e5a90f4011c8dc03f8f5d5223aafd6b5cd67e3b1ab425" }, { "id": "redeem", "data": "0x68e8e34d659149087451cafc89a7320114072e49fd89657ccba14cad82c0a533" } ] } }, "extensions": {} } ``` ### 3.3 Field Definitions #### PaymentRequired Root Fields | Field | Type | Required | Description | | ------------- | -------- | -------- | ------------------------------------------ | | `x402Version` | `number` | Yes | Protocol version. MUST be `2`. | | `error` | `string` | Yes | Human-readable error message. | | `resource` | `object` | Yes | Protected resource information. | | `accepts` | `array` | Yes | Array of accepted payment schemes. | | `extensions` | `object` | Yes | Empty object `{}` for x402 spec alignment. | #### Resource Fields | Field | Type | Required | Description | | ------------- | -------- | -------- | --------------------------------------------------------- | | `url` | `string` | Yes | The protected resource URL. | | `description` | `string` | No | Human-readable description. | | `mimeType` | `string` | No | Expected response MIME type (e.g., `"application/json"`). | #### Scheme Fields (in `accepts[]` / `accepted`) | Field | Type | Required | Description | | --------- | -------- | -------- | -------------------------------------------------------------------- | | `scheme` | `string` | Yes | Payment scheme. MUST be `"nvm:erc4337"`. | | `network` | `string` | Yes | Blockchain network identifier using CAIP-2 (e.g., `"eip155:84532"`). | | `planId` | `string` | Yes | 256-bit plan identifier. | | `extra` | `object` | Yes | Additional scheme-specific fields. | #### Extra Fields | Field | Type | Required | Description | | ---------- | -------- | -------- | ------------------------------------------------------- | | `version` | `string` | Yes | Scheme version (e.g., `"1"`). | | `agentId` | `string` | No | 256-bit agent identifier (if plan has multiple agents). | | `httpVerb` | `string` | No | HTTP method for the endpoint. | #### PaymentPayload Fields | Field | Type | Required | Description | | ------------- | -------- | -------- | ------------------------------------------ | | `x402Version` | `number` | Yes | Protocol version. MUST be `2`. | | `resource` | `object` | No | Echoed from PaymentRequired. | | `accepted` | `object` | Yes | Selected scheme from `accepts[]`. | | `payload` | `object` | Yes | Scheme-specific authorization data. | | `extensions` | `object` | Yes | Empty object `{}` for x402 spec alignment. | #### Payload Fields (Authorization) | Field | Type | Required | Description | | --------------- | -------- | -------- | --------------------------------------------------------------- | | `signature` | `string` | Yes | EIP-712 signature of the payment payload, signed by the client. | | `authorization` | `object` | Yes | Session key authorization data. | #### Authorization Fields | Field | Type | Required | Description | | --------------------- | -------- | -------- | -------------------------------------------------------------------- | | `from` | `string` | Yes | Client's smart account address (ERC-4337 compliant). | | `sessionKeysProvider` | `string` | Yes | Session key provider identifier (e.g., `"zerodev"`, `"biconomy"`). | | `sessionKeys` | `array` | Yes | Array of session key objects. MUST contain at least one session key. | #### Session Key Object Each session key object MUST contain an `id` field and either `data` OR `hash`: | Field | Type | Required | Description | | ------ | -------- | ----------- | ------------------------------------------------------------------------------------- | | `id` | `string` | Yes | Operation identifier. Common values: `"order"`, `"redeem"`. | | `data` | `string` | Conditional | Base64-encoded session key data. Required if `hash` is not provided. | | `hash` | `string` | Conditional | Keccak256 hash of the base64-encoded session key. Required if `data` is not provided. | When `data` is provided, the facilitator can reconstruct and verify the session key directly. When only `hash` is provided, the facilitator MUST have a mechanism to retrieve the session key from off-chain storage using the hash. ### 3.4 Session Key Operations The extension defines standard operation identifiers: | Operation ID | Purpose | Description | | ------------ | -------- | ----------------------------------------------------------------------------------- | | `order` | Purchase | Executes a purchase operation (subscribe, buy credits, etc.). Used for auto top-up. | | `redeem` | Consume | Consumes access (deduct credits, mark subscription usage). Required for settlement. | Implementations MAY define additional operation identifiers for custom use cases. ## 4. Protocol Flow ### 4.1 Overview The protocol flow maintains compatibility with standard x402 while extending the verification and settlement phases: ```mermaid theme={null} --- title: Nevermined + x402 - Payment & Execution Flow --- sequenceDiagram autonumber %% ACTORS actor client as Client participant server as Server (AI Agent) participant facilitator as Facilitator (Nevermined API) participant blockchain as Blockchain %% 1) CLIENT SELECTS PAYMENT METHOD & SENDS PAYMENT PAYLOAD client->>server: GET /api server->>server: Checks the request includes PAYMENT-SIGNATURE header server->>client: HTTP 402 + PAYMENT-REQUIRED header note over client: Select payment method client->>client: Local signing of the Payment Payload
UserOperations order + burn client->>server: Request + PAYMENT-SIGNATURE header %%2) VERIFICATION activate server server->>facilitator: /verify (paymentRequired + paymentPayload + maxAmount) facilitator->>facilitator: check UserOperations include "burn" permissions alt no "burn" permissions facilitator-->>server: ERROR: Payment verification failed
(no "burn" permissions) end note over facilitator: "burn" permissions present facilitator->>blockchain: get Client balance blockchain-->>facilitator: Client balance activate facilitator alt not enough balance alt "order" permission NOT present facilitator-->>server: ERROR: Payment verification failed
(invalid UserOperation) end facilitator->>blockchain: Verify "order" UserOperation can be executed blockchain-->>facilitator: UserOperation verification result alt "order" UserOperation can be executed facilitator-->>server: OK: correct verification else "order" UserOperation can NOT be executed facilitator-->>server: ERROR: Invalid UserOperation. Can NOT order end else enough balance facilitator->>blockchain: Verify "burn" UserOperation can be executed blockchain-->>facilitator: UserOperation verification result alt "burn" UserOperation can be executed facilitator-->>server: OK: correct verification else "burn" UserOperation can NOT be executed facilitator-->>server: ERROR: Invalid UserOperation. Can NOT burn end end deactivate facilitator note over server: Check verification result alt verification was invalid server-->>client: HTTP 402 + PAYMENT-REQUIRED header end %%3) SETTLEMENT server->server: Server does the work to fulfill the client request server->>facilitator: /settle (paymentRequired + paymentPayload + maxAmount) activate facilitator facilitator->>blockchain: get Client balance blockchain-->>facilitator: Client balance alt not enough balance facilitator->>blockchain: Execute "order" UserOperation blockchain-->>facilitator: "order" tx end facilitator->>blockchain: execute "burn" UserOperation blockchain-->>facilitator: execution result alt "burn" OR "order" UserOperation failed facilitator-->>server: Settlement failed
(invalid UserOperation) server-->>client: HTTP 402 + PAYMENT-REQUIRED header else "burn" UserOperation executed facilitator-->>server: Settlement OK
(txHash, creditsBurned) end deactivate facilitator server-->>client: Response + PAYMENT-RESPONSE header deactivate server ``` ### 4.2 Step-by-Step Flow The following steps detail the complete payment and execution flow. Steps are grouped into three phases: **Initial Request**, **Verification**, and **Settlement**. *** #### Phase 1: Initial Request (Steps 1-5) **Step 1.** Client initiates an HTTP request to a Server (AI Agent). ```http theme={null} GET /api/resource HTTP/1.1 Host: agent.example.com ``` **Step 2.** Server validates whether the request contains a valid payment in the `PAYMENT-SIGNATURE` header. **Step 3.** If payment is absent, Server responds with `HTTP 402 Payment Required` status and the `PaymentRequired` object in the `PAYMENT-REQUIRED` header (base64-encoded). ```http theme={null} HTTP/1.1 402 Payment Required PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQYXltZW50IHJlcXVpcmVkLi4u ``` **Decoded PAYMENT-REQUIRED:** ```json theme={null} { "x402Version": 2, "error": "Payment required to access resource", "resource": { "url": "/api/resource", "description": "Protected resource", "mimeType": "application/json" }, "accepts": [{ "scheme": "nvm:erc4337", "network": "eip155:84532", "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015" } }], "extensions": {} } ``` **Step 4.** The Client: * Selects one of the `accepts` schemes returned by the server response * Creates a PaymentPayload with the selected scheme in `accepted` * Creates and signs locally a `UserOperation` representing the Payment Intent (using ERC-4337 and EIP-712) **Step 5.** Client sends an HTTP request to the server including the x402 Payment Payload and the signed `UserOperation`s in the `PAYMENT-SIGNATURE` HTTP header. ```http theme={null} GET /api/resource HTTP/1.1 Host: agent.example.com PAYMENT-SIGNATURE: ``` *** #### Phase 2: Verification (Steps 6-20) **Step 6.** Server validates the incoming data and forwards the payment data to the facilitator to verify the Client request. ```http theme={null} POST /verify HTTP/1.1 Host: facilitator.example.com Content-Type: application/json { "paymentRequired": { ... }, "paymentPayload": "", "maxAmount": "5" } ``` The facilitator extracts `planId`, `subscriberAddress` (from `payload.authorization.from`), and `agentId` from the token. **Step 7.** Facilitator checks the request and confirms if it includes "redeem" permissions. **Step 8.** IF the request DOES NOT include "redeem" permissions: * Facilitator rejects the request and returns an error to the server * Server returns to the client a `HTTP 402` response with `PAYMENT-REQUIRED` header **Step 9.** IF the request INCLUDES "redeem" permissions, the Facilitator queries the blockchain to check the Client balance. **Step 10.** Blockchain returns the Client balance to the Facilitator. **Step 11.** Facilitator checks if the "order" UserOperation is included. IF the "order" is NOT included AND the client has insufficient balance: * Facilitator rejects the request and returns an error to the server * Server returns to the client a `HTTP 402 PAYMENT-FAILED` response **Step 12.** IF the "order" UserOperation is included, the Facilitator verifies if the "order" UserOperation can be executed. **Step 13.** Blockchain returns the order verification result to the Facilitator. **Step 14.** IF the "order" UserOperation verification is correct, the Facilitator confirms to the server that the verification is correct. **Step 15.** IF the "order" UserOperation verification is INVALID, the Facilitator returns an error to the server. **Step 16.** IF the Client has enough balance, the Facilitator verifies if the "redeem" UserOperation can be executed. **Step 17.** The Blockchain returns the redeem verification result to the Facilitator. **Step 18.** IF the "redeem" UserOperation verification is correct, the Facilitator confirms to the server that the verification is correct. **Step 19.** IF the "redeem" UserOperation verification is INVALID, the Facilitator returns an error to the server. **Step 20.** The Server, after obtaining the verification result from the Facilitator (and BEFORE executing any task), checks the verification result: * If the verification was INVALID, the Server returns to the client a `HTTP 402 PAYMENT-FAILED` response * If the verification was correct, the Server continues with the request execution The verification phase ensures that the payment CAN be settled before the server performs any work. This protects the server from executing expensive operations without guaranteed payment. *** #### Phase 3: Settlement (Steps 21-32) **Step 21.** The Server executes the task to fulfill the client request (AI Task or any necessary work). **Step 22.** The Server calls the `/settle` endpoint of the Facilitator to settle the request. ```http theme={null} POST /settle HTTP/1.1 Host: facilitator.example.com Content-Type: application/json { "paymentRequired": { ... }, "paymentPayload": "", "maxAmount": "5" } ``` The `maxAmount` specifies the actual credits to redeem (can be less than or equal to the verified amount). **Step 23.** Facilitator queries the blockchain to check the Client balance. **Step 24.** Blockchain returns the Client balance to the Facilitator. **Step 25.** IF the Client does NOT have enough balance, the Facilitator executes the "order" UserOperation on behalf of the Client. **Step 26.** Blockchain returns the "order" transaction to the Facilitator. **Step 27.** Facilitator executes the "redeem" UserOperation on behalf of the Client. **Step 28.** Blockchain returns the execution result to the Facilitator. **Step 29.** IF the "redeem" OR "order" UserOperations execution FAILED, the Facilitator rejects the request and returns an error to the server. **Step 30.** The Server returns to the client a `HTTP 402 PAYMENT-FAILED` response. **Step 31.** IF the "redeem" UserOperation executed successfully, the Facilitator confirms to the server that the request is verified (including the payment tx / order tx). **Step 32.** Server returns to the client the response with the transaction payment confirmation in the `PAYMENT-RESPONSE` header (base64-encoded). ```http theme={null} HTTP/1.1 200 OK Content-Type: application/json PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbkhhc2giOi4uLg== { "result": "...", "payment": { "creditsBurned": "5", "remainingBalance": "95" } } ``` **Decoded PAYMENT-RESPONSE:** ```json theme={null} { "success": true, "transactionHash": "0x...", "network": "eip155:84532" } ``` The `PAYMENT-RESPONSE` header contains x402-standard settlement fields. Additional Nevermined-specific info (like `creditsBurned`) can be included in the response body. Steps 29-30 represent a failure scenario where the server has already performed work but settlement failed. Implementations SHOULD have mechanisms to handle this edge case, such as retry logic or dispute resolution. ### 4.3 HTTP Header Summary Per [x402 HTTP Transport v2](https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md), all payment data is transmitted via HTTP headers using base64 encoding: | Header | Direction | Content | | ------------------- | --------------------- | --------------------------------------- | | `PAYMENT-REQUIRED` | Server → Client (402) | Base64-encoded `PaymentRequired` object | | `PAYMENT-SIGNATURE` | Client → Server | Base64-encoded `PaymentPayload` object | | `PAYMENT-RESPONSE` | Server → Client (200) | Base64-encoded settlement receipt | All three headers use **base64 encoding**. The response body contains the actual resource data plus optional Nevermined-specific payment info. ## 5. Verification Requirements ### 5.1 Payload Validation The facilitator MUST verify: | Check | Requirement | | ------------ | ------------------------------------------------------------------------------- | | Version | `x402Version` MUST equal `2` | | Scheme | `scheme` MUST equal `"nvm:erc4337"` | | Network | `network` MUST be a supported network using CAIP-2 format | | Subscriber | `payload.authorization.from` MUST be a valid ERC-4337 smart account address | | Signature | `payload.signature` MUST be a valid EIP-712 signature | | Session Keys | At least one session key MUST be present in `payload.authorization.sessionKeys` | ### 5.2 Session Key Validation For each session key, the facilitator MUST verify: | Check | Requirement | | ----------- | --------------------------------------------------------------- | | Structure | Session key MUST contain valid `id` and either `data` or `hash` | | Validity | Session key MUST not be expired | | Permissions | Session key MUST grant the required permissions | | Scope | Session key MUST be scoped to the correct smart contracts | ### 5.3 Balance and Permission Checks The facilitator MUST verify: | Check | Requirement | | ----------------- | --------------------------------------------------------------- | | Redeem Permission | A `redeem` session key MUST be present | | Balance | Client MUST have sufficient balance OR valid `order` permission | | UserOperation | All UserOperations MUST pass on-chain simulation | ## 6. Settlement Requirements ### 6.1 Execution Order When executing settlement, the facilitator MUST: 1. Check client balance 2. If balance insufficient, execute `order` UserOperation first 3. Wait for `order` transaction confirmation 4. Execute `redeem` UserOperation 5. Return transaction receipts ### 6.2 Atomicity The facilitator SHOULD ensure that partial settlement does not leave the system in an inconsistent state. If the `order` succeeds but `redeem` fails, the facilitator MUST handle the error appropriately. ### 6.3 Receipt Format The facilitator MUST return a receipt. The `PAYMENT-RESPONSE` header contains x402-standard fields: | Field | Type | Description | | ----------------- | --------- | --------------------------------------------------------- | | `success` | `boolean` | Whether settlement succeeded. | | `transactionHash` | `string` | Transaction hash of the redeem operation. | | `network` | `string` | Network where transactions were executed (CAIP-2 format). | Additional settlement details MAY be included in the response body: | Field | Type | Description | | ------------------ | -------- | ------------------------------------------------------ | | `creditsRedeemed` | `string` | Number of credits redeemed. | | `remainingBalance` | `string` | Subscriber's remaining balance. | | `orderTx` | `string` | Transaction hash of the order operation (if executed). | ## 7. Error Handling ### 7.1 Error Codes | Code | Name | Description | | ------------------------ | --------------------- | ------------------------------------------------- | | `INVALID_PAYLOAD` | Invalid Payload | The payment payload structure is invalid | | `INVALID_SIGNATURE` | Invalid Signature | The EIP-712 signature verification failed | | `INSUFFICIENT_BALANCE` | Insufficient Balance | Client lacks balance and valid `order` permission | | `INVALID_USER_OPERATION` | Invalid UserOperation | UserOperation failed on-chain simulation | | `EXPIRED_SESSION_KEY` | Expired Session Key | Session key has expired | | `SETTLEMENT_FAILED` | Settlement Failed | On-chain settlement transaction failed | | `UNSUPPORTED_NETWORK` | Unsupported Network | The specified network is not supported | ### 7.2 Error Response Format ```json theme={null} { "error": { "code": "INSUFFICIENT_BALANCE", "message": "Client balance is 0, no order permission provided", "details": { "clientAddress": "0x...", "requiredBalance": "1000000", "currentBalance": "0" } } } ``` ## 8. Security Considerations ### 8.1 Signature Security * The EIP-712 signature MUST be verified against the client's smart account address * Replay protection SHOULD be implemented using nonces or timestamps * The facilitator MUST NOT execute UserOperations with invalid signatures ### 8.2 Session Key Security * Session keys SHOULD have limited validity periods * Session keys SHOULD be scoped to specific contracts and methods * Session keys SHOULD include spending limits where applicable * Session keys MUST be revocable by the smart account owner ### 8.3 Facilitator Trust Model Clients delegate execution authority to the facilitator. This trust model assumes: * The facilitator will only execute operations after successful verification * The facilitator will only execute operations after the server completes its work * The facilitator acts as an honest intermediary ### 8.4 Network Considerations * All network identifiers MUST be validated against a known allowlist * Cross-network attacks MUST be prevented by including the network in signed data ## 9. Implementation Notes ### 9.1 ERC-4337 Compatibility This extension is designed to work with any ERC-4337 compliant smart account implementation. The `provider` field in the authorization object indicates which session key format is used. ### 9.2 Session Key Providers Common session key providers and their formats: | Provider | Description | | ---------- | ----------------------------------- | | `zerodev` | ZeroDev Kernel session keys | | `biconomy` | Biconomy smart account session keys | | `safe` | Safe session modules | Implementations SHOULD document which providers they support. ### 9.3 Gas and Fees The facilitator acts as the paymaster for UserOperation execution. Settlement economics (gas fees, facilitator fees) are outside the scope of this specification. ## 10. References * [x402 Protocol](https://x402.org/) * [ERC-4337: Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337) * [EIP-712: Typed Structured Data Hashing and Signing](https://eips.ethereum.org/EIPS/eip-712) * [EIP-3009: Transfer With Authorization](https://eips.ethereum.org/EIPS/eip-3009) * [RFC 2119: Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119)