# 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 Agent Plans Get the list of plans that can be ordered to access an agent: ```bash theme={null} nvm agents get-agent-plans # With pagination nvm agents get-agent-plans --pagination '{"page": 1, "offset": 10}' # JSON output nvm agents get-agent-plans --format json ``` ## 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 "did:nvm:abc123" ``` Output includes: * Agent metadata (name, description, creator) * API endpoint configuration * Associated payment plans ## Registering Agents ### Register Agent with Existing Plans Register a new AI agent and associate it with one or more existing payment plans: ```bash theme={null} nvm agents register-agent \ --agent-metadata agent-metadata.json \ --agent-api "https://api.example.com/v1/agent" \ --payment-plans "plan-id-1,plan-id-2" ``` **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" } } ``` ### Register Agent and Plan Together Register a new AI agent and create a payment plan for it in a single command: ```bash theme={null} nvm agents register-agent-and-plan \ --agent-metadata agent-metadata.json \ --agent-api "https://api.example.com/v1/agent" \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` Optional flags: * `--access-limit` — Limit the number of times the plan can be ordered **plan-metadata.json**: ```json theme={null} { "name": "My Agent - Basic Plan", "description": "100 credits for API access", "tags": ["ai", "assistant", "basic"] } ``` **price-config.json**: ```json theme={null} { "tokenAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "price": 1000000, "amountOfCredits": 100 } ``` **credits-config.json**: ```json theme={null} { "subscriptionType": "credits", "accessType": "credits", "minCreditsToCharge": 1, "maxCreditsToCharge": 10 } ``` ## Updating Agents ### Update Agent Metadata Modify agent name, description, API endpoint, or other metadata: ```bash theme={null} nvm agents update-agent-metadata \ --agent-metadata updated-metadata.json \ --agent-api "https://api-v2.example.com/agent" ``` Both `--agent-metadata` and `--agent-api` are required. **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"] } ``` ## Managing Payment Plans ### Add Payment Plan to Agent Associate an existing payment plan with an agent: ```bash theme={null} nvm agents add-plan-to-agent --agent-id ``` Example: ```bash theme={null} nvm agents add-plan-to-agent "did:nvm:plan123" --agent-id "did:nvm:agent456" ``` ### Remove Payment Plan from Agent Disassociate a payment plan from an agent: ```bash theme={null} nvm agents remove-plan-from-agent --agent-id ``` Example: ```bash theme={null} nvm agents remove-plan-from-agent "did:nvm:plan123" --agent-id "did:nvm:agent456" ``` ### List Plans Associated with Agent See which plans give access to an agent: ```bash theme={null} nvm agents get-agent-plans ``` ## 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 \ --agent-api "https://api.example.com/agent" # 4. Verify agent is accessible nvm agents get-agent $AGENT_ID echo "Agent setup complete!" ``` ### Example 2: One-Step Agent and Plan Registration Register an agent and its payment plan in a single command: ```bash theme={null} nvm agents register-agent-and-plan \ --agent-metadata agent.json \ --agent-api "https://api.example.com/agent" \ --plan-metadata plan.json \ --price-config price.json \ --credits-config credits.json ``` ### Example 3: 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" ``` ## JSON Output for Automation Use `--format json` to integrate with other tools: ```bash theme={null} # Get agent data AGENT=$(nvm agents get-agent "did:nvm:agent123" --format json) # Extract fields NAME=$(echo $AGENT | jq -r '.name') echo "Agent: $NAME" ``` ## 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", "version": "1.2.0", "author": "YourCompany", "tags": ["code", "ai", "programming", "debugging"] } ``` ### 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 ### 3. Test Before Production Always test agents in sandbox environment: ```bash theme={null} # Register in sandbox nvm --profile sandbox agents register-agent \ --agent-metadata agent.json \ --agent-api "https://staging-api.example.com" \ --payment-plans "$STAGING_PLAN_ID" # Test the agent nvm --profile sandbox 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" ``` ## Common Issues ### "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 get-plans # 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 To interact with the Nevermined API, you need an API key. Open [nevermined.app](https://nevermined.app), sign in, then go to **Settings > Global NVM API Keys** and click **+ New API Key**. ## 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 ``` ## Authentication ### Browser Login (Recommended) The quickest way to authenticate: ```bash theme={null} nvm login ``` This opens your browser to sign in with Google, X, or email. After login, your API key is automatically captured and saved to your CLI config. Options: * `--environment ` — Target environment (default: sandbox) * `--profile ` — Config profile to save to (default: default) * `--no-browser` — Print the login URL instead of opening the browser Examples: ```bash theme={null} nvm login --environment live nvm login --profile production --environment live nvm login --no-browser ``` ### Logout Remove your API key from the CLI config: ```bash theme={null} nvm logout nvm logout --profile production nvm logout --all-profiles ``` ## 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` - Testing (recommended for learning and development) * `live` - Production environment Example: ```bash theme={null} $ nvm config init ? Enter your NVM API Key: sandbox: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 get-plans ``` ### 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 get-plans ``` 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 get-plans ``` 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} # Browser login (recommended) nvm login # Or initialize configuration manually 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 ### Login Authenticate via browser login. Opens your browser, waits for you to sign in, and saves the API key to your CLI config: ```bash theme={null} nvm login nvm login --environment live nvm login --profile production --environment live nvm login --no-browser # Print URL instead of opening browser ``` ### Logout Remove your API key from the local configuration so you need to authenticate again: ```bash theme={null} nvm logout nvm logout --profile production nvm logout --all-profiles # Remove API keys from every profile ``` ### 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) Flags: * `--api-key` — Provide API key non-interactively * `--environment` — Set environment (sandbox, live, staging\_sandbox, staging\_live) * `-i, --interactive` — Interactive mode (default: true) ### View Configuration Display your current configuration: ```bash theme={null} nvm config show # Show all profiles nvm config show --all ``` 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 ``` ### Configuration File Structure The config file at `~/.config/nvm/config.json`: ```json theme={null} { "profiles": { "default": { "nvmApiKey": "sandbox:eyJxxxxaaaa...bbbbbbbb", "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 subscriber has permission to use credits from a payment plan. This simulates credit usage without actually burning credits: ```bash theme={null} nvm facilitator verify-permissions \ --params verify.json ``` **verify.json**: ```json theme={null} { "paymentRequired": "eyJhbGci...", "x402AccessToken": "eyJhbGci...", "maxAmount": "5" } ``` ### Settle Permissions Settle (burn) credits from a subscriber's payment plan. This executes the actual credit consumption: ```bash theme={null} nvm facilitator settle-permissions \ --params settle.json ``` **settle.json**: ```json theme={null} { "paymentRequired": "eyJhbGci...", "x402AccessToken": "eyJhbGci...", "maxAmount": "5" } ``` ## Organizations Commands Manage organization members and settings. ### List Members View all members in your organization: ```bash theme={null} nvm organizations get-members # Filter by role nvm organizations get-members --role admin # Filter active members nvm organizations get-members --is-active true # Pagination nvm organizations get-members --page 1 --offset 10 ``` ### Create Member Add a new member to your organization: ```bash theme={null} nvm organizations create-member # With email and role nvm organizations create-member --user-email "john@example.com" --user-role "developer" ``` Flags: * `--user-email` — Member's email address (optional) * `--user-role` — Member's role (optional) ### Connect Stripe Account Connect a user with Stripe for fiat payments: ```bash theme={null} nvm organizations connect-stripe-account \ --user-email "john@example.com" \ --user-country-code "US" \ --return-url "https://yourapp.com/stripe-callback" ``` All flags are required: * `--user-email` — User's email address * `--user-country-code` — Two-letter country code * `--return-url` — URL to redirect after Stripe connection ## X402 Token Commands ### Get X402 Access Token Generate an access token for a plan with delegated permissions: ```bash theme={null} nvm x402token get-x402-access-token ``` Example: ```bash theme={null} nvm x402token get-x402-access-token "did:nvm:abc123" ``` Optional flags: * `--agent-id` — Target agent ID * `--redemption-limit` — Maximum credits that can be redeemed * `--order-limit` — Maximum number of orders * `--expiration` — Token expiration time Example with options: ```bash theme={null} nvm x402token get-x402-access-token "did:nvm:abc123" \ --agent-id "did:nvm:agent456" \ --redemption-limit 100 \ --expiration 3600 ``` Output: ``` X402 Access Token Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Plan ID: did:nvm:abc123 ``` ### Save Token Save token for later use: ```bash theme={null} # Save to environment variable export X402_TOKEN=$(nvm x402token get-x402-access-token "did:nvm:abc123" \ --format json | jq -r '.token') # Use token in requests curl -H "payment-signature: $X402_TOKEN" https://agent-api.example.com ``` ## Global Flags All commands support these global flags: ### Format Flag Control output format: ```bash theme={null} # Table output (default) nvm plans get-plans # JSON output nvm plans get-plans --format json # Quiet output (minimal) nvm plans get-plans --format quiet ``` ### Profile Flag Use a specific configuration profile: ```bash theme={null} # Use production profile nvm --profile production plans get-plans # Use staging profile nvm --profile staging agents get-agent ``` ### Verbose Flag Enable verbose output with detailed logging: ```bash theme={null} nvm plans order-plan "did:nvm:abc123" --verbose ``` Output includes: * Request/response details * 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 case $1 in production) nvm config set activeProfile production echo "Switched to production environment" ;; sandbox) nvm config set activeProfile default echo "Switched to sandbox environment" ;; *) echo "Usage: $0 {production|sandbox}" exit 1 ;; esac nvm config show ``` ### Multi-Profile Operations Run commands across multiple profiles: ```bash theme={null} #!/bin/bash PROFILES=("default" "production") COMMAND="$@" for PROFILE in "${PROFILES[@]}"; do echo "Profile: $PROFILE" nvm --profile $PROFILE $COMMAND echo "" done ``` Usage: ```bash theme={null} ./multi-profile.sh plans get-plans ``` ## 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 }}" ``` ## Common Issues ### "Config file not found" Initialize configuration: ```bash theme={null} nvm login # or nvm config init ``` ### "Invalid profile" Check available profiles: ```bash theme={null} nvm config show --all ``` ### "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 * **Trial plans**: One-time-use plans for testing (credits or time limited) ## Listing Plans View all available payment plans: ```bash theme={null} # Table output (default) nvm plans get-plans # JSON output for scripting nvm plans get-plans --format json # With pagination and sorting nvm plans get-plans --page 1 --offset 10 --sort-by name --sort-order asc ``` ## 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 "did:nvm:abc123" ``` Output includes: * Plan metadata (name, description, creator) * Pricing configuration * Credits configuration * 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..." ``` ## Getting Agents for a Plan List all agents accessible through a specific plan: ```bash theme={null} nvm plans get-agents-associated-to-a-plan # With pagination nvm plans get-agents-associated-to-a-plan --pagination '{"page": 1, "offset": 10}' ``` ## Creating Plans ### Flexible Plan Registration Register a plan with full control over price and credits/duration configuration: ```bash theme={null} nvm plans register-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` Optional flags: * `--nonce` — Custom nonce value * `--access-limit` — Maximum number of times the plan can be ordered ### 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", "minCreditsToCharge": 1, "maxCreditsToCharge": 10 } ``` ### Time-Based Plan Create a time-based plan with time-limited access: ```bash theme={null} nvm plans register-time-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` **credits-config.json** (for time plan): ```json theme={null} { "subscriptionType": "time", "accessType": "time", "duration": 2592000 } ``` Duration is in seconds (2592000 = 30 days). ### Trial Plans Trial plans can only be purchased once per user and are useful for letting users test your agents. **Credits trial** (limited by credits): ```bash theme={null} nvm plans register-credits-trial-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` **Time trial** (limited by duration): ```bash theme={null} nvm plans register-time-trial-plan \ --plan-metadata plan-metadata.json \ --price-config price-config.json \ --credits-config credits-config.json ``` ## Ordering Plans ### Crypto Payment Purchase a plan with cryptocurrency: ```bash theme={null} nvm plans order-plan ``` ### Fiat Payment Initiate a plan purchase with fiat payment. Returns a URL where the user can complete the payment: ```bash theme={null} nvm plans order-fiat-plan ``` ## Minting Credits ### Mint Credits Add credits to a plan and transfer them to a receiver (plan owner only): ```bash theme={null} nvm plans mint-plan-credits \ --credits-amount 1000 \ --credits-receiver "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" ``` ### Mint Expirable Credits Add time-limited credits: ```bash theme={null} nvm plans mint-plan-expirable \ --credits-amount 1000 \ --credits-receiver "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \ --credits-duration 2592000 ``` The `--credits-duration` flag is optional and specifies duration in seconds. ## Redeeming Credits Burn/redeem credits for a given payment plan: ```bash theme={null} nvm plans redeem-credits \ --plan-id \ --credits-amount-to-redeem 5 \ --redeem-from "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" ``` ## Price Configuration Helpers The CLI provides helper commands to build price configuration objects: ```bash theme={null} # Free (no payment required) nvm plans get-free-price-config # Fiat price nvm plans get-fiat-price-config --amount 1000 --receiver "0x123..." # Crypto price nvm plans get-crypto-price-config --amount 1000 --receiver "0x123..." # Crypto with specific token nvm plans get-crypto-price-config --amount 1000 --receiver "0x123..." --token-address "0xToken..." # Native token price nvm plans get-native-token-price-config --amount 1000 --receiver "0x123..." # ERC20 token price nvm plans get-erc20-price-config --amount 1000 --receiver "0x123..." --token-address "0xToken..." # Pay-as-you-go price nvm plans get-pay-as-you-go-price-config --amount 1000 --receiver "0x123..." ``` ## Credits Configuration Helpers Helper commands to build credits configuration objects: ```bash theme={null} # Fixed credits (same amount per request) nvm plans get-fixed-credits-config --credits-granted 100 --credits-per-request 1 # Dynamic credits (range per request) nvm plans get-dynamic-credits-config --credits-granted 100 \ --min-credits-per-request 1 --max-credits-per-request 10 # Pay-as-you-go credits nvm plans get-pay-as-you-go-credits-config # Expirable duration nvm plans get-expirable-duration-config --duration-of-plan 2592000 # Non-expirable nvm plans get-non-expirable-duration-config ``` ## Advanced Operations ### Set Onchain Mirror Mark whether burns of these credits are mirrored on-chain: ```bash theme={null} nvm plans set-onchain-mirror \ --credits-config credits-config.json \ --onchain-mirror ``` ### Set Redemption Type Set the redemption type in a credits configuration: ```bash theme={null} nvm plans set-redemption-type \ --credits-config credits-config.json \ --redemption-type "fixed" ``` ## 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 "did:nvm:abc123" --format json) # Extract specific field with jq PLAN_NAME=$(echo $PLAN_DATA | jq -r '.name') echo "Plan: $PLAN_NAME" ``` ## 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", "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: Monitor Plan Balance ```bash theme={null} #!/bin/bash PLAN_ID="did:nvm:abc123" 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" 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} { "minCreditsToCharge": 1, "maxCreditsToCharge": 10 } ``` ### 3. Test in Sandbox First Always test new plans in sandbox before going live: ```bash theme={null} # Test in sandbox nvm --profile sandbox plans register-credits-plan \ --plan-metadata metadata.json \ --price-config price.json \ --credits-config credits.json # Verify it works nvm --profile sandbox plans get-plan ``` ## Common Issues ### "Insufficient balance" When registering plans, ensure you have enough credits or tokens in your account. ### "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 sandbox 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 credits with the Nevermined CLI # Making Purchases Complete guide to ordering payment plans and managing credits 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 ### Crypto Payment Purchase a payment plan with cryptocurrency: ```bash theme={null} nvm plans order-plan ``` Example: ```bash theme={null} nvm plans order-plan "did:nvm:abc123" ``` Output: ``` Plan Order Success Plan ID: did:nvm:abc123 Credits Purchased: 100 Transaction Hash: 0x1234567890abcdef... Status: Confirmed ``` ### Fiat Payment Initiate a plan purchase with fiat. This returns a URL where you can complete the payment: ```bash theme={null} nvm plans order-fiat-plan ``` Example: ```bash theme={null} nvm plans order-fiat-plan "did:nvm:abc123" ``` ## 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: did:nvm:abc123 Credits Remaining: 75 ``` ### Monitor Multiple Plans Check balances for all your plans: ```bash theme={null} #!/bin/bash PLANS=("did:nvm:plan1" "did:nvm:plan2" "did:nvm:plan3") for PLAN in "${PLANS[@]}"; do echo "Checking balance for $PLAN..." nvm plans get-plan-balance $PLAN echo "" done ``` ## Refilling Credits ### Purchase Additional Credits Re-order a plan to add more credits to your existing balance: ```bash theme={null} nvm plans order-plan ``` ### Bulk Credit Purchase Order multiple plans at once: ```bash theme={null} #!/bin/bash PLANS=("did:nvm:plan1" "did:nvm:plan2") for PLAN in "${PLANS[@]}"; do echo "Ordering $PLAN..." nvm plans order-plan $PLAN done ``` ## Credit Usage ### Set Up Balance Monitoring Create a script to alert when credits run low: ```bash theme={null} #!/bin/bash PLAN_ID="did:nvm:abc123" MIN_CREDITS=10 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" fi ``` ### Auto-Refill Script Automatically refill credits when low: ```bash theme={null} #!/bin/bash # Run as cron job: 0 */6 * * * /path/to/auto-refill.sh PLAN_ID="did:nvm:abc123" MIN_CREDITS=20 BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') if [ "$BALANCE" -lt "$MIN_CREDITS" ]; then echo "$(date): Credits low ($BALANCE), ordering more..." nvm plans order-plan $PLAN_ID if [ $? -eq 0 ]; then 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" fi else echo "$(date): Balance healthy: $BALANCE credits" fi ``` ## Examples ### Example 1: Complete Purchase Flow ```bash theme={null} #!/bin/bash # 1. Browse available plans echo "Available Plans:" nvm plans get-plans # 2. Get details about a specific plan PLAN_ID="did:nvm:abc123" 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: Team Credit Management ```bash theme={null} #!/bin/bash TEAM_PLANS=("did:nvm:plan1" "did:nvm:plan2" "did:nvm:plan3") # 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 Balances:" for PLAN in "${TEAM_PLANS[@]}"; do nvm plans get-plan-balance $PLAN --format json | jq '{plan: .planId, remaining: .balance}' 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. Use JSON Output for Scripting Always use `--format json` in scripts: ```bash theme={null} BALANCE=$(nvm plans get-plan-balance $PLAN_ID --format json | jq -r '.balance') ``` ## Common Issues ### "Insufficient funds" Ensure your wallet has enough balance for crypto payments. ### "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 get-plans ``` ## 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 and the payment-signature header 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 "did:nvm:abc123" ``` Optional flags: * `--agent-id` — Target agent ID * `--redemption-limit` — Maximum credits that can be redeemed * `--order-limit` — Maximum number of orders * `--expiration` — Token expiration time Example with options: ```bash theme={null} nvm x402token get-x402-access-token "did:nvm:abc123" \ --agent-id "did:nvm:agent456" \ --redemption-limit 100 \ --expiration 3600 ``` Output: ``` X402 Access Token Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Plan ID: did:nvm:abc123 Use this token in the payment-signature header: curl -H "payment-signature: 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 "did:nvm:abc123" --format json | jq -r '.token') echo $TOKEN > ~/.nvm-token # Use token from file curl -H "payment-signature: $(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 "did:nvm:abc123" --format json | jq -r '.token') # Make request with payment-signature header curl -X POST https://agent-api.example.com/v1/chat \ -H "Content-Type: application/json" \ -H "payment-signature: $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 = 'did:nvm:abc123' 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', 'payment-signature': 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 = 'did:nvm:abc123' 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', 'payment-signature': 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} { "paymentRequired": "eyJhbGci...", "x402AccessToken": "eyJhbGci...", "maxAmount": "5" } ``` ### Automatic Verification (SDK) For agent implementations, use the SDK for automatic verification: ```typescript theme={null} import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox' }) // Build payment requirements for your plan/agent const paymentRequired = buildPaymentRequired({ planId: 'did:nvm:abc123', endpoint: '/v1/chat', agentId: process.env.NVM_AGENT_ID!, httpVerb: 'POST' }) // Verify incoming request const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: req.headers['payment-signature'] as string, maxAmount: BigInt(5) }) if (!verification.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} { "paymentRequired": "eyJhbGci...", "x402AccessToken": "eyJhbGci...", "maxAmount": "5" } ``` ### Automatic Settlement (SDK) Integrate settlement into your agent: ```typescript theme={null} // After processing request await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: req.headers['payment-signature'] as string, maxAmount: BigInt(5) }) ``` ## Complete Query Workflow ### End-to-End Example Complete flow from purchase to query: ```bash theme={null} #!/bin/bash # Complete agent query workflow PLAN_ID="did:nvm:abc123" 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 "payment-signature: $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="did:nvm:abc123" 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 "payment-signature: $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="did:nvm:abc123" 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 "payment-signature: $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 "payment-signature: $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 ""did:nvm:abc123"" "Hello, agent!" ``` ## Monitoring Usage ### Track Credit Consumption Monitor how credits are used per query: ```bash theme={null} #!/bin/bash # Credit usage tracking PLAN_ID="did:nvm:abc123" 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 "payment-signature: $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 payment-signature header is included curl -H "payment-signature: $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 Open [nevermined.app](https://nevermined.app), sign in, then go to **Settings > Global NVM API Keys** and click **+ New API Key**. Copy the key and set it as an environment variable: ```bash theme={null} export NVM_API_KEY="sandbox:your-api-key-here" ``` Keep your API key secure. Do not commit it to version control or share it publicly. ## 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 Install and use the TypeScript Payments library. Install and use the Python Payments library. # API Reference Source: https://docs.nevermined.app/docs/api-reference/openclaw-plugin/commands All available tools and slash commands in the Nevermined OpenClaw plugin # API Reference The plugin provides slash commands for chat channels and tools that agents can invoke on behalf of users. Slash commands are triggered directly by users with the `/` prefix, while tools are called by the AI agent in response to natural language requests. ## Authentication Commands ### Log In — `/nvm_login` Authenticate with the Nevermined platform to enable all payment tools. The plugin supports three authentication flows: automatic browser login, environment selection, and direct API key input. When using browser login, the plugin starts a temporary local server, opens your default browser to the Nevermined login page, and captures the API key automatically after you sign in. On headless servers where no browser is available, paste your API key directly. | Parameter | Type | Required | Default | Description | | ------------- | ------ | -------- | --------- | ------------------- | | `environment` | string | No | `sandbox` | `sandbox` or `live` | > **Example prompt:** "Log me in to Nevermined" ``` /nvm_login /nvm_login live /nvm_login sandbox:eyJhbG... ``` *** ### Log Out — `/nvm_logout` End your Nevermined session and remove the stored API key from memory. After logging out, all payment tools will be unavailable until you authenticate again. Use this when switching accounts or when you want to revoke the current session for security. *** ## Subscriber Tools These tools are designed for users who consume paid AI agent services. They handle the full lifecycle of purchasing a plan, paying for access, and querying agents. ### Check Credit Balance — `nevermined_checkBalance` Retrieve the current credit balance for a payment plan. This is useful to verify how many credits remain before making requests, or to confirm that a recent purchase was credited to your account. The response includes the plan name, remaining balance, and whether you are an active subscriber. If a `planId` is configured in the plugin settings, it is used by default. You can override it per call. | Parameter | Type | Required | Default | Description | | --------- | ------ | -------- | --------------- | ---------------------------- | | `planId` | string | No | Config `planId` | The payment plan ID to check | **Example prompt:** > How many credits do I have left on my Weather Forecast plan? **Returns:** ```json theme={null} { "planId": "did:nv:abc123...", "planName": "Basic Plan", "balance": "95", "isSubscriber": true } ``` *** ### Get Access Token — `nevermined_getAccessToken` Generate an x402 access token that authorizes requests to a paid Nevermined agent. The token is cryptographically signed and contains delegated permissions (order, burn) that allow the agent to verify and settle payments on your behalf. This tool supports two payment schemes. **Crypto** (the default) uses the `nvm:erc4337` on-chain scheme with session keys and smart accounts. **Fiat** uses the `nvm:card-delegation` scheme, which charges a previously enrolled credit card via Stripe delegation. When using fiat, the plugin automatically looks up your enrolled payment methods and selects the first one unless you specify a `paymentMethodId`. Most users will not need to call this tool directly — `nevermined_queryAgent` handles token acquisition automatically. Use this tool when you need the raw token for custom integrations or debugging. | Parameter | Type | Required | Default | Description | | ------------------------ | ------ | -------- | ------------------------------------------------ | ------------------------------------------------------------------------ | | `planId` | string | No | Config `planId` | The payment plan ID | | `agentId` | string | No | Config `agentId` | The agent ID | | `paymentType` | string | No | Config `paymentType` or `"crypto"` | `"crypto"` (nvm:erc4337 scheme) or `"fiat"` (nvm:card-delegation scheme) | | `paymentMethodId` | string | No | Auto-selects first enrolled card | Stripe payment method ID (`pm_...`). Only used for fiat. | | `spendingLimitCents` | number | No | Config `defaultSpendingLimitCents` or `1000` | Max spend in cents. Only used for fiat. | | `delegationDurationSecs` | number | No | Config `defaultDelegationDurationSecs` or `3600` | Delegation duration in seconds. Only used for fiat. | **Example prompt:** > Get me an access token for the Weather Oracle agent so I can call it from my script. > **Example prompt:** "Get me an access token for the translation agent" **Returns:** ```json theme={null} { "accessToken": "eyJhbG..." } ``` *** ### Purchase a Crypto Plan — `nevermined_orderPlan` Purchase a payment plan using cryptocurrency. This initiates an on-chain transaction that transfers the plan's price to the builder's wallet and credits your account with the plan's allotted credits. The transaction is processed through the Nevermined Protocol smart contracts. Use this for plans priced in native tokens (ETH, MATIC) or ERC-20 tokens (USDC). For plans priced in fiat currency, use `nevermined_orderFiatPlan` instead. | Parameter | Type | Required | Default | Description | | --------- | ------ | -------- | --------------- | ----------------------- | | `planId` | string | No | Config `planId` | The plan ID to purchase | **Example prompt:** > Buy the Weather Forecast plan so I can start querying the agent. **Returns:** Order confirmation with transaction hash. *** ### Purchase a Fiat Plan — `nevermined_orderFiatPlan` Purchase a payment plan using fiat currency (USD). Instead of executing an on-chain transaction, this tool returns a Stripe checkout URL where you complete the payment in a browser. Once the payment is confirmed, credits are added to your account. Use this for plans that have been created with `pricingType: fiat`. For crypto-priced plans, use `nevermined_orderPlan` instead. | Parameter | Type | Required | Default | Description | | --------- | ------ | -------- | --------------- | ----------------------- | | `planId` | string | No | Config `planId` | The plan ID to purchase | **Example prompt:** > I want to purchase the Premium Agent plan using my credit card. **Returns:** ```json theme={null} { "result": { "checkoutUrl": "https://checkout.stripe.com/..." } } ``` *** ### List Payment Methods — `nevermined_listPaymentMethods` Retrieve the list of credit cards you have enrolled for fiat payments. Cards are enrolled through the [Nevermined App](https://nevermined.app) under Settings > Payment Methods. Each entry includes the card brand, last four digits, and expiration date. This is useful to verify which cards are available before making a fiat payment, or to find the `paymentMethodId` for a specific card when you have multiple cards enrolled. No parameters required. **Example prompt:** > Which credit cards do I have on file for Nevermined payments? **Returns:** ```json theme={null} [ { "id": "pm_...", "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2027 } ] ``` *** ### Query a Paid Agent — `nevermined_queryAgent` Send a prompt to a paid Nevermined AI agent and get the response. This is the main tool for interacting with agents — it handles the full payment lifecycle in a single call: 1. Acquires an x402 access token (crypto or fiat, depending on `paymentType`) 2. Sends the prompt to the agent's URL with the `PAYMENT-SIGNATURE` header 3. Returns the agent's response If the agent returns a 402 (Payment Required) response, the tool returns an error suggesting you purchase credits first. This typically means your plan has run out of credits or you haven't purchased a plan yet. Like `nevermined_getAccessToken`, this tool supports both crypto and fiat payment types. When using fiat, it automatically resolves your enrolled card and builds the delegation config. | Parameter | Type | Required | Default | Description | | ------------------------ | ------ | -------- | ------------------------------------------------ | ------------------------------------------------------------------------ | | `agentUrl` | string | **Yes** | — | The URL of the agent to query | | `prompt` | string | **Yes** | — | The prompt to send to the agent | | `planId` | string | No | Config `planId` | The payment plan ID | | `agentId` | string | No | Config `agentId` | The agent ID | | `method` | string | No | `POST` | HTTP method | | `paymentType` | string | No | Config `paymentType` or `"crypto"` | `"crypto"` (nvm:erc4337 scheme) or `"fiat"` (nvm:card-delegation scheme) | | `paymentMethodId` | string | No | Auto-selects first enrolled card | Stripe payment method ID (`pm_...`). Only used for fiat. | | `spendingLimitCents` | number | No | Config `defaultSpendingLimitCents` or `1000` | Max spend in cents. Only used for fiat. | | `delegationDurationSecs` | number | No | Config `defaultDelegationDurationSecs` or `3600` | Delegation duration in seconds. Only used for fiat. | **Example prompts:** > Ask the Weather Oracle at [https://weather.example.com/agent](https://weather.example.com/agent) what the forecast is for Barcelona. > Query the translation agent at [https://translate.example.com/tasks](https://translate.example.com/tasks) to translate "hello world" into French, and pay with my credit card. *** ## Builder Tools These tools are designed for agent builders who want to register their AI agents on the Nevermined marketplace and create payment plans that subscribers can purchase. ### Register an Agent — `nevermined_registerAgent` Register a new AI agent on Nevermined and create an associated payment plan in a single operation. This publishes your agent to the Nevermined marketplace, making it discoverable and purchasable by subscribers. The tool creates both the agent record (with its endpoint URL) and a payment plan (with pricing and credit allocation). The `pricingType` parameter controls how the plan is priced: `"crypto"` for native blockchain tokens, `"erc20"` for stablecoins like USDC, or `"fiat"` for USD pricing via Stripe. After registration, the returned `agentId` and `planId` should be saved in your plugin config for the paid endpoint to work. | Parameter | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------------------------------- | | `name` | string | **Yes** | Agent name | | `description` | string | No | Agent description | | `agentUrl` | string | **Yes** | The endpoint URL for the agent | | `planName` | string | **Yes** | Name for the payment plan | | `priceAmounts` | string | **Yes** | Comma-separated price amounts in wei (crypto) or cents (fiat) | | `priceReceivers` | string | **Yes** | Comma-separated receiver addresses | | `creditsAmount` | number | **Yes** | Number of credits in the plan | | `tokenAddress` | string | No | ERC20 token address (e.g. USDC). Omit for native token. | | `pricingType` | string | No | `"crypto"` (default), `"erc20"`, or `"fiat"` | **Example prompt:** > Register a new agent called "Code Review Bot" at [https://my-server.com/nevermined/agent](https://my-server.com/nevermined/agent) with a plan named "Code Review" priced at 1000000 (1 USDC) to address 0xABC... with token 0x036CbD... granting 10 credits. > **Example prompt:** "Register my translation agent hosted at [https://agent.example.com](https://agent.example.com) with a plan named 'Translation Plan' that costs 1000000 wei (1 USDC) sent to address 0x123... for 100 credits" **Returns:** ```json theme={null} { "agentId": "did:nv:...", "planId": "did:nv:...", "txHash": "0x..." } ``` *** ### Create a Payment Plan — `nevermined_createPlan` Create a standalone payment plan without associating it with an agent. This is useful when you want to manage plans separately from agents, or when a single plan should grant access to multiple agents. The plan can be priced in three ways: `"fiat"` sets the price in USD cents (e.g. `"100"` = \$1.00) and subscribers pay via Stripe, `"erc20"` sets the price in an ERC-20 token's smallest unit (e.g. `"1000000"` = 1 USDC) and requires a `tokenAddress`, and `"crypto"` (the default) sets the price in the blockchain's native token. | Parameter | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------------------------------------------------------------ | | `name` | string | **Yes** | Plan name | | `description` | string | No | Plan description | | `priceAmount` | string | **Yes** | Price in cents for fiat (e.g. "100" = \$1.00), in token smallest unit for crypto | | `receiver` | string | **Yes** | Receiver wallet address (0x...) | | `creditsAmount` | number | **Yes** | Number of credits in the plan | | `pricingType` | string | No | `"fiat"` for Stripe/USD, `"erc20"` for ERC20 tokens, `"crypto"` for native token (default) | | `accessLimit` | string | No | `"credits"` (default) or `"time"` | | `tokenAddress` | string | No | ERC20 token contract address. Required when pricingType is "erc20". | **Example prompt:** > Create a fiat payment plan called "Pro Tier" at \$5.00 (500 cents) to wallet 0xABC... with 50 credits. **Returns:** ```json theme={null} { "planId": "did:nv:..." } ``` *** ### List My Plans — `nevermined_listPlans` Retrieve all payment plans owned by the authenticated builder. This returns the full list of plans you have created, including their IDs, names, and configuration. Useful for finding a `planId` to use with other tools or to review your current offerings. No parameters required. **Example prompt:** > Show me all the payment plans I've created on Nevermined. **Returns:** Array of plan objects. # Getting Started Source: https://docs.nevermined.app/docs/api-reference/openclaw-plugin/getting-started Install and configure the Nevermined OpenClaw plugin in minutes # Getting Started The Nevermined OpenClaw plugin exposes AI agent payment operations as gateway tools callable from any OpenClaw channel — Telegram, Discord, WhatsApp, and more. ## Prerequisites * An [OpenClaw](https://openclaw.ai) gateway instance * Node.js >= 18.0.0 * A [Nevermined account](https://nevermined.app) (free to create) ## Installation Install the plugin from your OpenClaw gateway: ```bash theme={null} openclaw plugin install @nevermined-io/openclaw-plugin ``` ## Authentication To interact with the Nevermined API, you need an API key. Open [nevermined.app](https://nevermined.app), sign in, then go to **Settings > Global NVM API Keys** and click **+ New API Key**. ### Option A: Browser login (recommended) Use the `/nvm_login` command from any connected chat channel: ``` /nvm_login ``` This opens a browser window where you authenticate with Nevermined. The API key is captured automatically and stored in your gateway config. To target the live environment: ``` /nvm_login live ``` To use a specific API key: ``` /nvm_login sandbox:eyJhbG... ``` ### Option B: Manual configuration Add your API key directly to `openclaw.json`: ```json theme={null} { "plugins": { "entries": { "openclaw-plugin": { "enabled": true, "config": { "nvmApiKey": "sandbox:eyJhbG...", "environment": "sandbox" } } } } } ``` ## Quick Test After authenticating, verify the plugin is working: 1. **Check your balance** — from any chat channel, the agent can ask `Example prompt: “How many credits do I have left on my plan?”` to verify connectivity. 2. **List plans** — you can ask `Example prompt: Show me all my payment plans”` to see available payment plans. ## Fiat Payments The plugin also supports fiat payments via credit card delegation. Enroll a card at [nevermined.app](https://nevermined.app), then use `paymentType: fiat` with any token or query tool. See the [guide](./guide.md#fiat-payments-credit-card) for details. ## Next Steps * [Setup](/docs/api-reference/openclaw-plugin/setup) — full configuration reference * [Commands](/docs/api-reference/openclaw-plugin/commands) — all available tools and slash commands # Building a Paid AI Agent with OpenClaw and Nevermined Source: https://docs.nevermined.app/docs/api-reference/openclaw-plugin/guide Step-by-step guide to building, monetizing, and querying a paid AI agent using the Nevermined OpenClaw plugin # Building a Paid AI Agent with OpenClaw and Nevermined The Nevermined OpenClaw plugin lets you monetize AI agents directly from chat channels. Subscribers pay per-request using the [x402 payment protocol](https://nevermined.ai/docs/api-reference/typescript/x402-protocol), and builders collect revenue automatically through on-chain payment plans. This guide walks through building a **Weather Oracle** — a paid agent that serves weather forecasts and charges 1 credit per request. ## What You Get The plugin adds 9 payment tools and 2 slash commands to your OpenClaw gateway: **Subscriber tools** — for users who consume paid services: | Tool | Purpose | | ------------------------------- | -------------------------------------------------------------- | | `nevermined_checkBalance` | Check remaining credits on a plan | | `nevermined_getAccessToken` | Get an x402 token for authenticating requests (crypto or fiat) | | `nevermined_orderPlan` | Purchase a crypto payment plan | | `nevermined_orderFiatPlan` | Purchase a fiat payment plan (returns Stripe checkout URL) | | `nevermined_listPaymentMethods` | List enrolled credit cards for fiat payments | | `nevermined_queryAgent` | Send a paid query to an agent (crypto or fiat) | **Builder tools** — for developers who create paid services: | Tool | Purpose | | -------------------------- | ------------------------------------------------------ | | `nevermined_registerAgent` | Register an agent with a payment plan (crypto or fiat) | | `nevermined_createPlan` | Create a standalone payment plan | | `nevermined_listPlans` | List your payment plans | **Slash commands**: `/nvm_login` and `/nvm_logout` for authentication. **Paid HTTP endpoint**: An x402-compatible endpoint on the gateway that handles payment verification, request processing, and credit settlement automatically. ## Prerequisites * An [OpenClaw](https://openclaw.ai) gateway instance (v2026.2+) * Node.js >= 18 * A [Nevermined account](https://nevermined.app) with an API key ## Step 1: Install the Plugin From your OpenClaw gateway server: ```bash theme={null} openclaw plugin install @nevermined-io/openclaw-plugin ``` Or install manually by placing the package in `~/.openclaw/extensions/nevermined/`. After installation, restart the gateway: ```bash theme={null} openclaw gateway restart ``` You should see in the logs: ``` Registered 9 Nevermined payment tools ``` ## Step 2: Authenticate Send `/nvm_login` from any connected chat channel (Telegram, Discord, etc.). The plugin supports two flows: **Browser login** (if your server has a display): ``` /nvm_login ``` A browser window opens for Nevermined authentication. The API key is captured automatically. **Manual login** (headless servers): ``` /nvm_login sandbox:eyJhbGciOiJFUzI1NksifQ... ``` Paste your API key directly. Get one from [Nevermined App](https://nevermined.app) under Settings > API Keys. Use `sandbox` for testing, `live` for production. ## Step 3: Register Your Agent and Payment Plan Ask your Claw to register an agent. In any chat channel, send a message like: > Register a Nevermined agent called "Weather Oracle" at URL `https://my-gateway.example.com/nevermined/agent` with a plan named "Weather Forecast" priced at 1000000 (1 USDC) to address `0xYourWalletAddress` with token `0x036CbD53842c5426634e7929541eC2318f3dCF7e` granting 5 credits. The agent will call `nevermined_registerAgent` with these parameters: | Parameter | Value | Description | | ---------------- | ------------------------------------------------- | --------------------------------------------- | | `name` | Weather Oracle | Display name on Nevermined | | `agentUrl` | `https://my-gateway.example.com/nevermined/agent` | Public URL of your paid endpoint | | `planName` | Weather Forecast | Name for the payment plan | | `priceAmounts` | `1000000` | Price in token units (1 USDC = 1,000,000 wei) | | `priceReceivers` | `0xYourWalletAddress` | Your wallet that receives payments | | `creditsAmount` | `5` | Credits granted per purchase | | `tokenAddress` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | USDC on Base Sepolia. Omit for native token. | The tool returns the `agentId` and `planId` — save these for the next step. ## Step 4: Enable the Paid Endpoint Add the returned IDs and enable the paid endpoint in your gateway config (`~/.openclaw/openclaw.json`): ```json theme={null} { "plugins": { "entries": { "openclaw-plugin": { "enabled": true, "config": { "nvmApiKey": "sandbox:eyJhbG...", "environment": "sandbox", "planId": "", "agentId": "", "enablePaidEndpoint": true, "agentEndpointPath": "/nevermined/agent", "creditsPerRequest": 1 } } } } } ``` Restart the gateway. You should see: ``` Registered 9 Nevermined payment tools Registered paid endpoint at /nevermined/agent ``` The paid endpoint handles the full x402 lifecycle: 1. Extracts the `payment-signature` header from incoming requests 2. Calls `verifyPermissions` to check the subscriber has credits 3. Processes the request (runs your agent handler) 4. Calls `settlePermissions` to burn credits 5. Returns the response with a `payment-response` header ### Configuration Reference | Field | Required | Default | Description | | -------------------- | -------- | ------------------- | ----------------------------------------------- | | `nvmApiKey` | No | — | API key (set via `/nvm_login` or config) | | `environment` | No | `sandbox` | `sandbox` for testing, `live` for production | | `planId` | No | — | Default plan ID for tools and the paid endpoint | | `agentId` | No | — | Default agent ID | | `creditsPerRequest` | No | `1` | Credits burned per request | | `enablePaidEndpoint` | No | `false` | Enable the x402 paid HTTP endpoint | | `agentEndpointPath` | No | `/nevermined/agent` | Path for the paid endpoint | ## Step 5: Test as a Subscriber From the same gateway (or a different one with a subscriber API key), test the full flow: ### Order the plan > Order the Weather Oracle plan `` The Claw calls `nevermined_orderPlan`. You receive 5 credits. ### Check your balance > Check my Nevermined balance for plan `` Should show 5 credits. ### Query the agent > Ask the Weather Oracle at `http://localhost:18789/nevermined/agent` about the weather in Barcelona The Claw calls `nevermined_queryAgent`, which: 1. Acquires an x402 access token 2. Sends the prompt with a `PAYMENT-SIGNATURE` header 3. The paid endpoint verifies, processes, and settles 4. Returns the weather forecast ### Verify credit burn > Check my balance again Should show 4 credits — one was consumed. ## Fiat Payments (Credit Card) The plugin supports fiat payments via credit card delegation, allowing users who have enrolled a card through the [Nevermined App](https://nevermined.app) to pay for agent queries without cryptocurrency. ### 1. Enroll a card Users enroll a credit card via the Nevermined App under Settings > Payment Methods. ### 2. List enrolled cards > List my payment methods The Claw calls `nevermined_listPaymentMethods` and returns the enrolled cards with brand, last 4 digits, and expiration. ### 3. Order a fiat plan > Order the fiat plan `` The Claw calls `nevermined_orderFiatPlan`, which returns a Stripe checkout URL. The user completes payment in a browser. ### 4. Query an agent with fiat > Ask the Weather Oracle at `http://localhost:18789/nevermined/agent` about the weather in Paris, pay with my credit card The Claw calls `nevermined_queryAgent` with `paymentType: fiat`. The plugin: 1. Looks up enrolled payment methods (or uses the specified `paymentMethodId`) 2. Gets an x402 access token using the `nvm:card-delegation` scheme 3. Sends the request with the `PAYMENT-SIGNATURE` header 4. The agent verifies and settles as usual You can also set `paymentType: fiat` in the plugin config to make fiat the default for all calls. ## Custom Agent Handlers The plugin includes a mock weather handler for demonstration. To use your own logic, pass a custom `agentHandler` when registering the plugin: ```typescript theme={null} import neverminedPlugin from '@nevermined-io/openclaw-plugin' neverminedPlugin.register(api, { agentHandler: async (body) => { const response = await myAIModel.generate(body.prompt) return { result: response } }, }) ``` The handler receives `{ prompt: string }` and returns any JSON-serializable object. ## Credit Balance Awareness When the plugin is authenticated and a `planId` is configured, it automatically injects the current credit balance into the agent's context before each prompt. This means your Claw can proactively warn users when credits are running low: > "You have 2 credits remaining on the Weather Forecast plan. Consider ordering more credits." The balance is cached for 60 seconds to avoid excessive API calls. ## How It Works The plugin implements the [x402 payment protocol](https://nevermined.ai/docs/api-reference/typescript/x402-protocol) for agent-to-agent payments. It supports two payment schemes: * **Crypto** (`nvm:erc4337`) — on-chain payments using session keys and smart accounts * **Fiat** (`nvm:card-delegation`) — credit card payments via Stripe delegation Both schemes follow the same token flow: ``` Subscriber Claw Builder Claw (Gateway) │ │ │ 1. getX402AccessToken(planId, │ │ tokenOptions) │ │─────────────────────────────────────>│ Nevermined API │<─────────────────────────────────────│ returns token │ │ │ 2. POST /nevermined/agent │ │ Header: payment-signature: token │ │ Body: { prompt: "..." } │ │─────────────────────────────────────>│ │ │ 3. verifyPermissions() │ │ 4. Process request │ │ 5. settlePermissions() │ 6. Response + payment-response hdr │ │<─────────────────────────────────────│ ``` For crypto, credits are managed on-chain through the Nevermined Protocol. For fiat, credits are charged against the delegated card via Stripe. In both cases, `verifyPermissions` checks the subscriber's balance without consuming credits, and `settlePermissions` consumes them only after successful processing. ## Next Steps * [Commands Reference](./commands.md) — full parameter documentation for all tools * [Setup Reference](./setup.md) — detailed configuration options * [Nevermined Docs](https://nevermined.ai/docs) — platform documentation * [x402 Protocol](https://nevermined.ai/docs/api-reference/typescript/x402-protocol) — payment protocol specification * [OpenClaw Plugin Development](https://docs.openclaw.ai/tools/plugin) — building OpenClaw plugins # Setup Source: https://docs.nevermined.app/docs/api-reference/openclaw-plugin/setup Full configuration reference for the Nevermined OpenClaw plugin # Setup ## Configuration The plugin reads its configuration from the `plugins.openclaw-plugin` section of your `openclaw.json`: ```json theme={null} { "plugins": { "entries": { "openclaw-plugin": { "enabled": true, "config": { "nvmApiKey": "sandbox:eyJhbG...", "environment": "sandbox", "planId": "did:nv:abc123...", "agentId": "did:nv:def456...", "creditsPerRequest": 1, "enablePaidEndpoint": false, "agentEndpointPath": "/nevermined/agent", "paymentType": "crypto", "defaultSpendingLimitCents": 1000, "defaultDelegationDurationSecs": 3600 } } } } } ``` ### Configuration Fields | Field | Required | Default | Description | | ------------------------------- | -------- | ------------------- | -------------------------------------------------------------------------------- | | `nvmApiKey` | No | — | Your Nevermined API key. Can be set via `/nvm_login` instead. | | `environment` | No | `sandbox` | Target environment: `sandbox` for testing, `live` for production. | | `planId` | No | — | Default payment plan ID. When set, subscriber tools use this plan automatically. | | `agentId` | No | — | Default agent ID. Required for plans with multiple agents. | | `creditsPerRequest` | No | `1` | Number of credits consumed per request. | | `enablePaidEndpoint` | No | `false` | Enable the x402 paid HTTP endpoint on the gateway. | | `agentEndpointPath` | No | `/nevermined/agent` | HTTP path for the paid agent endpoint. | | `paymentType` | No | `crypto` | Default payment type: `crypto` (nvm:erc4337) or `fiat` (nvm:card-delegation). | | `defaultSpendingLimitCents` | No | `1000` | Default max spend in cents (\$10) for fiat payments. | | `defaultDelegationDurationSecs` | No | `3600` | Default delegation duration in seconds (1 hour) for fiat payments. | ### Environment Details | Environment | Description | Use Case | | ----------- | -------------------------------------- | ----------------------------------- | | `sandbox` | Test environment on Base Sepolia | Development, testing, integration | | `live` | Production environment on Base Mainnet | Live deployments with real payments | ## Authentication Flow The plugin supports a browser-based login flow identical to the [Nevermined CLI](/docs/api-reference/cli/getting-started): 1. User sends `/nvm_login` in any chat channel 2. The plugin starts a local HTTP server on a random port 3. A browser window opens to the Nevermined login page 4. After authentication, Nevermined redirects back with the API key 5. The key is stored in the plugin's in-memory config for the current session The login times out after 5 minutes if no authentication is received. ### Logging Out Send `/nvm_logout` or call `nvm_logout` to remove the stored API key. All payment tools will require re-authentication after logout. ## Default Values When `planId` and `agentId` are set in the config, all subscriber tools (`checkBalance`, `getAccessToken`, `orderPlan`, `queryAgent`) use them as defaults. You can always override them per-call by passing the parameter explicitly. This is useful for gateways that serve a single plan — configure it once and all tools work without extra parameters. # 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 ## Building Agent Cards ### With a Single Plan ```python theme={null} from payments_py.a2a.agent_card 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", }, ) ``` > **Note:** 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 Bearer token from Authorization 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, 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={"Authorization": f"Bearer {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} import os from payments_py.common.types import AgentMetadata, AgentAPIAttributes, AuthType # 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. All AgentAPIAttributes fields are optional — # most builders only need to set authentication. See # "AgentAPIAttributes Fields" below for opt-in Additional Security. agent_api = AgentAPIAttributes( auth_type=AuthType.BEARER, token=os.environ["AGENT_BEARER_TOKEN"], ) # 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} import os from payments_py.common.types import ( AgentMetadata, AgentAPIAttributes, AuthType, 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( auth_type=AuthType.BEARER, token=os.environ["AGENT_BEARER_TOKEN"], ) # 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 All fields are optional. Provide only what your integration needs. | Field | Type | Required | Description | | ---------------------- | ---------------- | -------- | ------------------------------------------------------ | | `auth_type` | `AuthType` | No | Authentication type (default: `AuthType.NONE`) | | `token` | `str` | No | Bearer token (when `auth_type` is `BEARER` or `OAUTH`) | | `username` | `str` | No | Basic-auth username | | `password` | `str` | No | Basic-auth password | | `endpoints` | `list[Endpoint]` | No | **Additional Security** allowlist — see below | | `open_endpoints` | `list[str]` | No | Public endpoints (no subscription required) | | `agent_definition_url` | `str` | No | Discoverable agent definition (OpenAPI / MCP / A2A) | ### Additional Security: Endpoint Allowlist (opt-in) Setting `endpoints` opts the agent into a platform-enforced allowlist. When set, only requests matching one of the registered URLs are accepted during x402 verification; non-matching requests are rejected. When omitted, the platform performs no route-level allowlist check — your Payments library middleware (`PaymentMiddleware`, `@requires_payment`) remains the sole gate. ```python theme={null} import os from payments_py.common.types import AgentAPIAttributes, AuthType, Endpoint agent_api = AgentAPIAttributes( auth_type=AuthType.BEARER, token=os.environ["AGENT_BEARER_TOKEN"], # Opt-in: route allowlist enforced by x402 verify endpoints=[ Endpoint(verb="POST", url="https://api.example.com/agents/:agentId/tasks"), Endpoint(verb="GET", url="https://api.example.com/agents/:agentId/tasks/:taskId"), Endpoint(verb="DELETE", url="https://api.example.com/agents/:agentId/tasks/:taskId"), ], open_endpoints=["https://api.example.com/health"], agent_definition_url="https://api.example.com/openapi.json", ) ``` The `:agentId` and `:taskId` placeholders will be replaced with actual values during request validation. > **Migration note:** existing agents that registered before April 2026 still have these fields populated and continue to enforce the allowlist as before. No migration is needed. ## 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 `update_agent_metadata` is also where builders typically opt in to **Additional Security** by adding an `endpoints` allowlist or `agent_definition_url` to an existing agent: ```python theme={null} import os from payments_py.common.types import ( AgentMetadata, AgentAPIAttributes, AuthType, Endpoint, ) updated_metadata = AgentMetadata( name="Updated Agent Name", description="Updated description with new features", tags=["ai", "updated", "v2"] ) # Adding `endpoints` activates the platform-enforced allowlist for this # agent. To return to allow-all, omit it on the next update. updated_api = AgentAPIAttributes( auth_type=AuthType.BEARER, token=os.environ["AGENT_BEARER_TOKEN"], endpoints=[Endpoint(verb="POST", url="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 ```mermaid theme={null} flowchart TD subgraph Builder B1[1. Create Plan — register_credits_plan] B2[2. Register Agent with Plan] B3[3. Optionally mint credits to users] B1 --> B2 --> B3 end subgraph Subscriber S1[1. Check Balance — get_plan_balance] S2[2. Order Plan — order_plan] S3[3. Get Access Token — get_x402_access_token] S4[4. Query Agent with access token] S1 --> S2 --> S3 --> S4 end Builder --> Subscriber ``` ## 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` | Production 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" ) ``` ### Fiat Price (EUR) ```python theme={null} from payments_py.plans import get_fiat_price_config from payments_py.common.types import Currency price_config = get_fiat_price_config( amount=2900, # €29.00 in euro cents receiver="0xBuilderAddress", currency=Currency.EUR ) ``` ### EURC Token Price (Euro Stablecoin) ```python theme={null} from payments_py.plans import get_eurc_price_config # EURC pricing (Euro stablecoin on Base, 6 decimals) price_config = get_eurc_price_config( amount=29_000_000, # €29.00 in smallest unit (6 decimals) receiver="0xBuilderAddress" ) # Or use a custom EURC address (e.g., testnet) from payments_py.common.types import EURC_TOKEN_ADDRESS_TESTNET testnet_config = get_eurc_price_config( amount=29_000_000, receiver="0xBuilderAddress", eurc_address=EURC_TOKEN_ADDRESS_TESTNET ) ``` ### 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 (basic — auto-creates delegation) 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) | | `token_options` | `X402TokenOptions` | No | Scheme and delegation configuration | ### Token Generation with Delegation For erc4337 (crypto) plans, token generation now uses delegations. Two patterns are supported: ```python theme={null} from payments_py.x402 import X402TokenOptions, DelegationConfig, CreateDelegationPayload # Pattern A — Auto-create delegation (simple) result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( delegation_config=DelegationConfig( spending_limit_cents=10000, # $100 duration_secs=604800 # 1 week ) ) ) # Pattern B — Explicit create + reuse (for sharing across plans) delegation = payments.delegation.create_delegation( CreateDelegationPayload( provider="erc4337", spending_limit_cents=10000, duration_secs=604800 ) ) result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( delegation_config=DelegationConfig( delegation_id=delegation.delegation_id ) ) ) ``` ## 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"), # AgentAPIAttributes() with no args registers a minimal agent — your # Payments library middleware handles per-route gating. See # docs/api/04-agents.md for opt-in Additional Security. agent_api=AgentAPIAttributes(), 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, token_options=token_options) ``` ## Request Flow Diagram ```mermaid theme={null} sequenceDiagram participant Subscriber participant Nevermined participant Agent Subscriber->>Nevermined: get_x402_access_token() Nevermined-->>Subscriber: accessToken Subscriber->>Agent: POST /tasks
payment-signature: token Agent->>Nevermined: verify_permissions() Nevermined-->>Agent: {isValid: true} Agent->>Nevermined: settle_permissions() Nevermined-->>Agent: {success: true} Agent-->>Subscriber: 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"Payer: {verification.payer}") else: print(f"Invalid request: {verification.invalid_reason}") ``` ### 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.transaction}") ``` ## 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.invalid_reason }), 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 import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware app = FastAPI() # Initialize payments payments = Payments.get_instance( PaymentOptions(nvm_api_key="nvm:agent-key", environment="sandbox") ) # Add x402 middleware app.add_middleware( PaymentMiddleware, payments=payments, routes={ "/api/tasks": { "plan_id": "your-plan-id", "agent_id": "your-agent-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 a `VerifyResponse`: | Field | Type | Description | | ------------------ | ------ | ---------------------------------------------- | | `is_valid` | `bool` | Whether the request is authorized | | `invalid_reason` | `str` | Reason for invalidity (if `is_valid` is false) | | `payer` | `str` | Payer's wallet address | | `agent_request_id` | `str` | Agent request ID for observability tracking | ## Settlement Response The `settle_permissions` method returns a `SettleResponse`: | Field | Type | Description | | ------------------- | ------ | ----------------------------------------------------- | | `success` | `bool` | Whether settlement succeeded | | `error_reason` | `str` | Reason for settlement failure (if `success` is false) | | `payer` | `str` | Payer's wallet address | | `transaction` | `str` | Blockchain transaction hash | | `credits_redeemed` | `str` | Number of credits burned | | `remaining_balance` | `str` | 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). ## Supported Schemes Nevermined supports two x402 payment schemes: | Scheme | Network | Use Case | Settlement | | --------------------- | ----------------------------------------- | ---------------- | ------------------------------------------------------------------ | | `nvm:erc4337` | `eip155:84532` (or other CAIP-2 chain ID) | Crypto payments | ERC-4337 UserOps + session keys | | `nvm:card-delegation` | `stripe` or `braintree` | Fiat/credit card | Stripe PaymentIntent or Braintree `transaction.sale` + credit burn | The scheme is determined by the plan's pricing configuration. Plans with `isCrypto: false` use `nvm:card-delegation`; all others use `nvm:erc4337`. For card-delegation plans, the network is derived from the plan's `fiatPaymentProvider` metadata (`'stripe'` or `'braintree'`). The SDK auto-detects both via `resolve_scheme()` / `resolve_network()`. ## 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'] # With delegation config (crypto — erc4337) from payments_py.x402 import X402TokenOptions, DelegationConfig, CreateDelegationPayload # Pattern A — auto-create delegation result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( delegation_config=DelegationConfig( spending_limit_cents=10000, # $100 duration_secs=604800 # 1 week ) ) ) # Pattern B — explicit create + reuse across plans delegation = payments.delegation.create_delegation( CreateDelegationPayload( provider="erc4337", spending_limit_cents=10000, duration_secs=604800 ) ) result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( delegation_config=DelegationConfig(delegation_id=delegation.delegation_id) ) ) ``` ### Card-Delegation Token Generation For fiat plans using `nvm:card-delegation`, pass `X402TokenOptions` with a `DelegationConfig`: ```python theme={null} from payments_py.x402 import X402TokenOptions, DelegationConfig # USD card delegation result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( scheme="nvm:card-delegation", delegation_config=DelegationConfig( provider_payment_method_id="pm_1AbCdEfGhIjKlM", spending_limit_cents=10000, # $100.00 duration_secs=2592000, # 30 days currency="usd", max_transactions=100 ) ) ) access_token = result['accessToken'] # EUR card delegation eur_result = payments.x402.get_x402_access_token( plan_id="your-plan-id", agent_id="agent-id", token_options=X402TokenOptions( scheme="nvm:card-delegation", delegation_config=DelegationConfig( provider_payment_method_id="pm_1AbCdEfGhIjKlM", spending_limit_cents=10000, # €100.00 (in euro cents) duration_secs=2592000, # 30 days currency="eur", max_transactions=100 ) ) ) ``` ### Auto Scheme Resolution Use `resolve_scheme()` to auto-detect the correct scheme from plan metadata: ```python theme={null} from payments_py.x402.resolve_scheme import resolve_scheme # Auto-detect scheme from plan metadata (cached for 5 minutes) scheme = resolve_scheme(payments, plan_id="your-plan-id") # Returns "nvm:erc4337" for crypto plans, "nvm:card-delegation" for fiat plans # Explicit override scheme = resolve_scheme(payments, plan_id="your-plan-id", explicit_scheme="nvm:card-delegation") ``` ### DelegationAPI Create delegations and list enrolled payment methods: ```python theme={null} from payments_py.x402 import CreateDelegationPayload # Create a crypto delegation delegation = payments.delegation.create_delegation( CreateDelegationPayload( provider="erc4337", spending_limit_cents=10000, duration_secs=604800 ) ) print(f"Delegation ID: {delegation.delegation_id}") # List enrolled payment methods (for card delegation) methods = payments.delegation.list_payment_methods() for method in methods: print(f"{method.brand} ****{method.last4} (expires {method.exp_month}/{method.exp_year})") # e.g., "visa ****4242 (expires 12/2027)" ``` `PaymentMethodSummary` fields: | Field | Type | Description | | ----------- | ----- | --------------------------------------- | | `id` | `str` | Payment method ID (e.g., `pm_...`) | | `brand` | `str` | Card brand (e.g., `visa`, `mastercard`) | | `last4` | `str` | Last 4 digits of the card number | | `exp_month` | `int` | Card expiration month | | `exp_year` | `int` | Card expiration 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! Payer: {verification.payer}") else: print(f"Invalid: {verification.invalid_reason}") ``` ### Verification Response | Field | Type | Description | | ------------------ | ------ | ---------------------------------------------- | | `is_valid` | `bool` | Whether verification passed | | `invalid_reason` | `str` | Reason for invalidity (if `is_valid` is false) | | `payer` | `str` | Payer's wallet address | | `agent_request_id` | `str` | Agent request ID for observability tracking | ## 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.transaction}") print(f"Remaining: {settlement.remaining_balance}") else: print(f"Settlement failed: {settlement.error_reason}") ``` ### Settlement Response | Field | Type | Description | | ------------------- | ------ | ------------------------------------------ | | `success` | `bool` | Whether settlement succeeded | | `error_reason` | `str` | Reason for failure (if `success` is false) | | `payer` | `str` | Payer's wallet address | | `transaction` | `str` | Blockchain transaction hash | | `credits_redeemed` | `str` | Credits that were burned | | `remaining_balance` | `str` | Credits remaining | ## Payment Required Object The `X402PaymentRequired` object specifies what payment is required. The `scheme` and `network` fields vary by payment type: ```python theme={null} from payments_py.x402.types import X402PaymentRequired, X402Resource, X402Scheme, X402SchemeExtra # Crypto plan (nvm:erc4337) payment_required = X402PaymentRequired( x402_version=2, resource=X402Resource( url="https://your-api.com/endpoint", description="Protected endpoint" # Optional ), accepts=[ X402Scheme( scheme="nvm:erc4337", network="eip155:84532", # Base Sepolia plan_id="your-plan-id", extra=X402SchemeExtra( http_verb="POST", # HTTP method goes in extra, not resource agent_id="agent-123" # Optional ) ) ], extensions={} ) # Fiat plan (nvm:card-delegation) payment_required_fiat = X402PaymentRequired( x402_version=2, resource=X402Resource( url="https://your-api.com/endpoint", description="Protected endpoint" ), accepts=[ X402Scheme( scheme="nvm:card-delegation", network="stripe", # or "braintree" — must match the plan's fiatPaymentProvider plan_id="your-plan-id", extra=X402SchemeExtra( http_verb="POST", agent_id="agent-123" ) ) ], extensions={} ) ``` ### Using the Helpers ```python theme={null} from payments_py.x402.helpers import build_payment_required, build_payment_required_for_plans # Single plan (scheme auto-detected from plan metadata when omitted) payment_required = build_payment_required( plan_id="your-plan-id", endpoint="https://api.example.com/tasks", agent_id="agent-123", http_verb="POST" ) # Explicit scheme override payment_required = build_payment_required( plan_id="your-plan-id", endpoint="https://api.example.com/tasks", agent_id="agent-123", http_verb="POST", scheme="nvm:card-delegation" # Force fiat scheme; network auto-resolved from plan metadata (stripe or braintree) ) # Multiple plans — creates one entry per plan in accepts[] payment_required = build_payment_required_for_plans( plan_ids=["plan-basic", "plan-premium"], endpoint="https://api.example.com/tasks", agent_id="agent-123", http_verb="POST" ) ``` For a single plan, `build_payment_required_for_plans` delegates to `build_payment_required` internally. When `scheme` is omitted, the network defaults to `eip155:84532` (Base Sepolia). When `scheme="nvm:card-delegation"`, the network is auto-resolved from the plan's `fiatPaymentProvider` metadata — either `"stripe"` or `"braintree"`. ## 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.invalid_reason, '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 ```mermaid theme={null} sequenceDiagram participant Subscriber participant Agent participant Nevermined Subscriber->>Agent: GET /api/process (no token) Agent-->>Subscriber: 402 Payment Required
{paymentRequired: {...}} Subscriber->>Nevermined: get_x402_access_token() Nevermined-->>Subscriber: {accessToken: "..."} Subscriber->>Agent: GET /api/process
payment-signature: token Agent->>Nevermined: verify_permissions() Nevermined-->>Agent: {isValid: true} Note over Agent: Process request Agent->>Nevermined: settle_permissions() Nevermined-->>Agent: {success: true} Agent-->>Subscriber: 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 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 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 Plans for an Agent ```typescript theme={null} // Get all payment plans associated with an agent const plans = await payments.agents.getAgentPlans(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.updateAgentMetadata( 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(newPlanId, agentId) 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(planIdToRemove, agentId) 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, total, page, offset } = await payments.agents.getAgentPlans(agentId) console.log(`Agent has ${plans.length} plans on page ${page} (offset ${offset}, total ${total})`) ``` ## 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 for monetizing AI agents The Nevermined Payments Library is a TypeScript SDK that enables AI builders and subscribers to monetize and access AI agents through the Nevermined protocol. The library supports flexible payment plans (credits and time-based), multiple payment methods (crypto and fiat), and seamless integration with AI agent frameworks. ## Overview The Payments Library provides: * Registration and discovery of AI agents with payment plans * Flexible pricing options (fixed per request or dynamic usage-based) * Credit-based access control with on-chain settlement * Support for browser and Node.js environments * Integration with MCP (Model Context Protocol) and A2A (Agent-to-Agent) protocols ## Prerequisites Before installing the Nevermined Payments Library, ensure you have: * **Node.js**: Version 18.x or higher * **TypeScript**: Version 5.x or higher (recommended) * **Nevermined API Key**: Get your free API key from [nevermined.app](https://nevermined.app) * **Package Manager**: npm (included with Node.js) or yarn ## Installation Install the library using npm or yarn: ```bash theme={null} # Using npm npm install @nevermined-io/payments # Using yarn yarn add @nevermined-io/payments ``` ### Peer Dependencies The library has optional peer dependencies for specific integrations: ```bash theme={null} # For MCP (Model Context Protocol) integration npm install @modelcontextprotocol/sdk # For Express-based HTTP agents npm install express @types/express ``` These dependencies are only required if you're using the respective integration features. ## Verify Installation After installation, verify the library is correctly installed by importing it: ```typescript theme={null} import { Payments } from '@nevermined-io/payments' console.log('Nevermined Payments Library installed successfully!') ``` You can also check the installed version: ```bash theme={null} npm list @nevermined-io/payments ``` ## Next Steps After successful installation: 1. Get your Nevermined API key from [nevermined.app](https://nevermined.app) 2. Choose your environment (sandbox for testing, live for production) 3. Initialize the Payments instance in your application Continue to [Initializing the Library](./initializing-the-library) to get started. *** **Source References**: * `package.json` * `README.md` # 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 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: (ctx) => { const { text } = ctx.args as { text: string } const textLength = 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 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 USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // USDC on Base Sepolia const priceConfig = payments.plans.getERC20PriceConfig( 20n, // Amount in token's smallest unit USDC_ADDRESS, // Token contract address builderAddress // Receiver address ) ``` ### EURC Token Payment (Euro Stablecoin) ```typescript theme={null} // EURC pricing (Euro stablecoin on Base, 6 decimals) const eurcPriceConfig = payments.plans.getEURCPriceConfig( 29_000_000n, // €29.00 in smallest unit (6 decimals) builderAddress // Receiver address ) // Or use a custom EURC address (e.g., testnet) import { EURC_TOKEN_ADDRESS_TESTNET } from '@nevermined-io/payments' const testnetEurcConfig = payments.plans.getEURCPriceConfig( 29_000_000n, builderAddress, EURC_TOKEN_ADDRESS_TESTNET ) ``` ### Fiat Payment (Stripe) ```typescript theme={null} // Fiat pricing in USD (default) const priceConfig = payments.plans.getFiatPriceConfig( 1000n, // Amount in cents ($10.00) builderAddress ) // Fiat pricing in EUR import { Currency } from '@nevermined-io/payments' const eurPriceConfig = payments.plans.getFiatPriceConfig( 2900n, // Amount in euro cents (€29.00) builderAddress, Currency.EUR ) ``` ### 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 Plans for an Agent To find plans associated with an agent, use the agents API: ```typescript theme={null} // Get all payment plans associated with an agent const { plans } = await payments.agents.getAgentPlans(agentId) plans.forEach(plan => { console.log(`Plan: ${plan.name} (${plan.planId})`) }) ``` ## 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 This guide explains how to purchase payment plans and check 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 access 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 purchased this plan } ``` ### 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 and routed to Stripe, use `orderFiatPlan` to get a Stripe checkout URL: ```typescript theme={null} const fiatResult = await subscriberPayments.plans.orderFiatPlan(planId) if (fiatResult.checkoutUrl) { console.log(`Complete payment at: ${fiatResult.checkoutUrl}`) // Redirect the user to the Stripe checkout page } ``` `orderFiatPlan` is the SDK entry point for **Stripe-priced** plans only. Plans with `fiatPaymentProvider: 'braintree'` use a Braintree Drop-in checkout that runs inside the [Nevermined App](https://nevermined.app); the SDK does not currently expose a programmatic order method for Braintree. See [Braintree onboarding](/docs/products/nvm-pay/braintree-onboarding) for details. #### CLI ```bash theme={null} # Order a fiat plan (returns a Stripe checkout URL) nvm plans order-fiat-plan ``` > **Note**: When using X402 card-delegation tokens (`nvm:card-delegation` scheme), the payment is handled automatically through the delegated card — no separate checkout URL is needed. See [Querying an Agent](./querying-an-agent) for details on card-delegation tokens. ### 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 Stripe checkout for fiat-priced plans is managed through the Nevermined App dashboard. Visit [nevermined.app](https://nevermined.app) to configure Stripe payments for your plans. ## 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 purchased const balance = await subscriberPayments.plans.getPlanBalance(planId) if (balance.isSubscriber && BigInt(balance.balance) > 0n) { console.log('Already purchased 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 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) ) ``` ### Card-Delegation Tokens (Fiat) For plans that accept fiat payments, generate tokens using the `nvm:card-delegation` scheme. Pass `X402TokenOptions` with a `CardDelegationConfig` to specify the payment method and spending limits: ```typescript theme={null} import { Payments, EnvironmentName, X402TokenOptions } from '@nevermined-io/payments' const subscriberPayments = Payments.getInstance({ nvmApiKey: process.env.SUBSCRIBER_API_KEY!, environment: 'sandbox' as EnvironmentName, }) // List enrolled payment methods const methods = await subscriberPayments.delegation.listPaymentMethods() console.log(`Found ${methods.length} enrolled card(s)`) // Build token options for card-delegation const tokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { providerPaymentMethodId: methods[0].id, // or a specific 'pm_...' ID spendingLimitCents: 1000, // max $10.00 durationSecs: 3600, // 1 hour delegation }, } // Generate fiat access token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, undefined, // redemptionLimit undefined, // orderLimit undefined, // expiration tokenOptions, ) ``` #### Auto-Detecting the Scheme If you don't know whether a plan uses crypto or fiat, use `resolveScheme()` to auto-detect from plan metadata: ```typescript theme={null} import { Payments, resolveScheme } from '@nevermined-io/payments' const scheme = await resolveScheme(subscriberPayments, planId) let tokenOptions: X402TokenOptions | undefined if (scheme === 'nvm:card-delegation') { const methods = await subscriberPayments.delegation.listPaymentMethods() tokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { providerPaymentMethodId: methods[0].id, spendingLimitCents: 1000, durationSecs: 3600, }, } } const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, undefined, undefined, undefined, tokenOptions, ) ``` #### CLI ```bash theme={null} # Crypto (default) nvm x402token get-x402-access-token # Fiat with auto-selected card nvm x402token get-x402-access-token --payment-type fiat # Fiat with specific card and limits nvm x402token get-x402-access-token --payment-type fiat \ --payment-method-id pm_1AbCdEfGhIjKlM \ --spending-limit-cents 5000 \ --delegation-duration-secs 7200 # Auto-detect scheme from plan metadata nvm x402token get-x402-access-token --auto-resolve-scheme # List enrolled payment methods nvm delegation list-payment-methods ``` ### 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) ``` **Note**: The A2A (Agent-to-Agent) protocol also uses the `payment-signature` header for authentication. For A2A integration details, see the [A2A Integration](./a2a-integration) guide. ## 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', 'payment-signature': 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 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 ## Supported Schemes Nevermined supports two x402 payment schemes: | Scheme | Network | Use Case | Settlement | | --------------------- | ----------------------------------------- | ---------------- | ------------------------------------------------------------------ | | `nvm:erc4337` | `eip155:84532` (or other CAIP-2 chain ID) | Crypto payments | ERC-4337 UserOps + session keys | | `nvm:card-delegation` | `stripe` or `braintree` | Fiat/credit card | Stripe PaymentIntent or Braintree `transaction.sale` + credit burn | The scheme is determined by the plan's pricing configuration. Plans with `isCrypto: false` use `nvm:card-delegation`; all others use `nvm:erc4337`. For card-delegation plans, the network is derived from the plan's `fiatPaymentProvider` metadata (`'stripe'` or `'braintree'`). The SDK auto-detects both via `resolveScheme()` / `resolveNetwork()`. ## 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. ### Generate Card-Delegation Tokens For fiat plans using `nvm:card-delegation`, pass `X402TokenOptions` with a `CardDelegationConfig`: ```typescript theme={null} import { X402TokenOptions, CardDelegationConfig } from '@nevermined-io/payments' // USD card delegation const tokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { providerPaymentMethodId: 'pm_1AbCdEfGhIjKlM', spendingLimitCents: 10000, // $100.00 durationSecs: 2592000, // 30 days currency: 'usd', maxTransactions: 100 } } // EUR card delegation const eurTokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { providerPaymentMethodId: 'pm_1AbCdEfGhIjKlM', spendingLimitCents: 10000, // €100.00 (in euro cents) durationSecs: 2592000, // 30 days currency: 'eur', maxTransactions: 100 } } // USD card delegation token const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, undefined, // redemptionLimit undefined, // orderLimit undefined, // expiration tokenOptions ) // EUR card delegation token const { accessToken: eurAccessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, undefined, undefined, undefined, eurTokenOptions ) ``` ### Reusing Existing Delegations Instead of creating a new delegation on every token request, you can reuse an existing delegation by passing its `delegationId`. This is useful when running multiple agents that should share a single spending budget: ```typescript theme={null} const tokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { delegationId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', } } const { accessToken } = await subscriberPayments.x402.getX402AccessToken( planId, agentId, undefined, undefined, undefined, tokenOptions ) ``` When `delegationId` is provided, the backend verifies that the delegation is active and that the requesting API key has access, then returns its existing token without creating a new delegation. ### Specifying a Card You can target a specific enrolled card using `cardId`. The backend will look for an active delegation on that card or create a new one: ```typescript theme={null} const tokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { cardId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', spendingLimitCents: 5000, // Only used if a new delegation is created durationSecs: 604800, } } ``` ### Auto-Selection When neither `cardId` nor `delegationId` is specified (and `providerPaymentMethodId` is omitted), the backend automatically selects the best card and delegation for the requesting API key. It finds cards accessible to the API key, looks for active delegations with remaining budget, and reuses one if available — otherwise creates a new delegation: ```typescript theme={null} const tokenOptions: X402TokenOptions = { scheme: 'nvm:card-delegation', delegationConfig: { spendingLimitCents: 10000, durationSecs: 2592000, } } ``` ### CardDelegationConfig Reference | Field | Type | Required | Description | | ------------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `delegationId` | `string` | No | Existing delegation UUID to reuse | | `cardId` | `string` | No | Payment method UUID to target | | `providerPaymentMethodId` | `string` | No | PSP-side payment method handle: Stripe `pm_...` ID, or Braintree `paymentMethodToken` | | `spendingLimitCents` | `number` | No\* | Max spending in cents (for new delegations) | | `durationSecs` | `number` | No\* | Duration in seconds (for new delegations) | | `currency` | `string` | No | Currency code, defaults to `usd` | | `merchantAccountId` | `string` | No | Merchant account identifier on the PSP: Stripe Connected Account ID, or seller's per-currency Braintree `merchantAccountId` | | `maxTransactions` | `number` | No | Max transactions allowed | \* Required when creating a new delegation. Ignored when reusing an existing one via `delegationId`. ### Auto Scheme Resolution Use `resolveScheme()` to auto-detect the correct scheme from plan metadata: ```typescript theme={null} import { resolveScheme } from '@nevermined-io/payments' // Auto-detect scheme from plan metadata (cached for 5 minutes) const scheme = await resolveScheme(payments, planId) // Returns "nvm:erc4337" for crypto plans, "nvm:card-delegation" for fiat plans // Explicit override const scheme = await resolveScheme(payments, planId, 'nvm:card-delegation') ``` ### DelegationAPI Manage payment methods and delegations for card delegation: ```typescript theme={null} import { DelegationAPI, PaymentMethodSummary } from '@nevermined-io/payments' const delegationApi = DelegationAPI.getInstance(payments.options) // List enrolled payment methods const methods: PaymentMethodSummary[] = await delegationApi.listPaymentMethods() for (const method of methods) { console.log(`${method.brand} ****${method.last4} (expires ${method.expMonth}/${method.expYear})`) if (method.allowedApiKeyIds) { console.log(` Restricted to API keys: ${method.allowedApiKeyIds.join(', ')}`) } } ``` #### Update Payment Method Restrict a card to specific NVM API Keys so only designated agents can use it: ```typescript theme={null} await delegationApi.updatePaymentMethod('pm-uuid-here', { alias: 'Production Card', allowedApiKeyIds: ['sk-agent-1', 'sk-agent-2'], // Only these API keys can use this card }) // Remove restrictions (any API key can use the card) await delegationApi.updatePaymentMethod('pm-uuid-here', { allowedApiKeyIds: null, }) ``` #### List Delegations Retrieve all delegations for the authenticated user: ```typescript theme={null} const { delegations, totalResults } = await delegationApi.listDelegations() console.log(`Total delegations: ${totalResults}`) for (const d of delegations) { console.log(`${d.delegationId} - ${d.status} (${d.remainingBudgetCents} cents remaining)`) if (d.apiKeyId) { console.log(` Restricted to API key: ${d.apiKeyId}`) } } ``` #### DelegationListResponse Fields `listDelegations()` resolves to a `DelegationListResponse` object: | Field | Type | Description | | -------------- | --------------------- | ------------------------------- | | `delegations` | `DelegationSummary[]` | Array of delegation records | | `totalResults` | `number` | Total number of delegations | | `page` | `number` | Current page number | | `offset` | `number` | Offset into the full result set | #### DelegationSummary Fields Each item in the `delegations` array is a `DelegationSummary`: | Field | Type | Description | | ------------------------- | ---------------- | ------------------------------------------------------------------- | | `delegationId` | `string` | Unique delegation identifier | | `provider` | `string` | Payment provider (`stripe` or `braintree`) | | `providerPaymentMethodId` | `string` | Provider-side payment method ID | | `status` | `string` | Delegation status (`Active`, `Revoked`, etc.) | | `spendingLimitCents` | `string` | Maximum spendable amount in cents | | `amountSpentCents` | `string` | Amount already spent in cents | | `remainingBudgetCents` | `string` | Remaining budget in cents | | `currency` | `string` | Currency code (e.g., `usd`) | | `transactionCount` | `number` | Number of transactions made | | `expiresAt` | `string` | Expiry timestamp (ISO 8601) | | `createdAt` | `string` | Creation timestamp (ISO 8601) | | `apiKeyId` | `string \| null` | API key this delegation is restricted to, or `null` if unrestricted | #### PaymentMethodSummary Fields | Field | Type | Description | | ------------------ | ------------------ | --------------------------------------------------------- | | `id` | `string` | Payment method ID (e.g., `pm_...`) | | `brand` | `string` | Card brand (e.g., `visa`, `mastercard`) | | `last4` | `string` | Last 4 digits of the card number | | `expMonth` | `number` | Card expiration month | | `expYear` | `number` | Card expiration year | | `allowedApiKeyIds` | `string[] \| null` | API keys allowed to use this card (`null` = unrestricted) | ## 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 } ``` ### Card-Delegation Token Structure For fiat plans using `nvm:card-delegation`, the token contains a JWT-based delegation authorization: ```json theme={null} { "x402Version": 2, "accepted": { "scheme": "nvm:card-delegation", "network": "stripe", "planId": "plan-123", "extra": { "version": "1", "agentId": "did:nv:agent-456" } }, "payload": { "token": "eyJhbGciOiJSUzI1NiIs...", "authorization": { "from": "0xSubscriberAddress", "sessionKeys": [{ "id": "redeem", "data": "0xabc123..." }] } }, "extensions": {} } ``` ### Token Components * **x402Version**: Protocol version (2 for current spec) * **accepted**: Payment method specification * **scheme**: `nvm:erc4337` for crypto or `nvm:card-delegation` for fiat * **network**: `eip155:84532` (Base Sepolia) for crypto, `stripe` or `braintree` for fiat (matches the plan's `fiatPaymentProvider` metadata) * **planId**: The payment plan being used * **extra**: Additional metadata (version, agentId, etc.) * **payload**: Payment authorization * **signature** (erc4337): Cryptographic proof of payment authorization * **token** (card-delegation): Signed JWT encoding the delegation claims * **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(`Payer: ${verification.payer}`) } else { console.log(`✗ Verification failed: ${verification.invalidReason}`) } ``` ### Verification Response ```typescript theme={null} interface VerifyPermissionsResult { isValid: boolean // True if token is valid invalidReason?: string // Reason for invalidity (if isValid is false) payer?: string // Address of the payer's wallet agentRequestId?: string // Agent request ID for observability tracking (Nevermined extension) urlMatching?: string // URL pattern that matched the endpoint (Nevermined extension) agentRequest?: StartAgentRequest // Agent request context for observability (Nevermined extension) } interface StartAgentRequest { agentRequestId: string // Unique request identifier agentName: string // Name of the agent agentId: string // Agent identifier balance: PlanBalance // Current plan balance (planId, balance, pricePerCredit, etc.) urlMatching: string // URL pattern that was matched verbMatching: string // HTTP verb that was matched batch: boolean // Whether this is a batch request } ``` ## 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. The `scheme` and `network` vary by plan type: **Crypto plan:** ```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": {} } ``` **Fiat plan:** ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/tasks", "description": "Task execution API", "mimeType": "application/json" }, "accepts": [ { "scheme": "nvm:card-delegation", "network": "stripe", "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' // Scheme auto-detected from plan metadata when omitted 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 for nvm:erc4337) description: 'Task execution', // Description scheme: 'nvm:erc4337' // Optional: omit to auto-detect from plan metadata } ) ``` When `scheme` is set to `'nvm:card-delegation'`, the `network` is auto-resolved from the plan's `fiatPaymentProvider` metadata — either `'stripe'` or `'braintree'`. Pass `network` explicitly to override. ## 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 8. **Restrict Cards to API Keys**: When running multiple agents, restrict each card to specific NVM API Keys using `allowedApiKeyIds` to prevent unauthorized spending 9. **Reuse Delegations**: Pass `delegationId` to reuse existing delegations instead of creating new ones on each request — this avoids delegation sprawl and keeps spending consolidated ## 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/delegation-api.ts` (DelegationAPI: listPaymentMethods, listDelegations, updatePaymentMethod) * `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. # API error codes Source: https://docs.nevermined.app/docs/development-guide/api-errors/codes Catalogue of every BCK.* error code returned by the Nevermined API Every error response from the Nevermined API carries a stable `code` (e.g. `BCK.X402.0008`). The table below enumerates every code, its HTTP status, category, canonical message, and remediation hint. Pages on this site are anchored by code — link directly to `#bck-x402-0008` from your error handlers, runbooks, or support tickets. Locked response fields are `code`, `message`, and the HTTP status. Optional fields (`hint`, `docsUrl`, `category`, `retryable`, `correlationId`) appear only when supplied. Consumers that branch only on `code` and `message` ignore the rest transparently. ## Namespaces * [`BCK.AGENT`](#bck-agent) — 11 codes * [`BCK.APIKEY`](#bck-apikey) — 12 codes * [`BCK.AUTH`](#bck-auth) — 9 codes * [`BCK.BRAINTREE`](#bck-braintree) — 9 codes * [`BCK.COMMON`](#bck-common) — 29 codes * [`BCK.CREDITS`](#bck-credits) — 6 codes * [`BCK.DELEGATION`](#bck-delegation) — 2 codes * [`BCK.GUEST`](#bck-guest) — 2 codes * [`BCK.LEGAL_DOCS`](#bck-legal_docs) — 6 codes * [`BCK.METRIC`](#bck-metric) — 5 codes * [`BCK.NOTIF`](#bck-notif) — 9 codes * [`BCK.OAUTH`](#bck-oauth) — 6 codes * [`BCK.OBSERVABILITY`](#bck-observability) — 5 codes * [`BCK.ORGANIZATIONS`](#bck-organizations) — 15 codes * [`BCK.PAYPAL`](#bck-paypal) — 1 code * [`BCK.PLANS`](#bck-plans) — 1 code * [`BCK.POINT`](#bck-point) — 19 codes * [`BCK.PROTOCOL`](#bck-protocol) — 47 codes * [`BCK.STRIPE`](#bck-stripe) — 32 codes * [`BCK.STRIPE.CONNECT`](#bck-stripe-connect) — 5 codes * [`BCK.TRANSCODING`](#bck-transcoding) — 1 code * [`BCK.TXS`](#bck-txs) — 10 codes * [`BCK.USER_PROFILE`](#bck-user_profile) — 2 codes * [`BCK.VISA`](#bck-visa) — 13 codes * [`BCK.WIDGET`](#bck-widget) — 5 codes * [`BCK.WIDGET_KEYS`](#bck-widget_keys) — 2 codes * [`BCK.WIDGET_SESSION`](#bck-widget_session) — 12 codes * [`BCK.X402`](#bck-x402) — 17 codes

`BCK.AGENT`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | -------- | --------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BCK.AGENT.0001` | 400 | business | — | Invalid Agent Execution Status | | | `BCK.AGENT.0002` | 500 | business | — | Error updating step agent | | | `BCK.AGENT.0003` | 403 | business | — | Unable to create task for agent | | | `BCK.AGENT.0004` | 403 | business | — | Invalid user | | | `BCK.AGENT.0005` | 403 | business | — | Unable to get task for did, task and user | | | `BCK.AGENT.0006` | 403 | business | — | Unable to find tasks by subscriber | | | `BCK.AGENT.0007` | 404 | business | — | Connection not found for clientId | | | `BCK.AGENT.0008` | 500 | business | — | Error registering websocket connection | | | `BCK.AGENT.0009` | 403 | business | — | Error creating steps for agent | | | `BCK.AGENT.0010` | 403 | business | — | Task not found or completed | | | `BCK.AGENT.0011` | 500 | internal | ❌ | Agent lookup failed: agent with the given entryId not found in service layer | The service-layer findOneById returned null. The original behaviour was a generic 500; consumer code that needs 404 semantics should use BCK.PROTOCOL.0004. |

`BCK.APIKEY`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | -------- | --------- | ----------------------------------------------------- | ---- | |
`BCK.APIKEY.0001` | 403 | auth | — | API Key not registered, you need to register it first | | | `BCK.APIKEY.0002` | 404 | auth | — | API Key with given hash not found | | | `BCK.APIKEY.0003` | 401 | auth | — | Unable to revoke API Key | | | `BCK.APIKEY.0004` | 401 | auth | — | Invalid Nevermined API Key | | | `BCK.APIKEY.0006` | 401 | auth | — | Invalid Nevermined Key Metadata | | | `BCK.APIKEY.0007` | 401 | auth | — | API Key issuer does not match user address | | | `BCK.APIKEY.0008` | 403 | auth | — | API Key with hash already exists | | | `BCK.APIKEY.0009` | 500 | auth | — | Error search API Key trannsactions | | | `BCK.APIKEY.0010` | 401 | auth | — | Expiration date must be in the future | | | `BCK.APIKEY.0011` | 500 | auth | — | Error searching user API Keys | | | `BCK.APIKEY.0012` | 401 | auth | — | Unable to generate assertion | | | `BCK.APIKEY.0013` | 401 | auth | — | Malformed API Key payload | |

`BCK.AUTH`

| Code | HTTP | Category | Retryable | Message | Hint | | -------------------- | ---- | -------- | --------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.AUTH.0001` | 500 | auth | — | Error registering API Key | | | `BCK.AUTH.0002` | 401 | auth | — | Authentication required: credentials are missing, expired or invalid | | | `BCK.AUTH.0003` | 403 | auth | — | Forbidden: caller is not the owner of this resource and lacks an admin role | | | `BCK.AUTH.0004` | 403 | auth | ❌ | Organisation admin privileges required | The caller is not an active admin of the target organisation. Prefer the more specific siblings (BCK.AUTH.0005-0009) at new throw sites; this code remains as a catch-all. | | `BCK.AUTH.0005` | 403 | auth | ✅ | Unable to verify organisation membership | The membership lookup itself failed (e.g. transient DB error). Check the cause field on the server log; retry once. | | `BCK.AUTH.0006` | 403 | auth | ❌ | Caller does not belong to any organisation | Add the user as a member of the target organisation before calling admin-scoped endpoints. | | `BCK.AUTH.0007` | 403 | auth | ❌ | Caller does not have administrator privileges | The caller is a member but not an admin. Promote the role via the organisation admin UI / API, or call from an admin account. | | `BCK.AUTH.0008` | 403 | auth | ❌ | Caller account is not active | The user is suspended/disabled. Reactivate via the admin UI before retrying. | | `BCK.AUTH.0009` | 403 | auth | ❌ | Target organisation is not active | The org has been deactivated (subscription lapsed, manual disable, etc.). Reactivate before performing admin operations. |

`BCK.BRAINTREE`

| Code | HTTP | Category | Retryable | Message | Hint | | ------------------------- | ---- | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | |
`BCK.BRAINTREE.0001` | 500 | integration | — | Braintree platform gateway is not configured | | | `BCK.BRAINTREE.0002` | 400 | integration | — | Braintree charge failed | | | `BCK.BRAINTREE.0003` | 502 | integration | — | Braintree OAuth token refresh failed | | | `BCK.BRAINTREE.0004` | 400 | integration | — | Plan owner has not connected a Braintree merchant account | | | `BCK.BRAINTREE.0005` | 400 | integration | — | Plan is not a fiat plan — cannot be purchased via Braintree | | | `BCK.BRAINTREE.0006` | 409 | integration | — | Plan owner has no Braintree merchant account connected | | | `BCK.BRAINTREE.0007` | 400 | integration | — | Plan owner's Braintree account does not have a merchant account in the plan's currency | | | `BCK.BRAINTREE.0008` | 400 | integration | — | Cannot create a Braintree plan in this currency: your Braintree account has no merchant account in that currency. Add one in your Braintree dashboard, then disconnect and reconnect to refresh. | | | `BCK.BRAINTREE.0009` | 400 | integration | — | Plan metadata is missing currency. Plans must specify a currency to be settled via Braintree. | |

`BCK.COMMON`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | ----------- | --------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.COMMON.0001` | 500 | internal | — | Unable to initialize Nevermined instance | | | `BCK.COMMON.0002` | 500 | internal | — | Unable to generate session key | | | `BCK.COMMON.0003` | 500 | internal | — | Unable to retrieve fees information from Nevermined | | | `BCK.COMMON.0004` | 500 | internal | — | Unable to calculate Asset Price | | | `BCK.COMMON.0005` | 500 | internal | — | Unable to load NFT Contract | | | `BCK.COMMON.0006` | 500 | internal | — | Unable to register Credits Plan on Nevermined | | | `BCK.COMMON.0007` | 500 | internal | — | Unable to register Time Plan on Nevermined | | | `BCK.COMMON.0008` | 500 | internal | — | Unable to register Points event in the database | | | `BCK.COMMON.0009` | 500 | internal | — | Unable to register API Key usage in the database | | | `BCK.COMMON.0010` | 500 | internal | — | Unable to register AI Agent | | | `BCK.COMMON.0011` | 500 | internal | — | Unable to register File asset | | | `BCK.COMMON.0012` | 500 | internal | — | Unable to generate access token to AI Agent | | | `BCK.COMMON.0013` | 404 | internal | — | Unable to resolve DDO from DID | | | `BCK.COMMON.0014` | 404 | internal | — | Plan not found | | | `BCK.COMMON.0015` | 500 | internal | — | Unable to get the Plan balance | | | `BCK.COMMON.0016` | 500 | internal | — | Unable to order the subscription | | | `BCK.COMMON.0017` | 500 | internal | — | There are no files associated to the file asset | | | `BCK.COMMON.0018` | 500 | internal | — | An error happened while downloading the asset files | | | `BCK.COMMON.0019` | 500 | internal | — | An error happened while trying to mint credits | | | `BCK.COMMON.0020` | 500 | internal | — | An error happened while trying to burn credits | | | `BCK.COMMON.0021` | 404 | internal | — | Unable to resolve widget metadata from DID | | | `BCK.COMMON.0022` | 403 | internal | — | Method not supported | | | `BCK.COMMON.0023` | 403 | internal | — | Error parsing input | | | `BCK.COMMON.0024` | 401 | internal | — | Could not validate the login claim | | | `BCK.COMMON.0025` | 400 | internal | — | Invalid UUID format in request | | | `BCK.COMMON.0026` | 400 | validation | — | Invalid uint256 identifier (must be a decimal string in \[0, 2^256 - 1]) | | | `BCK.COMMON.0027` | 409 | business | ❌ | Resource already exists (database unique constraint violated) | A record with the same unique key already exists. Either update the existing record or use a different identifier. | | `BCK.COMMON.0028` | 409 | business | ❌ | Referenced resource does not exist (foreign key violation) | The request references an entity (plan, agent, user, organisation) that does not exist or has been deleted. Verify the referenced ID. | | `BCK.COMMON.0029` | 503 | integration | ✅ | Database temporarily unavailable | The database is being restarted or is under maintenance. Retry the request after a short backoff. |

`BCK.CREDITS`

| Code | HTTP | Category | Retryable | Message | Hint | | ----------------------- | ---- | -------- | --------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.CREDITS.0001` | 400 | business | — | Mint amount must be positive | | | `BCK.CREDITS.0002` | 400 | business | — | Burn amount must be positive | | | `BCK.CREDITS.0003` | 402 | business | — | Insufficient credits for plan | | | `BCK.CREDITS.0004` | 400 | business | — | Refund amount must be positive | | | `BCK.CREDITS.0005` | 500 | business | — | Credit lots do not satisfy the burned amount — FIFO invariant violated | | | `BCK.CREDITS.0006` | 500 | internal | ❌ | Cannot enqueue on-chain order mirror without a corresponding minted credit lot | enqueueFiatOrderMirror must be called after a successful creditsService.mint with the same sourceTx. This usually indicates an out-of-order call by a service, not a runtime data issue. |

`BCK.DELEGATION`

| Code | HTTP | Category | Retryable | Message | Hint | | -------------------------- | ---- | -------- | --------- | ------------------------ | ----------------------------------------------------------------------------------------- | |
`BCK.DELEGATION.0001` | 404 | business | ❌ | Payment method not found | Verify the paymentMethodId. The payment method may have been removed or never existed. | | `BCK.DELEGATION.0002` | 404 | business | ❌ | Delegation not found | Verify the delegationId. The delegation may have been revoked, expired, or never existed. |

`BCK.GUEST`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | ---------- | --------- | --------------------------------- | -------------------------------------------------------------------------------------- | |
`BCK.GUEST.0001` | 400 | validation | ❌ | Invalid guest account request | Check the request body — guest provisioning requires either fingerprint or externalId. | | `BCK.GUEST.0002` | 500 | business | — | Failed to provision guest account | |

`BCK.LEGAL_DOCS`

| Code | HTTP | Category | Retryable | Message | Hint | | -------------------------- | ---- | -------- | --------- | --------------------------------------------------------------------- | ---- | |
`BCK.LEGAL_DOCS.0001` | 404 | business | — | Unknown legal document | | | `BCK.LEGAL_DOCS.0002` | 404 | business | — | Unknown legal document version | | | `BCK.LEGAL_DOCS.0003` | 422 | business | — | Submitted legal document version is not the current effective version | | | `BCK.LEGAL_DOCS.0004` | 412 | business | — | Legal consent is required for the current document versions | | | `BCK.LEGAL_DOCS.0005` | 401 | business | — | Authenticated request without a resolvable user identity | | | `BCK.LEGAL_DOCS.0006` | 400 | business | — | No wallet linked to the authenticated Privy account | |

`BCK.METRIC`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | ----------- | --------- | ------------------------------------------- | ---- | |
`BCK.METRIC.0001` | 500 | integration | — | Error registering asset access | | | `BCK.METRIC.0002` | 404 | integration | — | Error search asset metrics | | | `BCK.METRIC.0003` | 404 | integration | — | Error getting info from metrics service for | | | `BCK.METRIC.0004` | 500 | integration | — | Error getting balance for account | | | `BCK.METRIC.0005` | 500 | integration | — | Error getting total hits for account | |

`BCK.NOTIF`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | ----------- | --------- | ------------------------------------------------------ | ---- | |
`BCK.NOTIF.0001` | 404 | integration | — | Unable to find notification by id | | | `BCK.NOTIF.0002` | 404 | integration | — | Error searching for notifications | | | `BCK.NOTIF.0003` | 500 | integration | — | Error updating notification | | | `BCK.NOTIF.0004` | 500 | integration | — | Error updating notification read status | | | `BCK.NOTIF.0005` | 500 | integration | — | Error deleting notification | | | `BCK.NOTIF.0006` | 404 | integration | — | Error searching for notifications filtered by receiver | | | `BCK.NOTIF.0007` | 500 | integration | — | Resend API Key or Email Sender not set | | | `BCK.NOTIF.0008` | 500 | integration | — | Error sending email notification | | | `BCK.NOTIF.0009` | 401 | integration | — | The user doesnt own this notification | |

`BCK.OAUTH`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | -------- | --------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.OAUTH.0001` | 404 | auth | ❌ | OAuth resource not found | Verify the agentId or client identifier in the OAuth request. | | `BCK.OAUTH.0002` | 400 | auth | ❌ | Invalid OAuth request | Check the request parameters — typically the redirect\_uri, scope, or grant\_type does not match the registered client. Prefer the more specific BCK.OAUTH.0003-0006 siblings at new throw sites. | | `BCK.OAUTH.0003` | 400 | business | ❌ | No payment plans available for agent | The target agent has no published plans the caller can purchase. Publish a plan first, or pass an explicit plan\_id that the caller already owns. | | `BCK.OAUTH.0004` | 400 | auth | ❌ | Invalid or expired authorization code | Authorization codes are single-use and short-lived. Restart the flow from /authorize to obtain a fresh code, and ensure the code\_verifier matches the original PKCE code\_challenge. | | `BCK.OAUTH.0005` | 400 | auth | ❌ | Resource mismatch between token request and authorization code | The `resource` parameter on /token does not match the value bound to the authorization code. Resend with the same `resource` you used on /authorize. | | `BCK.OAUTH.0006` | 400 | auth | ❌ | User profile not found for the authorization code | The user bound to the authorization code no longer exists in our DB (deleted profile, or environment mismatch). Restart the flow with a valid signed-in user. |

`BCK.OBSERVABILITY`

| Code | HTTP | Category | Retryable | Message | Hint | | ----------------------------- | ---- | ----------- | --------- | ----------------------------------------------- | ---- | |
`BCK.OBSERVABILITY.0001` | 500 | integration | — | Error fetching observability data from Helicone | | | `BCK.OBSERVABILITY.0002` | 400 | integration | — | Invalid response from Helicone API | | | `BCK.OBSERVABILITY.0003` | 401 | integration | — | Unauthorized access to Helicone API | | | `BCK.OBSERVABILITY.0004` | 429 | integration | — | Rate limit exceeded for Helicone API | | | `BCK.OBSERVABILITY.0005` | 502 | integration | — | Helicone API service unavailable | |

`BCK.ORGANIZATIONS`

| Code | HTTP | Category | Retryable | Message | Hint | | ----------------------------- | ---- | ----------- | --------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.ORGANIZATIONS.0001` | 500 | business | — | Error creating organization member | | | `BCK.ORGANIZATIONS.0002` | 500 | business | — | Error getting organization members | | | `BCK.ORGANIZATIONS.0003` | 500 | business | — | Error saving organization branding | | | `BCK.ORGANIZATIONS.0004` | 500 | business | — | Error updating organization | | | `BCK.ORGANIZATIONS.0005` | 500 | business | — | Error creating organization | | | `BCK.ORGANIZATIONS.0006` | 500 | business | — | Error creating organization with first admin | The two-step bootstrap (create org + add first admin) failed. Check the underlying error in logs; the org may have been created without the admin link. | | `BCK.ORGANIZATIONS.0007` | 500 | auth | — | Login failed for organization | | | `BCK.ORGANIZATIONS.0008` | 403 | business | ❌ | User is already a member of the organization | The membership already exists. Read the membership instead of recreating it, or change the role via the update endpoint. | | `BCK.ORGANIZATIONS.0009` | 403 | business | ❌ | User is not a member of the organization | Add the user as a member before performing membership-scoped operations. | | `BCK.ORGANIZATIONS.0010` | 500 | business | — | Failed to update organization member | | | `BCK.ORGANIZATIONS.0011` | 404 | business | ❌ | Organization not found | Verify the orgId. Soft-deleted organizations also surface as not-found. | | `BCK.ORGANIZATIONS.0012` | 500 | business | — | Failed to deactivate organization | | | `BCK.ORGANIZATIONS.0013` | 500 | business | — | Failed to retrieve updated organization | | | `BCK.ORGANIZATIONS.0014` | 403 | business | ❌ | User already belongs to another organization | A user can only belong to one organization at a time. Remove the user from the current organization before adding them to a new one. | | `BCK.ORGANIZATIONS.0015` | 500 | integration | ✅ | Failed to look up organization (database error) | Repository.findOne returned a driver/connection error rather than null. Inspect the cause field on the server log; if the database is up, this is likely a query timeout or connection-pool exhaustion. Distinct from BCK.ORGANIZATIONS.0011, which signals a confirmed not-found result. |

`BCK.PAYPAL`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | ----------- | --------- | ------------------------------------------------- | ---- | |
`BCK.PAYPAL.0001` | 500 | integration | — | Unexpected error during PayPal/Braintree checkout | |

`BCK.PLANS`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | -------- | --------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.PLANS.0001` | 500 | internal | ❌ | Plan lookup failed: plan with the given entryId not found in service layer | The service-layer findOneById returned null. Original behaviour was a generic 500; consumer code that needs 404 semantics should use BCK.PROTOCOL.0003. |

`BCK.POINT`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------- | ---- | -------- | --------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.POINT.0001` | 401 | business | — | User with address is not allowed to access | | | `BCK.POINT.0002` | 404 | business | — | User with id not found | | | `BCK.POINT.0003` | 404 | business | — | Unable to search user points | | | `BCK.POINT.0004` | 404 | business | — | Unable to search event points | | | `BCK.POINT.0005` | 404 | business | — | Unable to find user points aggregated | | | `BCK.POINT.0006` | 404 | business | — | Points rule not found | | | `BCK.POINT.0007` | 403 | business | ❌ | Points rule recurrency exhausted for user | The rule allows only one (or N) accruals per user; the cap has been reached. | | `BCK.POINT.0008` | 500 | internal | — | Points rule recurrency type not implemented | | | `BCK.POINT.0009` | 500 | internal | ❌ | No points rule matches the supplied price/role | The price/role tiers in points.rules.ts are exhaustive for non-negative inputs; reaching this code typically means a negative or otherwise unexpected price was supplied. The status is 500 to preserve the original "throw new Error" behaviour from before #1538; a follow-up (tracked in the Wave-B issue) will demote this to 400 and add input validation at the API boundary. | | `BCK.POINT.0010` | 403 | business | ❌ | Unsupported points cap recurrency type | The rule references a cap recurrency variant that the engine does not recognise. Update the rule to one of capday/capweek/capmonth. | | `BCK.POINT.0011` | 403 | business | ❌ | Points rule (onlyonce) already accrued by this user | The rule allows exactly one accrual per user and the user already received their share. No retry will succeed. | | `BCK.POINT.0012` | 403 | business | ❌ | Points rule (timeslimitted) per-user lifetime cap reached | The rule allows at most rule.cap accruals per (user, rule); the user has reached that ceiling and the cap does not reset. Note: the throw-site `details` line may mention an item reference for historical reasons — the check is per-user, not per-item. Per-item semantics live in BCK.POINT.0013 (onceperitem). | | `BCK.POINT.0013` | 403 | business | ❌ | Points rule (onceperitem) already accrued for this item | The user already received points for this specific item/reference. Try a different item. | | `BCK.POINT.0014` | 403 | business | ❌ | Points rule (capday/week/month) time-window cap exhausted | The user reached the cap for this rule within the current time window (day/week/month). Wait for the window to roll over. | | `BCK.POINT.0015` | 500 | internal | ❌ | No subscription-price points rule for the supplied price | getRuleIdBySubscriptionPrice() saw a price the tier table does not cover — typically a negative value. Add input validation at the API boundary. | | `BCK.POINT.0016` | 500 | internal | ❌ | No crypto-seller points rule for the supplied price | getRuleIdByCryptoSellerPrice() saw a price the tier table does not cover. | | `BCK.POINT.0017` | 500 | internal | ❌ | No crypto-buyer points rule for the supplied price | getRuleIdByCryptoBuyerPrice() saw a price the tier table does not cover. | | `BCK.POINT.0018` | 500 | internal | ❌ | No fiat-seller points rule for the supplied price | getRuleIdByFiatSellerPrice() saw a price the tier table does not cover. | | `BCK.POINT.0019` | 500 | internal | ❌ | No fiat-buyer points rule for the supplied price | getRuleIdByFiatBuyerPrice() saw a price the tier table does not cover. |

`BCK.PROTOCOL`

| Code | HTTP | Category | Retryable | Message | Hint | | ------------------------ | ---- | ----------- | --------- | --------------------------------------------------------------------------------------------------------------------- | ---- | |
`BCK.PROTOCOL.0001` | 500 | integration | — | Unable to register payment plan | | | `BCK.PROTOCOL.0002` | 500 | integration | — | Unable to register agent | | | `BCK.PROTOCOL.0003` | 404 | integration | — | Unable to get payment plan by planId | | | `BCK.PROTOCOL.0004` | 404 | integration | — | Unable to get agent by agentId | | | `BCK.PROTOCOL.0005` | 500 | integration | — | Error ordering plan | | | `BCK.PROTOCOL.0006` | 500 | integration | — | Error getting balance of plan | | | `BCK.PROTOCOL.0007` | 500 | integration | — | Error minting plan | | | `BCK.PROTOCOL.0008` | 500 | integration | — | Error deleting plan from agent | | | `BCK.PROTOCOL.0009` | 500 | integration | — | Error adding plan to agent | | | `BCK.PROTOCOL.0010` | 403 | integration | — | Invalid credits type | | | `BCK.PROTOCOL.0011` | 403 | integration | — | Insufficient balance | | | `BCK.PROTOCOL.0012` | 500 | integration | — | Error updating agent | | | `BCK.PROTOCOL.0013` | 500 | integration | — | Error updating plan | | | `BCK.PROTOCOL.0014` | 500 | integration | — | Error de-activating agent | | | `BCK.PROTOCOL.0015` | 500 | integration | — | Error de-activating plan | | | `BCK.PROTOCOL.0016` | 401 | integration | — | The user doesnt own this agent | | | `BCK.PROTOCOL.0017` | 401 | integration | — | The user doesnt own this plan | | | `BCK.PROTOCOL.0018` | 200 | integration | — | The agent is already in the desired state | | | `BCK.PROTOCOL.0019` | 200 | integration | — | The plan is already in the desired state | | | `BCK.PROTOCOL.0020` | 500 | integration | — | Error redeming credits | | | `BCK.PROTOCOL.0021` | 500 | integration | — | Error getting user plans | | | `BCK.PROTOCOL.0022` | 404 | integration | — | Error getting user agents | | | `BCK.PROTOCOL.0023` | 404 | integration | — | Error getting plan associated to agent. Agent not found | | | `BCK.PROTOCOL.0024` | 404 | integration | — | Error getting agent associated to plan. Plan not found | | | `BCK.PROTOCOL.0025` | 500 | integration | — | Unable to generate agent access token | | | `BCK.PROTOCOL.0026` | 403 | integration | — | The agent doesnt include the plan specified | | | `BCK.PROTOCOL.0027` | 403 | integration | — | Unable to validate access token | | | `BCK.PROTOCOL.0028` | 403 | integration | — | Invalid agent ID in access token | | | `BCK.PROTOCOL.0029` | 403 | integration | — | Proof is required for this plan | | | `BCK.PROTOCOL.0030` | 403 | integration | — | Invalid proof | | | `BCK.PROTOCOL.0031` | 403 | integration | — | Endpoint not included in the agent api | | | `BCK.PROTOCOL.0032` | 500 | integration | — | Unable to track access transaction | | | `BCK.PROTOCOL.0033` | 403 | integration | — | You do not have permission to track access transactions for this owner | | | `BCK.PROTOCOL.0034` | 500 | integration | — | Unable to track access processor queue entry | | | `BCK.PROTOCOL.0035` | 500 | integration | — | Unable to track agent task | | | `BCK.PROTOCOL.0036` | 500 | integration | — | Unable to track agent processor queue entry | | | `BCK.PROTOCOL.0037` | 404 | integration | — | Agent task not found | | | `BCK.PROTOCOL.0038` | 500 | integration | — | Error updating agent task | | | `BCK.PROTOCOL.0039` | 500 | integration | — | Error redeeming credits and updating agent task | | | `BCK.PROTOCOL.0040` | 500 | integration | — | Unable to register agent and plan | | | `BCK.PROTOCOL.0041` | 403 | integration | — | The user doesnt have a valid Stripe account enabled | | | `BCK.PROTOCOL.0042` | 403 | integration | — | Either amount or marginPercent must be provided, but not both | | | `BCK.PROTOCOL.0043` | 403 | integration | — | Plan does not have valid price configuration for margin calculation. Credits type must be DYNAMIC. | | | `BCK.PROTOCOL.0044` | 404 | integration | — | No Helicone request found for agent request ID | | | `BCK.PROTOCOL.0045` | 500 | integration | — | Error getting all plans | | | `BCK.PROTOCOL.0046` | 400 | integration | — | Fiat plan price exceeds the maximum allowed (\$999,999.99). Stripe does not support payment intents above this limit. | | | `BCK.PROTOCOL.0047` | 400 | integration | — | Fiat plan price is below the minimum allowed (\$1.00). Lower prices do not cover the payment-processor fixed fee. | |

`BCK.STRIPE`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.STRIPE.0001` | 400 | integration | — | Error creating Stripe account | | | `BCK.STRIPE.0002` | 400 | integration | — | Error creating Stripe checkout session | | | `BCK.STRIPE.0003` | 400 | integration | — | Error creating Stripe payment intent | | | `BCK.STRIPE.0004` | 500 | integration | — | Error processing Stripe account webhook for updating an account | | | `BCK.STRIPE.0005` | 400 | integration | — | Error processing Stripe connect webhook | | | `BCK.STRIPE.0006` | 500 | integration | — | Error processing Stripe checkout event with error | | | `BCK.STRIPE.0007` | 400 | integration | — | Invalid input params | | | `BCK.STRIPE.0008` | 400 | integration | — | Stripe event not handled | | | `BCK.STRIPE.0009` | 400 | integration | — | The plan indicated is not valid for Stripe payment | | | `BCK.STRIPE.0010` | 400 | integration | — | The plan is not a Fiat plan | | | `BCK.STRIPE.0011` | 400 | integration | — | Error calculating plan checkout price | | | `BCK.STRIPE.0012` | 400 | integration | — | The account selling the plan is not properly configured to accept Stripe payments | | | `BCK.STRIPE.0013` | 400 | integration | — | Payment intent not succeeded | | | `BCK.STRIPE.0014` | 400 | integration | — | Invalid payment amount from payment intent | | | `BCK.STRIPE.0015` | 400 | integration | — | Customer not found | | | `BCK.STRIPE.0016` | 400 | integration | — | Subscription not found | | | `BCK.STRIPE.0017` | 400 | integration | — | Invoices not found | | | `BCK.STRIPE.0018` | 400 | integration | — | Error retrieving Stripe payment metadata | | | `BCK.STRIPE.0019` | 400 | integration | — | Error retrieving Stripe balance | | | `BCK.STRIPE.0020` | 500 | integration | — | Error canceling subscription | | | `BCK.STRIPE.0021` | 400 | integration | — | Unable to create Stripe subscription | | | `BCK.STRIPE.0022` | 424 | integration | — | The settlement could not be executed because the seller account has not properly configured the payment service provider (Stripe) | | | `BCK.STRIPE.0023` | 503 | integration | — | Transient failure while looking up the seller payment service provider configuration | | | `BCK.STRIPE.0030` | 500 | integration | — | Application-fee true-up refund failed; row left Settled with the owed amount stored in providerMetadata.trueUpRefundOwedMicro for manual reconciliation | | | `BCK.STRIPE.0031` | 500 | internal | ❌ | Price conversion overflow when converting micro-units to cents | The plan price exceeds Number.MAX\_SAFE\_INTEGER after conversion. Lower the plan price or fix the unit boundary in convertMicroUnitsToCents. | | `BCK.STRIPE.0032` | 500 | integration | ❌ | Stripe checkout: user profile not found for account event | The Stripe account event referenced a user that no longer exists in our DB (deleted profile, or environment mismatch between live/sandbox). The webhook is marked permanent so Stripe stops retrying. | | `BCK.STRIPE.0033` | 500 | integration | ✅ | Stripe account webhook handler failed | Generic catch-all for the account webhook handler — inspect the cause field for the underlying error and the params for eventId/stripeAccountId/userId. | | `BCK.STRIPE.0034` | 500 | integration | ✅ | Stripe payment intent webhook handler failed | Generic catch-all for the payment intent webhook handler — inspect the cause and params.eventId. | | `BCK.STRIPE.0035` | 500 | integration | ✅ | Stripe subscription invoice webhook handler failed | Generic catch-all for the subscription invoice (recurring) webhook handler — inspect the cause and params.eventId. | | `BCK.STRIPE.0036` | 400 | business | ❌ | Stripe subscription creation: plan has no Stripe priceId | The plan DDO is missing metadata.plan.priceId. Re-publish the plan with a Stripe price configured, or use the one-shot payment intent flow. | | `BCK.STRIPE.0037` | 400 | integration | ✅ | Stripe subscription creation: no latest invoice on subscription | The newly-created subscription did not return a latest\_invoice. Retry once; if it persists, check Stripe dashboard for the subscription state. | | `BCK.STRIPE.0038` | 400 | integration | ✅ | Stripe subscription creation: no payment intent on subscription | The subscription invoice did not yield a payment\_intent. This usually means the customer has no default payment method. Confirm the SetupIntent has succeeded before creating the subscription. |

`BCK.STRIPE.CONNECT`

| Code | HTTP | Category | Retryable | Message | Hint | | ------------------------------ | ---- | ----------- | --------- | -------------------------------------------------------------- | ---- | |
`BCK.STRIPE.CONNECT.0001` | 503 | integration | — | Stripe Connect is not configured | | | `BCK.STRIPE.CONNECT.0002` | 400 | integration | — | Stripe OAuth token exchange failed | | | `BCK.STRIPE.CONNECT.0003` | 400 | integration | — | Stripe OAuth response missing stripe\_user\_id | | | `BCK.STRIPE.CONNECT.0004` | 400 | integration | — | Stripe account environment does not match platform environment | | | `BCK.STRIPE.CONNECT.0005` | 404 | integration | — | User profile not found for Stripe Connect update | |

`BCK.TRANSCODING`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------------- | ---- | ----------- | --------- | ------------- | ---- | |
`BCK.TRANSCODING.0001` | 404 | integration | — | UGC not found | |

`BCK.TXS`

| Code | HTTP | Category | Retryable | Message | Hint | | ------------------- | ---- | ----------- | --------- | --------------------------------------------------- | ---- | |
`BCK.TXS.0001` | 404 | integration | — | Error searching asset transactions by id | | | `BCK.TXS.0002` | 404 | integration | — | Error searching asset consumer transactions | | | `BCK.TXS.0003` | 404 | integration | — | Error searching distintc asset transactions | | | `BCK.TXS.0004` | 404 | integration | — | Error searching plan transactions | | | `BCK.TXS.0005` | 404 | integration | — | Error searching asset transactions grouped by owner | | | `BCK.TXS.0006` | 404 | integration | — | Error gathering DDO Info | | | `BCK.TXS.0007` | 404 | integration | — | Error getting active users for owner | | | `BCK.TXS.0008` | 404 | integration | — | Error getting total API calls for owner | | | `BCK.TXS.0009` | 404 | integration | — | Error getting total revenue for owner | | | `BCK.TXS.0010` | 404 | integration | — | Error getting dashboard metrics for owner | |

`BCK.USER_PROFILE`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------------- | ---- | -------- | --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | |
`BCK.USER_PROFILE.0001` | 500 | internal | ❌ | User profile not found | Verify the entryId. Note: legacy callers depended on this surfacing as 500; the canonical "user profile not found for caller-supplied identifier" lookup-not-found semantics live in BCK.USER\_PROFILE.0002 (404). | | `BCK.USER_PROFILE.0002` | 404 | business | ❌ | User profile not found | Verify the user identifier. The profile may have been disabled or never created. |

`BCK.VISA`

| Code | HTTP | Category | Retryable | Message | Hint | | -------------------- | ---- | ----------- | --------- | --------------------------------------------------------- | ---- | |
`BCK.VISA.0001` | 503 | integration | — | Visa payment provider is not configured | | | `BCK.VISA.0002` | 502 | integration | — | Visa card enrollment failed | | | `BCK.VISA.0003` | 502 | integration | — | Visa mandate creation failed | | | `BCK.VISA.0004` | 502 | integration | — | VGS cryptogram issuance failed | | | `BCK.VISA.0005` | 400 | integration | — | Invalid VGS webhook signature | | | `BCK.VISA.0006` | 502 | integration | — | VGS OAuth2 client\_credentials request failed | | | `BCK.VISA.0007` | 502 | integration | — | Stripe settlement of Visa virtual card failed | | | `BCK.VISA.0008` | 400 | integration | — | User has no email on file (required for Visa enrolment) | | | `BCK.VISA.0009` | 422 | integration | — | Visa mandate (intent) not provisioned for this delegation | | | `BCK.VISA.0010` | 404 | integration | — | VGS webhook references unknown card | | | `BCK.VISA.0011` | 400 | integration | — | VGS webhook payload malformed | | | `BCK.VISA.0012` | 400 | integration | — | VGS webhook revoke event missing card identifier | | | `BCK.VISA.0013` | 500 | integration | — | Unable to process VGS webhook | |

`BCK.WIDGET`

| Code | HTTP | Category | Retryable | Message | Hint | | ---------------------- | ---- | -------- | --------- | ----------------------------------- | ---- | |
`BCK.WIDGET.0001` | 500 | business | — | Unable to store widget config | | | `BCK.WIDGET.0002` | 404 | business | — | Unable to get widget config from id | | | `BCK.WIDGET.0003` | 401 | business | — | The user doesnt own this widget | | | `BCK.WIDGET.0004` | 500 | business | — | Error updating widget config | | | `BCK.WIDGET.0005` | 500 | business | — | Error deleting widget config | |

`BCK.WIDGET_KEYS`

| Code | HTTP | Category | Retryable | Message | Hint | | --------------------------- | ---- | -------- | --------- | ---------------------------- | ---- | |
`BCK.WIDGET_KEYS.0001` | 500 | internal | ❌ | Widget key generation failed | | | `BCK.WIDGET_KEYS.0002` | 404 | auth | — | Widget key not found | |

`BCK.WIDGET_SESSION`

| Code | HTTP | Category | Retryable | Message | Hint | | ------------------------------ | ---- | -------- | --------- | ---------------------------------------------------------- | ---- | |
`BCK.WIDGET_SESSION.0001` | 401 | auth | — | Invalid or expired widget init token | | | `BCK.WIDGET_SESSION.0002` | 401 | auth | — | Organization has no active widget key | | | `BCK.WIDGET_SESSION.0003` | 500 | auth | — | Widget JWT secret not configured | | | `BCK.WIDGET_SESSION.0004` | 500 | auth | — | Error creating widget session user | | | `BCK.WIDGET_SESSION.0005` | 401 | auth | — | Invalid or expired widget session token | | | `BCK.WIDGET_SESSION.0006` | 500 | auth | — | Widget encryption key not configured or invalid | | | `BCK.WIDGET_SESSION.0007` | 401 | auth | — | Widget init token is missing required fields | | | `BCK.WIDGET_SESSION.0008` | 401 | auth | — | Widget session token is missing required wallet claim | | | `BCK.WIDGET_SESSION.0009` | 401 | auth | — | Widget session token is missing required apiKeyHash claim | | | `BCK.WIDGET_SESSION.0010` | 401 | auth | — | Widget session token is missing required widgetKeyId claim | | | `BCK.WIDGET_SESSION.0011` | 401 | auth | — | Widget key has been revoked or no longer exists | | | `BCK.WIDGET_SESSION.0012` | 403 | auth | — | Origin not allowed for this widget key | |

`BCK.X402`

| Code | HTTP | Category | Retryable | Message | Hint | | -------------------- | ---- | -------- | --------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
`BCK.X402.0001` | 404 | business | — | Agent not found | | | `BCK.X402.0002` | 404 | business | — | Plan not found | | | `BCK.X402.0003` | 400 | business | — | The plan is not associated to the agent | | | `BCK.X402.0004` | 500 | business | — | Error generating X402 access token | | | `BCK.X402.0005` | 402 | business | — | Invalid access token | | | `BCK.X402.0006` | 500 | business | — | Error verifying permissions | | | `BCK.X402.0007` | 500 | business | — | Failed to order Pay-as-you-go plan | | | `BCK.X402.0008` | 500 | business | — | Failed to order crypto plan | | | `BCK.X402.0009` | 500 | business | — | Failed to redeem credits | | | `BCK.X402.0010` | 400 | business | — | Invalid x402 access token | | | `BCK.X402.0011` | 404 | business | — | User profile not found | | | `BCK.X402.0012` | 400 | business | — | resource.url is required when agentId is provided | | | `BCK.X402.0013` | 400 | business | — | Accepted payment method does not match requirements | | | `BCK.X402.0014` | 400 | business | — | Delegation restricted to a different plan | | | `BCK.X402.0015` | 404 | business | ❌ | Permission not found | The permission record either has been revoked or never existed for this combination of (subscriber, plan, agent). | | `BCK.X402.0016` | 400 | business | ❌ | Permission is already revoked | The permission was already revoked. No further action required. | | `BCK.X402.0017` | 500 | internal | ❌ | Failed to issue credits after card charge | The card was charged successfully but the DB-side mint of credits failed. The charge is auto-refunded when the provider supports it; otherwise a `failed_post_charge_*` delegationTransactions row is left for manual reconciliation. | # API error model Source: https://docs.nevermined.app/docs/development-guide/api-errors/overview Understand the structure, codes, and remediation hints returned by the Nevermined API when a request fails. Every error response from the Nevermined API follows a single, stable shape so your code can branch on `code`, surface a friendly message to the user, and link directly to the docs entry that explains the failure. Locked fields — `code`, `message`, and the HTTP status — are guaranteed for every error response. Additive fields (`hint`, `docsUrl`, `category`, `retryable`, `correlationId`) only appear when the catalogue or the throw site supplies them. Consumers that branch only on `code` and `message` ignore the rest transparently. ## Response shape ```jsonc theme={null} { // Locked — always present. SDKs read these. "code": "BCK.X402.0008", "message": "Failed to order crypto plan", // Additive — present when the catalogue or the throw site supplies them. "details": "Bundler returned execution-reverted", "hint": "Verify the wallet has sufficient balance and that the plan is active. Inspect params.planId and the bundler RPC logs around the request correlationId.", "docsUrl": "https://docs.nevermined.ai/docs/development-guide/api-errors/codes#bck-x402-0008", "category": "integration", "retryable": true, "correlationId": "a3f6b1c4-7d2e-4a9b-8e0c-12f4d8e6c5b9", "uuid": "e-550e8400-e29b-41d4-a716-446655440000", "date": "2026-05-09T12:34:56.789Z", // JSON-stringified contextual params from the throw site (parse with JSON.parse if needed). "params": "{\"planId\":\"43298432984329\"}" // Server stack — only emitted when NODE_ENV is "development" or "test". // "stackTrace": "..." } ``` ## Field reference Stable identifier of the form `BCK..`. Branch on this in your error handlers — it never changes for a given failure mode. Human-readable canonical reason. Safe to surface to end users; it never includes secrets. Actionable remediation. Tells you what to fix or where to look. Optional — present when the catalogue or throw site supplies one. Permalink to the entry on this docs site. Surface as a "Learn more" link in your UI. One of `validation`, `auth`, `business`, `integration`, `internal`. Useful for client-side fallback strategies without parsing the message. `true` when the same call is expected to succeed after a backoff. Absent when the catalogue does not assert either way. Request-scoped id stamped by the global filter and echoed in the `x-correlation-id` response header. Quote this when reporting issues. JSON-stringified contextual data from the throw site (IDs, state). Always a string in the wire format — parse with `JSON.parse` if you need structured access. Never includes credentials or PII. Free-form technical reason supplied at the throw site (e.g. `"Bundler returned execution-reverted"`). Supplements — never replaces — the canonical `message`. Optional. Server-generated exception id of the form `e-`. Distinct from `correlationId`; use it when correlating a specific failure event with server logs. Optional. ISO-8601 timestamp the API stamped when the exception was constructed. Always present. Server-side stack trace. Emitted **only** when `NODE_ENV` is `development` or `test` — never in `staging` or `production`. Do not depend on it from client code. ## Reading errors from the SDK Both first-party SDKs surface the API's `code` and `message` directly, so you can branch on the code in your application. ```typescript theme={null} import { Payments, PaymentsError } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'sandbox', }) try { await payments.plans.orderPlan(planId) } catch (err) { if (err instanceof PaymentsError) { // err.code === 'BCK.X402.0008' // err.message === 'Failed to order crypto plan. ' if (err.code === 'BCK.X402.0008') { showRetryableError(err.message) } else { throw err } } } ``` ```python theme={null} import os from payments_py import Payments, PaymentOptions from payments_py.common.payments_error import PaymentsError payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment="sandbox", ) ) try: payments.plans.order_plan(plan_id) except PaymentsError as err: if err.code == "BCK.X402.0008": # err.args[0] / str(err) is "Failed to order crypto plan. " show_retryable_error(str(err)) else: raise ``` ## Correlation ids Every request gets a unique `correlationId`: * If you send `x-correlation-id` (or `x-request-id`) on the request, the API honours it. * Otherwise the API generates a fresh UUID. * Either way, the id is echoed back in the `x-correlation-id` response header **and** stamped into the JSON body of any error response. When you open a support ticket or file a bug, quote the `correlationId` — it lets us pull the matching server logs without you sharing the request payload. ## Categories at a glance Caller's request did not pass schema or shape validation. Fix the request and retry. **Not** retryable as-is. Caller is unauthenticated, the credentials are invalid, or the caller lacks the required role. Fix credentials or membership and retry. Caller's request is well-formed and authenticated, but a business rule blocks it (e.g. user already a member, plan already deactivated, points cap exhausted). Adjust the request semantics. The API hit an upstream system that misbehaved (Stripe, Braintree, the blockchain RPC, the bundler). Often `retryable: true`. Backoff and retry. A bug or invariant violation on our side. Quote the `correlationId` in a support ticket — these need engineering attention. ## Full code catalogue The complete list of every code, its HTTP status, category, message, and remediation hint lives in the **API Errors → Codes** page under the **API** tab. Browse all `BCK.*` codes — anchored by code so you can deep-link to a specific failure mode (`#bck-x402-0008`). A handful of catalogue entries return an HTTP 2xx status because they describe **informational no-op outcomes** rather than failures — for example `BCK.PROTOCOL.0018` ("agent already in the desired state") and `BCK.PROTOCOL.0019` ("plan already in the desired state") are emitted with `200 OK` when a toggle endpoint is called against state that already matches the request. The shape is identical to the error responses described above, so the same SDK handlers and `docsUrl` deep-links continue to work; consumers that branch on `code` can treat these as "already done, nothing to do". This page is reachable from three places in the sidebar so you can find it from wherever you're working: * **API → API Errors** — the canonical home, alongside the endpoint reference. * **Guides → API Errors** — for readers exploring concepts. * **SDK → TypeScript SDK / Python SDK / CLI / OpenClaw Plugin** — at the bottom of each SDK group, since SDK consumers see these errors most often. # Docs 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 nevermined-docs https://docs.nevermined.app/mcp ``` ```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 nevermined-docs https://docs.nevermined.app/mcp ``` **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! # AI Coding 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 **Commands to install** from the Nevermined repository: ```bash theme={null} mkdir -p ~/.claude/skills tmpdir="$(mktemp -d)" git clone --depth 1 --filter=blob:none --sparse https://github.com/nevermined-io/docs "$tmpdir" cd "$tmpdir" git sparse-checkout set skills/nevermined-payments cp -R skills/nevermined-payments ~/.claude/skills/ cd - rm -rf "$tmpdir" ``` 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 Account-based settlement * How subscribers generate and sign x402 payment proofs * How delegations, 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/integrate/patterns/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 Extensions Nevermined introduces two x402 schemes for different payment rails: | Scheme | Network | Use Case | Settlement | | --------------------- | -------------- | ---------------- | ---------------------------------- | | `nvm:erc4337` | `eip155:84532` | Crypto payments | ERC-4337 UserOps + session keys | | `nvm:card-delegation` | `stripe` | Fiat/credit card | Stripe PaymentIntent + credit burn | For the complete delegation specification, see the [Delegation Spec](/docs/specs/x402-card-delegation). ### `nvm:erc4337` — Smart Account Extension The `nvm:erc4337` 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. The scheme depends on the plan's pricing configuration: **Crypto plan (`nvm:erc4337`):** ```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": {} } ``` **Fiat plan (`nvm:card-delegation`):** ```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:card-delegation", "network": "stripe", "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. Token generation requires a `delegationConfig` that controls spending limits and duration. You can either auto-create a delegation inline (Pattern A) or create one explicitly and reuse its ID (Pattern B). ```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] // Pattern A: Auto-create a delegation inline const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId, { delegationConfig: { spendingLimitCents: 10000, durationSecs: 604800 } }) // Pattern B: Create a delegation explicitly, then reuse its ID const delegation = await payments.delegation.createDelegation({ provider: 'erc4337', spendingLimitCents: 10000, durationSecs: 604800 }) const { accessToken: token } = await payments.x402.getX402AccessToken(planId, agentId, { delegationConfig: { delegationId: delegation.delegationId } }) } ``` ```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") # Pattern A: Auto-create a delegation inline token_res = payments.x402.get_x402_access_token(plan_id, agent_id, { 'delegationConfig': { 'spendingLimitCents': 10000, 'durationSecs': 604800 } }) access_token = token_res["accessToken"] # Pattern B: Create a delegation explicitly, then reuse its ID delegation = payments.delegation.create_delegation({ 'provider': 'erc4337', 'spendingLimitCents': 10000, 'durationSecs': 604800 }) token_res = payments.x402.get_x402_access_token(plan_id, agent_id, { 'delegationConfig': { 'delegationId': delegation['delegationId'] } }) access_token = token_res["accessToken"] ``` The SDK auto-detects the payment scheme from plan metadata. For fiat plans (`isCrypto: false`), use `resolveScheme()` (TypeScript) or `resolve_scheme()` (Python) to determine the correct scheme. For fiat plans, use `provider: 'stripe'` in the delegation; for crypto plans, use `provider: 'erc4337'`. Middleware handles this automatically — see the [Express.js](/docs/integrate/add-to-your-agent/express) and [FastAPI](/docs/integrate/add-to-your-agent/fastapi) integration guides. ### 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. The middleware automatically detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) use `nvm:card-delegation` (Stripe). **No code changes are needed on the agent side.** You can explicitly override with the `scheme` parameter in the route configuration. ### 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` and `nvm:card-delegation` schemes * **Delegations** provide a unified permission model for both crypto (`nvm:erc4337`) and fiat (`nvm:card-delegation`) schemes, with auto-detection from plan metadata * **Subscribers** delegate controlled permissions using `DelegationConfig` (spending limits and duration) * **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 Learn how Nevermined adds payments and access control to AI agents, MCP tools, and static assets with payment plans, metering, and per-request entitlement. Nevermined adds a payment and entitlement layer to any **monetizable service** (agent APIs, MCP tools, or static assets). Attach a **payment plan** and each inbound request is validated, metered, and settled in real time. No checkout flows, no manual invoicing, no distinction between human and agent callers. ## What Can You Monetize? Nevermined validates each inbound request against the caller’s entitlement before your code runs: * **Agent APIs** - AI agents exposed as HTTP endpoints (REST, webhooks, etc.) * **MCP Tools** - tool endpoints served via the Model Context Protocol * **Static Assets** - gated resources like files, datasets, or downloads Every monetizable service has: * A **public endpoint** (the URL callers hit) * A **payment plan** (pricing and access terms) * A **validation step** (Nevermined checks entitlement per request) * **Usage tracking** (automatic metering on every call) ## Standards and protocols (x402) Nevermined supports modern payment standards used in agentic 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 defines how users pay and what they're entitled to consume: [**Cards**](/docs/products/nvm-pay/overview) (Visa or Stripe via NVM Pay) or [**Crypto**](/docs/integrate/patterns/stablecoin-payments) (USDC, EURC on-chain). **Fixed** (same cost per call) or **Dynamic** (builder-defined per request, or cost+margin). **Prepaid** (buy upfront) or **Pay-as-you-go** (charge per request). Merchants receive fiat payouts via [Stripe Connect](https://stripe.com/connect) or crypto directly to their wallet. Connect your account in the [Nevermined App](https://nevermined.app). Users buy a plan upfront. Each API call deducts from their balance: * **Fixed pricing**: every request costs the same (e.g., \$10 for 100 requests at \$0.10 each) * **Dynamic pricing**: charge different amounts per request based on complexity, or apply cost+margin automatically No upfront purchase. Charge the user's card or wallet on each request: * **Cards**: agents charge via card delegation ([NVM Pay](/docs/products/nvm-pay/overview)) * **Crypto**: settle in USDC or EURC on-chain Add expiration to any plan: * **Duration**: set expiry (days, months, years) * **Hybrid**: combine usage limits with time limits (e.g., 1,000 requests valid for 30 days) Charge on success: meetings booked, leads qualified, issues resolved. Only bill when your agent delivers a defined outcome or milestone. ### Examples * **Customer Support Agent**: \$49/month unlimited, or \$10 for 1,000 calls * **Dataset Access**: \$99 for 30-day access * **Agent API**: \$0.10/call prepaid, or \$0.15/call PAYG * **Sales Agent**: \$5 per qualified lead (outcome-based) ## How It All Works Together Define your service interface and metadata using the Payments SDK. Set payment method, pricing model, and plan type using the Payments SDK. Purchase a prepaid or pay-as-you-go plan, paid with card ([NVM Pay](/docs/products/nvm-pay/overview)) or crypto. Validate entitlement before serving requests or granting access. Automatically track usage per request and settle payments. ## Payment Methods Pay with real Visa or Stripe cards via [NVM Pay](/docs/products/nvm-pay/overview). Agents charge against spending mandates with per-transaction ceilings and usage limits. No crypto wallet needed. USDC, EURC, or any ERC-20 token on-chain. Lowest fees and full transparency for crypto-native users. ## Merchant Onboarding Merchants receive fiat payouts via [Stripe Connect](https://stripe.com/connect) or crypto directly to their wallet. Connect your account in the [Nevermined App](https://nevermined.app). ISO 27001, SOC 2 Type II, PCI SAQ-D, GDPR compliant. Card data captured via PCI Level 1 infrastructure (VGS). Nevermined never stores raw card numbers. # FAQ Source: https://docs.nevermined.app/docs/getting-started/faq Frequently asked questions about Nevermined, NVM Pay, Stripe Connect, payments, and card delegation. ## NVM Pay How agents pay with your real card, within limits you control. NVM Pay lets users enroll a Visa or Stripe card, set spending limits, and let agents charge it directly via x402. No checkout pages, no human in the loop. Manage everything at [pay.nevermined.app](https://pay.nevermined.app). NVM Pay uses your real credit or debit card, not a separate prepaid balance. At enrollment, the card number is tokenized by a PCI-compliant vault. Nevermined never sees or stores the raw card number. What the agent receives is a scoped credential that encodes identity, spending limits, and merchant restrictions. The charge appears on your normal statement. Only within the limits you set. When you create a mandate, you define an amount ceiling, maximum number of transactions, and expiration date. A per-card spending ceiling applies across all active mandates. The agent can only charge within these bounds, and you can cancel a mandate at any time. The transaction is rejected instantly before it reaches the payment processor. Mandates are enforced server-side on every request. The agent receives a `402` error explaining which limit was hit (amount cap, transaction count, or time window). One click in the [dashboard](https://pay.nevermined.app) or one API call. Revocation is immediate: no grace period, no cached tokens. The agent loses payment capability the instant you revoke. No. NVM Pay works with regular Visa and Stripe cards. All payments are settled via card rails. No wallet or stablecoins required. Three ways, from simplest to most explicit: 1. **Automatic** — If you have exactly one active mandate, it's selected automatically. 2. **Key-linked** — Link a specific mandate to a specific API key. The system always routes that key to its linked mandate. 3. **Explicit ID** — Pass the mandate ID directly in the request for full control. See [Mandate Selection](/docs/products/nvm-pay/mandate-selection) for details. During the pilot, each enrolled card has a \$10.00 cumulative ceiling across all active mandates. Contact the Nevermined team to request increased limits. From the agent's perspective, nothing changes — same x402 payment header either way. The difference is settlement: * **Stripe** — Card tokenized by Stripe, payment settles through Stripe's network. * **Visa** — Card tokenized by Visa Token Service (VTS), each payment generates a one-time VIC cryptogram that settles through standard Visa acquiring rails. Stripe supports most Visa, Mastercard, Amex, and Discover cards. For the Visa path, virtual cards can currently be issued for Visa cards from a limited number of US issuers (excluding Chase, Citi). Most corporate and prepaid cards are not eligible for the Visa path. Card data is captured via a PCI-compliant vault (VGS) and tokenized before entering our system. Nevermined holds ISO 27001 and SOC 2 Type II certifications and operates at PCI SAQ-D level. Authentication uses FIDO2 passkeys or 3DS at enrollment. Every transaction is logged for full auditability. ## Stripe Connect Nevermined uses [Stripe Connect](https://stripe.com/connect) to route fiat payments directly to your Stripe account. You need to connect a Stripe account before you can receive credit card payments from your users. **Stripe Connect** is for *builders* receiving fiat payouts. You connect your Stripe account so Nevermined can route revenue to you. **NVM Pay** is for *users* who want agents to pay with their card. The user enrolls a card, creates a spending mandate, and agents charge against it via x402. They serve different sides of the transaction: Stripe Connect handles payouts to builders, NVM Pay handles card-based payments from users. Open the [Nevermined App](https://nevermined.app) and navigate to **Settings > User Profile**. Scroll down to the **Stripe** section. Click the **Connect Stripe** button. This opens Stripe's onboarding flow where you'll set up or link your Stripe account. In the sandbox environment, you'll see a banner saying "You're using a test account with test data." Click **Use test phone number** to skip SMS verification. Stripe Connect phone verification step Click **Use test code** to auto-fill the test code `000000` and continue. Stripe Connect test code step Select **Individual** from the business type dropdown and click **Continue**. Stripe Connect business type step Use these test values: | Field | Test value | | ---------------- | ------------------ | | Legal first name | `Sarah` | | Legal last name | `Mitchell` | | Date of birth | `15 / 03 / 1990` | | Address | `350 Fifth Avenue` | | City | `New York` | | State | `New York` | | Zip code | `10118` | | Last 4 SSN | `0000` | Use `0000` for the last 4 digits of SSN. Any other value may trigger Stripe's additional identity verification flow. Stripe Connect personal details step Click **Use test account** to auto-fill the test routing number (`110000000`) and account number (`000123456789`), then click **Continue**. Stripe Connect bank details step Review everything and click **Agree and submit**. You'll be redirected back to the Nevermined App and see a "Stripe successfully connected" confirmation. Stripe Connect review and submit step No. If you don't have one, Stripe's onboarding flow will create a new account for you. No. Only if you want to accept fiat (credit card) payments. Plans that only accept stablecoin or crypto don't need a Stripe account. When a user pays with a credit card, Nevermined routes the payment to your connected Stripe account. Fees (Nevermined's 2% + Stripe's processing fees) are deducted automatically. Stripe handles the payout schedule to your bank. Yes. Sandbox transactions use [Stripe test cards](https://stripe.com/docs/testing#international-cards), so no real money is charged. See the [full list of Stripe test values](https://docs.stripe.com/connect/testing?accounts-namespace=v2). Yes. Go to **Settings > Stripe Connect** in the Nevermined App. Any pending payouts will still be routed to the previously connected account. ## General Nevermined adds a payment and entitlement layer to any agent API or MCP tool. Each inbound request is validated, metered, and settled in real time. You handle the AI, we handle payments. x402 is an open HTTP standard for agent payments. When an agent hits a paywall, the server responds with `402 Payment Required`. The agent attaches a payment credential in the next request header and the purchase completes in milliseconds. No checkout, no redirect. Agents autonomously buy, sell, or rent services from each other. Nevermined's identity, metering, and payment infrastructure makes those machine-to-machine transactions possible. AI agents fire thousands of micro-tasks and are OpEx heavy. You need per-request metering and real-time settlement. Seat-based pricing and traditional checkout flows can't keep up. Stripe handles checkout but doesn't track sub-cent API calls. Nevermined adds per-request metering and settlement on top, while still letting you settle via Stripe or USDC. Stripe was built for monthly subscriptions and shopping carts. We're built for millions of micro-transactions between autonomous agents. Any framework that can make an HTTP request: OpenAI, Anthropic, LangChain, CrewAI, Strands, MCP tool servers, and custom agents. We provide TypeScript and Python SDKs, a CLI, and an MCP server. Usage-based bills per API call or compute cycle. Outcome-based charges only when your agent delivers a result (lead booked, bug fixed, etc.). Nevermined supports either model, or a hybrid, out of the box. No. Agents pay on real card rails (Visa, Mastercard). If a merchant accepts cards today, they already accept agent payments. No new SDK, no new settlement flow. Contact the Nevermined team to schedule a merchant onboarding call. Once enrolled, your business can accept payments from AI agents via x402 with no changes to your existing payment infrastructure. # Overview Source: https://docs.nevermined.app/docs/getting-started/overview Nevermined is building the financial rails for AI. Monetize agents and enable autonomous agent-to-agent payments with real cards. AI agents need to pay and get paid, but traditional payment infrastructure wasn't built for that. Three problems stand in the way: * **Agents can't spend autonomously** - no way to delegate a real payment method with controlled limits * **Merchants can't accept agentic payments** - no metering, no per-request settlement, no programmatic access control * **Everyone's locked to one provider** - rigid integrations that don't work across frameworks or payment processors Nevermined solves all three. Put a payment gate in front of any API, charge per request, and settle automatically. You handle the AI, we handle the rest. Launch an integration in 5 minutes Explore the REST API and SDKs ## How It Works Nevermined Payment Flow Any HTTP endpoint your agent exposes. Nevermined gates access at the request level - if a caller isn't entitled, they get a 402 before your code even runs. * **Payment method** - Fiat (cards via [NVM Pay](/docs/products/nvm-pay/overview)) or crypto (USDC) * **Pricing model** - Fixed per call, or dynamic (builder-defined or cost+margin) * **Plan type** - Prepaid (users buy a plan upfront) or pay-as-you-go (charge per request) Entitled? Deliver and settle. Not entitled? Return **402 Payment Required**. **Builders** register API endpoints, set pricing per call, and receive payouts. Add a few lines of validation to your server. **Users** purchase a plan (fiat or crypto) or go pay-as-you-go. No human in the loop. ## Use Cases Meter and settle between autonomous agents Charge for tools and APIs with per-request billing Let agents pay with real cards via card delegation Gate datasets, models, and downloadable resources ## What You Can Monetize Deploy AI customer service agents with per-ticket billing, resolution-based pricing, or monthly support packages. Monetize AI legal research and document analysis with per-query pricing or case-based packages. AI sales assistants that qualify leads and schedule meetings with performance-based pricing. Conversational AI with per-message billing, token packages, or subscription tiers. AI-powered code reviews, security scans, and optimization with per-repo or subscription pricing. Vision services with dynamic pricing based on resolution, complexity, or analysis depth. AI translation and localization with pricing based on word count or language complexity. Analytics services with pricing based on dataset size and analysis complexity. ## What Nevermined Handles | Concern | What you get | | ------------------ | ------------------------------------------------------------------------------------------------ | | Payment processing | Fiat (card delegation via [NVM Pay](/docs/products/nvm-pay/overview)) and crypto (USDC on-chain) | | Access control | Gate API access per request via plans and x402 | | Usage metering | Per-request tracking and settlement | | Settlement | Automatic payouts to providers | ## Integration Options Nevermined is framework-agnostic. It works with LangChain, CrewAI, Strands, OpenAI Agents, or plain HTTP. * **Code-first** - TypeScript and Python SDKs, REST API, and CLI for full programmatic control * **[Nevermined Pay](https://pay.nevermined.app)** - Card enrollment, spending mandates, and delegation management ### Merchant Onboarding Use the [Nevermined App](https://nevermined.app) for reporting, analytics, and plan management. To receive fiat payouts, connect a Stripe Connect account there. Stablecoin payouts settle directly to your wallet. ## Next Steps Understand the platform terminology and building blocks Build your first integration end to end Set up agent card payments with Visa or Stripe TypeScript, Python, and REST API reference # AgentCore 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 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 [x402](/docs/development-guide/nevermined-x402) payment protection to your Express.js API. The `paymentMiddleware` handles verification and settlement automatically. ## 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 } }) ``` ### Payment Scheme (Crypto vs. Fiat) The middleware auto-detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) automatically use `nvm:card-delegation` (Stripe), while crypto plans use `nvm:erc4337`. You can explicitly override the scheme in the route configuration: ```typescript theme={null} paymentMiddleware(payments, { 'POST /ask': { planId: PLAN_ID, credits: 1, scheme: 'nvm:card-delegation' // Force fiat/Stripe scheme } }) ``` The middleware automatically detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) use `nvm:card-delegation` (Stripe). **No code changes are needed on the agent side.** You can explicitly override with the `scheme` parameter. For the full card-delegation specification, see the [Card Delegation Spec](/docs/specs/x402-card-delegation). ## 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 For fiat plans, clients can use `resolveScheme()` to auto-detect the payment scheme before generating tokens. See the [x402 developer guide](/docs/development-guide/nevermined-x402) for details on scheme resolution and `X402TokenOptions`. 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=sandbox: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 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 ```mermaid theme={null} sequenceDiagram participant Client participant Agent Client->>Agent: 1. POST /ask (no token) Agent-->>Client: 2. 402 Payment Required
Header: payment-required (base64) Note over Client: 3. Generate x402 token via SDK Client->>Agent: 4. POST /ask
Header: payment-signature (token) Note over Agent: Verify permissions
Execute request
Settle (burn credits) Agent-->>Client: 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 scheme="nvm:erc4337" # Optional: auto-detected from plan metadata ) } ) ``` When `scheme` is omitted, the middleware calls `resolve_scheme()` to auto-detect the correct scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) automatically use `nvm:card-delegation` (Stripe). ### 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 } } ) ``` ### Payment Scheme (Crypto vs. Fiat) The middleware auto-detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) automatically use `nvm:card-delegation` (Stripe), while crypto plans use `nvm:erc4337`. You can explicitly override the scheme in the route configuration: ```python theme={null} app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": { "plan_id": PLAN_ID, "credits": 1, "scheme": "nvm:card-delegation" # Force fiat/Stripe scheme } } ) ``` The middleware automatically detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) use `nvm:card-delegation` (Stripe). **No code changes are needed on the agent side.** You can explicitly override with the `scheme` parameter. For the full card-delegation specification, see the [Card Delegation Spec](/docs/specs/x402-card-delegation). ## 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 For fiat plans, clients can use `resolve_scheme()` to auto-detect the payment scheme before generating tokens. See the [x402 developer guide](/docs/development-guide/nevermined-x402) for details on scheme resolution and `X402TokenOptions`. 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=sandbox: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 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 // scheme is auto-detected from plan metadata when omitted const paymentRequired = buildPaymentRequired(PLAN_ID, { endpoint: req.path, agentId: AGENT_ID, httpVerb: req.method, scheme: 'nvm:erc4337' // Optional: omit to auto-detect from plan metadata }) // 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 # scheme is auto-detected from plan metadata when omitted payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=request.path, agent_id=AGENT_ID, http_verb=request.method, scheme="nvm:erc4337" # Optional: omit to auto-detect from plan metadata ) # 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 | When the `scheme` parameter is omitted from `buildPaymentRequired` / `build_payment_required`, the network is auto-derived from the scheme (`eip155:84532` for `nvm:erc4337`, `stripe` for `nvm:card-delegation`). The middleware and helpers call `resolveScheme()` / `resolve_scheme()` to detect the correct scheme from plan metadata automatically. ## 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 # LangChain Source: https://docs.nevermined.app/docs/integrate/add-to-your-agent/langchain Add Nevermined x402 payments to your LangChain and LangGraph agents **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 [LangChain](https://python.langchain.com/) and [LangChain.js](https://js.langchain.com/) tools using the [x402 protocol](https://github.com/coinbase/x402). The library provides two complementary approaches: | Approach | Best for | Payment layer | | --------------------------------------- | ---------------------------------------------- | ---------------- | | **`requiresPayment` wrapper/decorator** | Direct tool invocation, CLI scripts, notebooks | Per-tool wrapper | | **Payment middleware on HTTP server** | Serving the agent over HTTP | HTTP middleware | Both use the same Nevermined plan, credits, and settlement flow — choose whichever fits your deployment model. ## Installation ```bash theme={null} npm install @nevermined-io/payments @langchain/core @langchain/openai zod ``` The `@nevermined-io/payments/langchain` sub-path export provides the `requiresPayment()` wrapper. For the HTTP server approach, also install `express`. ```bash theme={null} pip install payments-py[langchain] langchain-openai ``` The `[langchain]` extra installs the LangChain dependency required for the decorator. For the HTTP server approach, also install `fastapi` and `uvicorn`. *** ## Approach 1: Tool Decorator / Wrapper ### x402 Payment Flow (decorator) ```mermaid theme={null} sequenceDiagram participant Client participant LLM participant Tool participant NVM as Nevermined Client->>LLM: invoke(messages) LLM->>Tool: tool_call (no token) Tool-->>LLM: PaymentRequired error (tool result) LLM-->>Client: "Payment required" Client->>NVM: get_x402_access_token(plan_id) NVM-->>Client: Access token Client->>LLM: invoke(messages, config=payment_token) LLM->>Tool: tool_call Tool->>NVM: Verify permissions NVM-->>Tool: Valid Tool->>Tool: Execute function Tool->>NVM: Settle (burn credits) NVM-->>Tool: Settled Tool-->>LLM: Result LLM-->>Client: Final answer ``` ### Quick Start In LangChain.js, `requiresPayment()` is a higher-order function that wraps the tool implementation: ```typescript filename="agent.ts" theme={null} import 'dotenv/config' import { tool } from '@langchain/core/tools' import { z } from 'zod' import { Payments } from '@nevermined-io/payments' import { requiresPayment } from '@nevermined-io/payments/langchain' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT || 'sandbox', }) const PLAN_ID = process.env.NVM_PLAN_ID! // Protect a tool with payment — 1 credit per call const searchData = tool( requiresPayment( (args) => `Results for '${args.query}': ...`, { payments, planId: PLAN_ID, credits: 1 } ), { name: 'search_data', description: 'Search for data on a given topic. Costs 1 credit.', schema: z.object({ query: z.string() }), } ) ``` The payment token is read from `config.configurable.payment_token`. Pass it when invoking the tool or agent. In Python, `@requires_payment` is a decorator applied before `@tool`: ```python filename="agent.py" theme={null} import os from dotenv import load_dotenv from langchain_core.runnables import RunnableConfig from langchain_core.tools import tool from payments_py import Payments, PaymentOptions from payments_py.x402.langchain import requires_payment load_dotenv() 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 — 1 credit per call @tool @requires_payment(payments=payments, plan_id=PLAN_ID, credits=1) def search_data(query: str, config: RunnableConfig) -> str: """Search for data on a given topic. Costs 1 credit.""" return f"Results for '{query}': ..." ``` The tool function **must** accept a `config: RunnableConfig` parameter. The decorator uses it to read the payment token from `config["configurable"]["payment_token"]`. ### Invoking with payment ```typescript filename="client.ts" theme={null} import { Payments } from '@nevermined-io/payments' // Subscriber side — acquire token const subscriber = Payments.getInstance({ nvmApiKey: process.env.NVM_SUBSCRIBER_API_KEY!, environment: process.env.NVM_ENVIRONMENT || 'sandbox', }) const token = await subscriber.x402.getX402AccessToken(PLAN_ID) const accessToken = token.accessToken // Invoke tool directly const result = await searchData.invoke( { query: 'AI trends' }, { configurable: { payment_token: accessToken } } ) ``` ```python filename="client.py" theme={null} from payments_py import Payments, PaymentOptions # Subscriber side — acquire token subscriber = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_SUBSCRIBER_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ) ) token = subscriber.x402.get_x402_access_token(plan_id=PLAN_ID) access_token = token["accessToken"] # Invoke tool directly result = search_data.invoke( {"query": "AI trends"}, config={"configurable": {"payment_token": access_token}}, ) ``` ### LLM-driven tool calling ```typescript theme={null} import { HumanMessage } from '@langchain/core/messages' import { ChatOpenAI } from '@langchain/openai' const llm = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 }) const tools = [searchData, summarizeData, researchTopic] const llmWithTools = llm.bindTools(tools) const toolMap = new Map(tools.map((t) => [t.name, t])) const messages = [new HumanMessage('Search for AI trends')] const aiMessage = await llmWithTools.invoke(messages) messages.push(aiMessage) for (const toolCall of aiMessage.tool_calls || []) { const result = await toolMap.get(toolCall.name)!.invoke( toolCall.args, { configurable: { payment_token: accessToken } } ) } ``` ```python theme={null} llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) tools = [search_data, summarize_data, research_topic] llm_with_tools = llm.bind_tools(tools) tool_map = {t.name: t for t in tools} messages = [HumanMessage(content="Search for AI trends")] ai_message = llm_with_tools.invoke(messages) messages.append(ai_message) for tool_call in ai_message.tool_calls: result = tool_map[tool_call["name"]].invoke( tool_call["args"], config={"configurable": {"payment_token": access_token}}, ) ``` ### LangGraph ReAct agent The same payment-protected tools work with LangGraph's `create_react_agent`: ```typescript theme={null} import { createReactAgent } from '@langchain/langgraph/prebuilt' const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4o-mini' }), tools: [searchData, summarizeData, researchTopic], prompt: 'You are a helpful research assistant.', }) const result = await agent.invoke( { messages: [{ role: 'human', content: 'Research AI agents and summarize' }] }, { configurable: { payment_token: accessToken } } ) ``` ```python theme={null} from langgraph.prebuilt import create_react_agent graph = create_react_agent( ChatOpenAI(model="gpt-4o-mini"), [search_data, summarize_data, research_topic], prompt="You are a helpful research assistant.", ) result = graph.invoke( {"messages": [("human", "Research AI agents and summarize")]}, config={"configurable": {"payment_token": access_token}}, ) ``` ### Dynamic Credits Three patterns for credit calculation: ```typescript theme={null} // Pattern 1: Static number — always costs 1 credit const searchData = tool( requiresPayment( (args) => `Results for ${args.query}`, { payments, planId: PLAN_ID, credits: 1 } ), { name: 'search_data', description: '...', schema: z.object({ query: z.string() }) } ) // Pattern 2: Arrow function — cost scales with output length const summarize = tool( requiresPayment( (args) => `Summary of ${args.text}`, { payments, planId: PLAN_ID, credits: (ctx) => Math.max(2, Math.min(Math.floor(String(ctx.result).length / 100), 10)), } ), { name: 'summarize', description: '...', schema: z.object({ text: z.string() }) } ) // Pattern 3: Named function — complex logic on args + result function calcCredits(ctx: { args: Record; result: unknown }): number { const topic = String(ctx.args.topic || '') const result = String(ctx.result || '') const base = 3 const keywordExtra = Math.max(0, topic.split(' ').length - 3) const outputExtra = Math.floor(result.length / 200) return Math.min(base + keywordExtra + outputExtra, 15) } const research = tool( requiresPayment( (args) => `Report on ${args.topic}`, { payments, planId: PLAN_ID, credits: calcCredits } ), { name: 'research', description: '...', schema: z.object({ topic: z.string() }) } ) ``` The credits function receives `{ args, result }` after tool execution. Three patterns for credit calculation: ```python theme={null} # Pattern 1: Static int — always costs 1 credit @tool @requires_payment(payments=payments, plan_id=PLAN_ID, credits=1) def search_data(query: str, config: RunnableConfig) -> str: ... # Pattern 2: Lambda — cost scales with output length @tool @requires_payment( payments=payments, plan_id=PLAN_ID, credits=lambda ctx: max(2, min(len(ctx.get("result", "")) // 100, 10)), ) def summarize_data(text: str, config: RunnableConfig) -> str: ... # Pattern 3: Named function — complex logic on args + result def calc_credits(ctx: dict) -> int: args = ctx.get("args", {}) result = ctx.get("result", "") base = 3 keyword_extra = max(0, len(args.get("topic", "").split()) - 3) output_extra = len(result) // 200 return min(base + keyword_extra + output_extra, 15) @tool @requires_payment(payments=payments, plan_id=PLAN_ID, credits=calc_credits) def research_topic(topic: str, config: RunnableConfig) -> str: ... ``` The `ctx` dict passed to the credits function contains: * `args` — the tool's input arguments * `result` — the tool's return value (evaluated after execution) *** ## Approach 2: HTTP Server with Payment Middleware For serving the agent over HTTP, use payment middleware on your framework. Payment is handled at the HTTP layer — tools are plain functions with no decorators or payment config. ### x402 Payment Flow (HTTP) ```mermaid theme={null} sequenceDiagram participant Client participant Agent Client->>Agent: 1. POST /ask (no token) Agent-->>Client: 2. 402 Payment Required
Header: payment-required (base64) Note over Client: 3. Generate x402 token via SDK Client->>Agent: 4. POST /ask
Header: payment-signature (token) Note over Agent: Verify permissions
Run LLM agent
Settle (burn credits) Agent-->>Client: 5. 200 OK + AI response
Header: payment-response (base64) ``` ### Server: LangChain ```typescript filename="src/server.ts" theme={null} import 'dotenv/config' import express from 'express' import { HumanMessage, ToolMessage } from '@langchain/core/messages' import { tool } from '@langchain/core/tools' import { ChatOpenAI } from '@langchain/openai' import { z } from 'zod' import { Payments } from '@nevermined-io/payments' import { paymentMiddleware } from '@nevermined-io/payments/express' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: process.env.NVM_ENVIRONMENT || 'sandbox', }) const PLAN_ID = process.env.NVM_PLAN_ID! // Plain tools — no requiresPayment, no config parameter const searchData = tool( (args) => `Results for '${args.query}': ...`, { name: 'search_data', description: 'Search for data.', schema: z.object({ query: z.string() }) } ) const summarizeData = tool( (args) => `Summary: ...`, { name: 'summarize_data', description: 'Summarize text.', schema: z.object({ text: z.string() }) } ) // LLM + tools const llm = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 }) const tools = [searchData, summarizeData] const llmWithTools = llm.bindTools(tools) const toolMap = new Map(tools.map((t) => [t.name, t])) async function runAgent(query: string): Promise { const messages: any[] = [new HumanMessage(query)] for (let i = 0; i < 10; i++) { const ai = await llmWithTools.invoke(messages) messages.push(ai) if (!ai.tool_calls?.length) return String(ai.content) for (const tc of ai.tool_calls) { const result = await (toolMap.get(tc.name) as any).invoke(tc.args) messages.push(new ToolMessage({ content: result, tool_call_id: tc.id! })) } } return String(messages.at(-1)?.content || 'No response.') } // Express app with payment middleware const app = express() app.use(express.json()) app.use(paymentMiddleware(payments, { 'POST /ask': { planId: PLAN_ID, credits: 1 }, })) app.post('/ask', async (req, res) => { const response = await runAgent(req.body.query) res.json({ response }) }) app.get('/health', (_req, res) => res.json({ status: 'ok' })) app.listen(8000, () => console.log('Running on http://localhost:8000')) ``` ```python filename="src/server.py" theme={null} import os from dotenv import load_dotenv load_dotenv() import uvicorn from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from langchain_core.messages import HumanMessage, ToolMessage from langchain_core.tools import tool from langchain_openai import ChatOpenAI from pydantic import BaseModel from payments_py import Payments, PaymentOptions from payments_py.x402.fastapi import PaymentMiddleware 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") # Plain tools — no @requires_payment, no RunnableConfig @tool def search_data(query: str) -> str: """Search for data on a given topic.""" return f"Results for '{query}': ..." @tool def summarize_data(text: str) -> str: """Summarize text into bullet points.""" return f"Summary: ..." # LLM + tools llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) tools = [search_data, summarize_data] llm_with_tools = llm.bind_tools(tools) tool_map = {t.name: t for t in tools} def run_agent(query: str) -> str: """Run the LangChain agent with a tool-call loop.""" messages = [HumanMessage(content=query)] for _ in range(10): ai = llm_with_tools.invoke(messages) messages.append(ai) if not ai.tool_calls: return ai.content for tc in ai.tool_calls: result = tool_map[tc["name"]].invoke(tc["args"]) messages.append(ToolMessage(content=result, tool_call_id=tc["id"])) return messages[-1].content # FastAPI app with payment middleware app = FastAPI(title="LangChain Agent with x402 Payments") app.add_middleware( PaymentMiddleware, payments=payments, routes={ "POST /ask": {"plan_id": PLAN_ID, "credits": 1, "agent_id": AGENT_ID}, }, ) class AskRequest(BaseModel): query: str @app.post("/ask") async def ask(body: AskRequest, request: Request) -> JSONResponse: response = run_agent(body.query) return JSONResponse(content={"response": response}) @app.get("/health") async def health(): return {"status": "ok"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) ``` ### Server: LangGraph Replace the tool-call loop with LangGraph's `create_react_agent`: ```typescript theme={null} import { tool } from '@langchain/core/tools' import { ChatOpenAI } from '@langchain/openai' import { createReactAgent } from '@langchain/langgraph/prebuilt' import { z } from 'zod' // Plain tools — no payment wrappers const searchData = tool( (args) => `Results for '${args.query}': ...`, { name: 'search_data', description: 'Search.', schema: z.object({ query: z.string() }) } ) const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4o-mini' }), tools: [searchData, summarizeData], }) async function runAgent(query: string): Promise { const result = await agent.invoke({ messages: [{ role: 'human', content: query }] }) const messages = result.messages || [] return messages.at(-1)?.content || 'No response.' } ``` ```python theme={null} from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent # Plain tools — no payment decorators @tool def search_data(query: str) -> str: """Search for data on a given topic.""" return f"Results for '{query}': ..." llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) graph = create_react_agent(llm, [search_data, summarize_data]) def run_agent(query: str) -> str: result = graph.invoke({"messages": [("human", query)]}) messages = result.get("messages", []) return messages[-1].content if messages else "No response." ``` The HTTP app, middleware, and route handlers are identical to the LangChain version above. ### Client: Full x402 HTTP Flow ```typescript filename="src/client.ts" theme={null} import 'dotenv/config' import { Payments } from '@nevermined-io/payments' const SERVER_URL = process.env.SERVER_URL || 'http://localhost:8000' const PLAN_ID = process.env.NVM_PLAN_ID! const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_SUBSCRIBER_API_KEY!, environment: process.env.NVM_ENVIRONMENT || 'sandbox', }) // Step 1: Request without token → 402 const resp402 = await fetch(`${SERVER_URL}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'AI trends' }), }) console.log(`Status: ${resp402.status}`) // 402 // Step 2: Decode payment requirements const pr = JSON.parse(Buffer.from(resp402.headers.get('payment-required')!, 'base64').toString()) console.log(`Plan: ${pr.accepts[0].planId}`) // Step 3: Acquire x402 token const token = await payments.x402.getX402AccessToken(PLAN_ID) const accessToken = token.accessToken // Step 4: Request with token → 200 const resp200 = await fetch(`${SERVER_URL}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'payment-signature': accessToken, }, body: JSON.stringify({ query: 'AI trends' }), }) const body = await resp200.json() console.log(`Response: ${body.response}`) // Step 5: Decode settlement receipt const settlement = JSON.parse( Buffer.from(resp200.headers.get('payment-response')!, 'base64').toString() ) console.log(`Credits charged: ${settlement.creditsRedeemed}`) console.log(`Remaining balance: ${settlement.remainingBalance}`) console.log(`Transaction: ${settlement.transaction}`) ``` ```python filename="src/client.py" theme={null} import base64 import json import os import httpx from payments_py import Payments, PaymentOptions SERVER_URL = os.environ.get("SERVER_URL", "http://localhost:8000") PLAN_ID = os.environ["NVM_PLAN_ID"] payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_SUBSCRIBER_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox"), ) ) with httpx.Client(timeout=60.0) as client: # Step 1: Request without token → 402 resp = client.post(f"{SERVER_URL}/ask", json={"query": "AI trends"}) assert resp.status_code == 402 # Step 2: Decode payment requirements pr = json.loads(base64.b64decode(resp.headers["payment-required"])) print(f"Plan: {pr['accepts'][0]['planId']}") # Step 3: Acquire x402 token token = payments.x402.get_x402_access_token(plan_id=PLAN_ID) access_token = token["accessToken"] # Step 4: Request with token → 200 resp = client.post( f"{SERVER_URL}/ask", headers={"payment-signature": access_token}, json={"query": "AI trends"}, ) print(f"Response: {resp.json()['response']}") # Step 5: Decode settlement receipt settlement = json.loads(base64.b64decode(resp.headers["payment-response"])) print(f"Credits charged: {settlement['creditsRedeemed']}") print(f"Remaining balance: {settlement['remainingBalance']}") print(f"Transaction: {settlement['transaction']}") ``` ### x402 HTTP Headers | 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 | The settlement receipt (`payment-response`) contains: | Field | Description | | ------------------ | ------------------------------------- | | `creditsRedeemed` | Number of credits charged | | `remainingBalance` | Subscriber's remaining credit balance | | `transaction` | Blockchain transaction hash | | `network` | Blockchain network (CAIP-2 format) | | `payer` | Subscriber wallet address | *** ## Decorator Configuration ### With Agent ID ```typescript theme={null} const myTool = tool( requiresPayment( (args) => `Result for ${args.query}`, { payments, planId: PLAN_ID, credits: 1, agentId: process.env.NVM_AGENT_ID, } ), { name: 'my_tool', description: '...', schema: z.object({ query: z.string() }) } ) ``` ```python theme={null} @tool @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, agent_id=os.environ.get("NVM_AGENT_ID"), ) def my_tool(query: str, config: RunnableConfig) -> str: ... ``` ### Multiple Plans (Python only) ```python theme={null} @tool @requires_payment( payments=payments, plan_ids=["plan-basic", "plan-premium"], credits=1, ) def my_tool(query: str, config: RunnableConfig) -> str: ... ``` ### Scheme and Network ```typescript theme={null} const myTool = tool( requiresPayment( (args) => `Result`, { payments, planId: PLAN_ID, credits: 1, network: 'eip155:84532' } ), { name: 'my_tool', description: '...', schema: z.object({ query: z.string() }) } ) ``` ```python theme={null} # Explicit card-delegation scheme for fiat payments @tool @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, scheme="nvm:card-delegation", ) def my_fiat_tool(query: str, config: RunnableConfig) -> str: ... ``` The decorator/wrapper automatically detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) use `nvm:card-delegation` (Stripe). **No code changes are needed on the agent side.** ## Complete Examples Working seller/buyer agents with LangGraph — includes both Python and TypeScript variants: * [seller-simple-agent/ts](https://github.com/nevermined-io/hackathons/tree/main/agents/seller-simple-agent/ts) — Seller with `paymentMiddleware` + `requiresPayment` demo * [buyer-simple-agent/ts](https://github.com/nevermined-io/hackathons/tree/main/agents/buyer-simple-agent/ts) — Buyer CLI agent with x402 token generation Each includes: * `src/server.ts` / `src/agent.ts` — LangGraph `createReactAgent` with payment-protected tools * `src/demo.ts` — `requiresPayment` wrapper demo (seller only) * `src/client.ts` — HTTP client with full x402 payment flow * [seller-simple-agent](https://github.com/nevermined-io/hackathons/tree/main/agents/seller-simple-agent) — `src/langgraph_agent.py` with `@requires_payment` decorator * [buyer-simple-agent](https://github.com/nevermined-io/hackathons/tree/main/agents/buyer-simple-agent) — `src/langgraph_agent.py` with buyer tools Each includes: * `src/langgraph_agent.py` — LangGraph `create_react_agent` with payment-protected tools * `src/agent_langgraph.py` — Interactive CLI entry point ## Environment Variables ```bash filename=".env" theme={null} # Nevermined (required) NVM_API_KEY=sandbox:your-api-key # Builder/server API key NVM_SUBSCRIBER_API_KEY=sandbox:your-key # Subscriber/client 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 ``` ## Next Steps Deep dive into paymentMiddleware for Express Deep dive into PaymentMiddleware for FastAPI Deep dive into x402 payment flows Configure credits, subscriptions, and dynamic pricing # Strands 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 The decorator automatically detects the payment scheme from plan metadata. Plans with fiat pricing (`isCrypto: false`) use `nvm:card-delegation` (Stripe). **No code changes are needed on the agent side.** You can explicitly override with the `scheme` parameter. ### 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: ... ``` ### Scheme and Network ```python theme={null} # Custom network for crypto payments @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_crypto_tool(query: str, tool_context=None) -> dict: ... # Explicit card-delegation scheme for fiat payments @tool(context=True) @requires_payment( payments=payments, plan_id=PLAN_ID, credits=1, scheme="nvm:card-delegation", # Fiat/Stripe (network auto-set to "stripe") ) def my_fiat_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=sandbox: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 payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount=str(cost) ) ``` ## 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 # Fiat Payments Source: https://docs.nevermined.app/docs/integrate/patterns/fiat-payments Accept credit cards via Stripe or Visa with no crypto wallet required. The easiest path for mainstream and enterprise adoption. Accept credit cards in **USD or EUR**, with no crypto wallet required. Fiat payments are the fastest path to mainstream adoption. Your users can start paying with a card they already have, either through Stripe Checkout or by enrolling their card via [Nevermined Pay](/docs/products/nvm-pay/overview) for autonomous agent charging. ## When to Use Fiat Payments Organizations that already pay for SaaS with corporate cards can onboard without touching crypto. No wallet setup, no token bridging. Users pay with the card in their pocket. Fiat-denominated billing simplifies accounting, invoicing, and compliance for businesses. Users go from discovery to first API call in seconds. No wallet funding step required. ## Fiat Payment Scenarios Nevermined supports fiat payments in two scenarios: * **Human-to-Agent**: A user visits the Nevermined App checkout page and purchases a plan with their credit card. This is the simplest flow for onboarding new customers. * **Agent-to-Agent**: An agent owner enrolls a credit card via [Nevermined Pay](/docs/products/nvm-pay/overview) (Visa or Stripe) and creates a spending mandate or delegation. The agent can then make autonomous payments on behalf of the owner, charging the card per request within the defined limits. ## Pay-per-request or a bundle of credits Nevermined offers two distinct fiat payment flows depending on whether you want upfront plan purchases or per-request charging. One-time or recurring payment via Stripe Checkout. The user buys a plan, a webhook confirms the payment, and credits are minted on-chain. Best for human users who want a familiar checkout experience. Per-request charging via the x402 protocol. The user enrolls a Visa or Stripe card, creates a spending mandate or delegation with limits, and the agent charges it for each request. Best for automated workflows and agent-to-agent payments. ## Human-to-Agent Purchase Flow The simplest way to accept fiat. Users complete a standard Stripe Checkout session, and Nevermined handles the rest. The user browses available plans in the Nevermined App or your custom UI and selects one priced in fiat. Nevermined creates a Stripe Checkout session and redirects the user to complete payment with their card. Stripe processes the charge. For recurring plans, Stripe manages subsequent billing automatically. Nevermined receives a webhook from Stripe confirming the successful payment. The corresponding credits are minted to the subscriber's account, and they can immediately start using the agent. Both one-time and recurring plan purchases are supported. For recurring plans, Stripe handles rebilling and Nevermined mints fresh credits each billing cycle. ## Agent-to-Agent Purchase Flow (Nevermined Pay) Fully autonomous agent-to-agent payments in fiat are enabled through [Nevermined Pay](/docs/products/nvm-pay/overview). This allows an agent to charge a user's card on-demand, without the user needing to pre-purchase credits. Instead of buying credits upfront, the user enrolls a card and creates a spending mandate or delegation with defined limits. NVM Pay supports two card enrollment providers: * **Visa** - Cards tokenized via Visa Token Service (VTS) with passkey (FIDO2) authentication * **Stripe** - Cards captured via Stripe Elements with SetupIntent tokenization The user enrolls a card via the [Nevermined Pay dashboard](https://pay.nevermined.app). Card data is tokenized by the provider (Visa or Stripe) and NVM Pay never stores raw card numbers. See [Card Enrollment](/docs/products/nvm-pay/card-enrollment) for the full flow. The user creates a mandate (Visa) or delegation (Stripe) that defines spending limits: amount ceiling, max transactions, and expiration. Optionally, they can link it to a specific API key for automatic selection. See [Mandates & Delegations](/docs/products/nvm-pay/mandates). When the agent makes a request, it calls the NVM Pay API with its NVM API key. NVM Pay resolves the right mandate/delegation and generates an x402 payment token. The resource server verifies the payment token, executes the workload, then settles. NVM Pay charges the enrolled card and records the transaction. Card delegation is what enables fiat-based agent-to-agent payments. An agent owner enrolls their card and sets spending limits, and the agent can autonomously pay for services from other agents, all settled in fiat. See the [NVM Pay FAQ](/docs/products/nvm-pay/faq) for common questions. ## Permission Model Mandates and delegations enforce strict spending limits to protect the cardholder. Both Visa mandates and Stripe delegations support the same controls: | Limit | Visa (Mandate) | Stripe (Delegation) | | ---------------- | ---------------------- | -------------------------------------- | | Spending limit | `amount` (USD) | `spendingLimitCents` (cents) | | Max transactions | `maxUsage` | `maxTransactions` | | Expiration | `expiresAt` (ISO date) | `durationSecs` (seconds from creation) | An authorization moves through a simple lifecycle: | State | Meaning | | ------------- | -------------------------------------------------------- | | **Active** | Valid and can be used for charges | | **Exhausted** | The spending limit or transaction count has been reached | | **Expired** | The expiration date has passed | | **Revoked** | The cardholder manually revoked it | Once an authorization leaves the **Active** state, a new one must be created to continue charging. See [Mandates & Delegations](/docs/products/nvm-pay/mandates) for full details on spending controls and the [Mandate Selection](/docs/products/nvm-pay/mandate-selection) algorithm. ## Security and PCI Compliance Card data never reaches Nevermined servers. Depending on the provider: * **Visa path** - Card data is captured via a PCI-compliant VGS vault and tokenized by Visa Token Service (VTS). Each payment generates a one-time VIC credential. * **Stripe path** - Card data goes directly to Stripe via Stripe Elements. Stripe manages secure storage and charging. **Nevermined is PCI compliant** (PCI SAQ D level) and holds ISO 27001 and SOC 2 Type II certifications. Strong Customer Authentication (3DS or FIDO2 passkeys) is enforced at enrollment. See the [NVM Pay FAQ](/docs/products/nvm-pay/faq) for full security details. ## Revenue Routing with Stripe Connect When a user pays with a card, Nevermined routes the revenue to the builder's connected Stripe account using Stripe Connect. Fees are deducted automatically before the payout reaches the builder. This means you receive funds directly in your Stripe account -- no manual transfers or reconciliation needed. ## Fees and Settlement | Item | Cost | | --------------------- | ----------------------------------------------------------------------------- | | Nevermined fee | 2% of the transaction amount | | Stripe processing fee | Fixed fee + % per transaction (varies by currency/region; see Stripe pricing) | | Supported currencies | USD, EUR | | Settlement | Via Stripe PaymentIntents to builder's connected account | Fiat payments carry higher fees than stablecoin payments (2% + Stripe fees vs. 1% flat). For high-volume or high-value use cases, consider [stablecoin payments](/docs/integrate/patterns/stablecoin-payments) to reduce costs. ## Supported Plan Types Prepaid credits purchased via Stripe Checkout. Credits are minted once payment confirms. Subscription access for a set duration. Stripe handles recurring billing automatically. Per-request charging via card delegation. No upfront purchase needed. Automatic rebilling at the end of each period. Stripe manages the billing cycle. ## Next Steps Enroll Visa or Stripe cards and create spending mandates for agent payments Accept crypto payments with the lowest fees and on-chain transparency Configure credits, time-based, dynamic, and hybrid plans Create and manage payment plans in the Nevermined App # Payment Models Source: https://docs.nevermined.app/docs/integrate/patterns/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 payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=access_token, max_amount=str(actual_credits_used), # 1-10 based on request ) ``` ## 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 or Braintree) Accept credit card payments via Stripe or Braintree. The active provider is selected per plan via the seller's `fiatPaymentProvider` metadata at registration time; both providers settle x402 charges through the same `nvm:card-delegation` scheme. ```typescript theme={null} const fiatConfig = await payments.plans.getFiatPriceConfig({ amount: 1000, // $10.00 currency: 'USD' }) // One-time fiat purchase via the SDK (Stripe-priced plans only): const { url } = await payments.plans.orderFiatPlan(planId) // Redirect user to Stripe checkout URL ``` `orderFiatPlan` returns a Stripe checkout URL and is only available for Stripe-priced plans. Braintree-priced plans are purchased through the Nevermined App's Braintree Drop-in checkout. Both Stripe and Braintree plans support per-request x402 settlement against credit-card delegations — see [Braintree onboarding](/docs/products/nvm-pay/braintree-onboarding) for the seller-side requirements (per-currency merchant accounts). ## 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 # Stablecoin Payments Source: https://docs.nevermined.app/docs/integrate/patterns/stablecoin-payments Accept USDC, EURC, or any ERC-20 token on-chain with the lowest fees and full transparency. Accept USDC, EURC, or any ERC-20 token directly on-chain. Stablecoin payments are Nevermined's native payment method, purpose-built for crypto users and autonomous agent-to-agent commerce. ## When to Use Stablecoins Agents with wallets can purchase plans and pay per request autonomously, with no human in the loop. Users buy plans through the Nevermined App checkout page, paying with tokens from their connected wallet. Your audience already holds stablecoins and prefers on-chain payments over traditional card flows. Just 1% Nevermined fee with no additional processing surcharges. Ideal for high-volume or high-value use cases. Every payment settles on-chain, giving both parties a verifiable, auditable record of every transaction. ## How It Works The subscriber funds their Nevermined smart account with ERC-20 tokens (USDC, EURC, or another supported token). This account is an ERC-4337 smart account that supports programmable permissions. The subscriber creates a delegation that grants the facilitator permission to execute specific actions (order plans, burn credits, redeem access) on their behalf. Each delegation is scoped by spending limits and duration, following the principle of least privilege. When the subscriber purchases a plan or makes a request, the facilitator submits a UserOperation to the smart contract. The payment settles on-chain and credits are minted to the subscriber. Each API call or agent query burns credits according to the plan configuration. If the subscriber's balance runs low, the system can auto-order more credits (if the wallet has sufficient balance and the delegation permits it). ## Supported Networks and Tokens Payments settle on the **Base** network. The most common tokens are: | Token | Description | Base Mainnet | Base Sepolia | | ----- | ------------------------------- | -------------------------------------------- | -------------------------------------------- | | USDC | USD-pegged stablecoin by Circle | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | | EURC | EUR-pegged stablecoin by Circle | `0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42` | `0x808456652fdb597867f38412077A9182bf77359F` | Nevermined supports any ERC-20 token on Base. If you need to accept a custom token, you can specify its contract address when creating your plan's price configuration. ## Permission Model Delegations are the core of the stablecoin permission model. Instead of giving the facilitator full control over a wallet, subscribers create scoped delegations that limit exactly what can be done. The delegation model is shared between crypto (`erc4337`) and fiat (`stripe`) payment providers. Each delegation enforces the **principle of least privilege** through two limits: | Limit | Description | | -------------------- | ------------------------------------------------------------------------- | | `spendingLimitCents` | Maximum total amount (in cents) that can be spent across all transactions | | `durationSecs` | Duration in seconds after which the delegation expires | When the spending limit is exhausted or the delegation expires, it stops working. The subscriber must create a new delegation with fresh limits to continue. Delegations are created via the SDK before generating an x402 access token: ```typescript theme={null} // Create a delegation for crypto payments const delegation = await payments.delegation.createDelegation({ provider: 'erc4337', spendingLimitCents: 10000, durationSecs: 604800 // 7 days }) ``` Delegations are revocable at any time by the smart account owner. You always retain full control over your wallet. Under the hood, delegations create the appropriate session keys for the chosen provider. ## Fees and Settlement | Item | Cost | | -------------------- | ----------------------------------------------- | | Nevermined fee | 1% of the plan price | | Processing surcharge | None | | Settlement | On-chain via Nevermined smart contracts on Base | Because there's no payment processor in the middle, stablecoin payments carry no additional fees beyond the 1% Nevermined fee. This makes them the most cost-effective option, especially for high-volume use cases. ## Supported Plan Types Prepaid credits consumed per request. Set total credits and cost per request when creating the plan. Unlimited access for a set duration (days, months, years). Great for subscription-style access. Variable credit charges based on request complexity, token count, or custom metrics. Combine time-based access with credit limits for balanced monetization. Offer free credits or time-limited trials to let users test your agent before purchasing. Per-request settlement in USDC or EURC. No prepaid credits needed -- customers pay on demand. ## Auto-Ordering When a subscriber's credit balance runs low, the system can automatically purchase more credits on their behalf. This happens seamlessly if: * The subscriber's wallet has sufficient token balance * The active delegation has remaining spending capacity (`spendingLimitCents` not exhausted) * The plan supports re-ordering This keeps agent-to-agent workflows running without interruption. If the delegation's `spendingLimitCents` is exhausted or the delegation has expired, auto-ordering stops. The subscriber must create a new delegation with fresh limits to resume. ## Next Steps Accept credit cards via Stripe for mainstream and enterprise users Configure credits, time-based, dynamic, and hybrid plans Deep dive into the x402 protocol and smart account settlement Create and manage plans programmatically with the TypeScript SDK # 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 Open [nevermined.app](https://nevermined.app), sign in, then go to **Settings > Global NVM API Keys** and click **+ New API Key**. Copy the key and set it as an environment variable: ```bash theme={null} export NVM_API_KEY="sandbox:your-api-key-here" ``` Keys are environment-specific — `sandbox:` prefix for testing, `live:` for production. ```bash theme={null} npm install @nevermined-io/payments ``` ```bash theme={null} pip install payments-py ``` ## Register Your Agent Create a script 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). No 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 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 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': [{'verb': 'POST', 'url': '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: process.env.NVM_API_KEY!, environment: 'sandbox', }) const PLAN_ID = process.env.NVM_PLAN_ID! const AGENT_ID = process.env.NVM_AGENT_ID! // Build payment required specification const paymentRequired = buildPaymentRequired(PLAN_ID, { endpoint: req.url, agentId: AGENT_ID, httpVerb: req.method, }) // Get token from payment-signature header const x402Token = req.headers['payment-signature'] as string if (!x402Token) { // Return 402 with payment-required header so clients know how to pay const paymentRequiredBase64 = Buffer.from( JSON.stringify(paymentRequired) ).toString('base64') return res .status(402) .setHeader('payment-required', paymentRequiredBase64) .json({ error: 'Payment Required' }) } // 1. Verify permissions (does NOT burn credits) const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: 1n, }) if (!verification.isValid) { return res.status(402).json({ error: verification.invalidReason }) } // 2. Execute your logic here — payment is verified const result = await handleRequest(req) // 3. Settle (burn credits) after successful processing await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: x402Token, maxAmount: 1n, }) return res.json({ result }) ``` ```python filename="middleware/payments.py" theme={null} import base64 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") ) PLAN_ID = os.environ["NVM_PLAN_ID"] AGENT_ID = os.environ["NVM_AGENT_ID"] # Build payment required specification payment_required = build_payment_required( plan_id=PLAN_ID, endpoint=request.url, agent_id=AGENT_ID, http_verb=request.method ) # Get token from payment-signature header x402_token = request.headers.get("payment-signature") if not x402_token: # Return 402 with payment-required header so clients know how to pay pr_base64 = base64.b64encode( payment_required.model_dump_json(by_alias=True).encode() ).decode() return JSONResponse( status_code=402, content={"error": "Payment Required"}, headers={"payment-required": pr_base64} ) # 1. Verify permissions (does NOT burn credits) verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1", ) if not verification.is_valid: return JSONResponse( status_code=402, content={"error": verification.invalid_reason} ) # 2. Execute your logic here — payment is verified result = handle_request(request) # 3. Settle (burn credits) after successful processing payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=x402_token, max_amount="1", ) return {"result": result} ``` ## 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: ${accessToken}" \ -d '{"prompt": "Hello"}' ``` Response: ```json theme={null} { "result": "Hello! How can I help you today?", "creditsRemaining": 99 } ``` Want subscribers to pay with a regular credit card instead of stablecoins? [NVM Pay](/docs/products/nvm-pay/overview) lets subscribers enroll a Visa or Stripe card and create spending mandates that agents charge directly. ## 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 (see: /docs/getting-started/get-your-api-key) export NVM_API_KEY="sandbox: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 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': [{'verb': 'POST', 'url': '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 (see: /docs/getting-started/get-your-api-key) export NVM_API_KEY="sandbox: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 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 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. # A2A Source: https://docs.nevermined.app/docs/integrations/google-a2a Integrate Nevermined with Google's Agent-to-Agent (A2A) protocol to authorize and charge per request between agents, with credits validation built in. Nevermined Payments integrates with [Google A2A](https://a2a-protocol.org/) to enable heterogeneous multi-agent systems 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 payment-signature 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 payment-signature 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. * **Scheme Auto-Detection**: Resolves `nvm:erc4337` or `nvm:card-delegation` from plan metadata automatically * **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. Follow the [Get Your API Key](/docs/getting-started/get-your-api-key) guide to create one. 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, PaymentOptions payments = Payments.get_instance( PaymentOptions(nvm_api_key="", environment="sandbox") ) ``` ## A2A Server The A2A server handles both `nvm:erc4337` and `nvm:card-delegation` schemes automatically. The scheme is resolved from plan metadata — no server-side code changes are needed for fiat plans. ### 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 (payment-signature) 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, 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). # 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 payment 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 payment token 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} import os from mcp.server.fastmcp import FastMCP from payments_py import Payments, PaymentOptions # 1. Initialize Payments payments = Payments.get_instance( PaymentOptions( nvm_api_key=os.environ["NVM_API_KEY"], environment=os.environ.get("NVM_ENVIRONMENT", "sandbox") ) ) # 2. Create MCP server with tool 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." # 3. Start with Nevermined paywall payments.mcp.start( fastmcp=fastmcp, agent_id=os.environ["NVM_AGENT_ID"], port=3000 ) ``` Requires `payments-py[mcp]`: `pip install payments-py[mcp] python-dotenv` **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 * `payments-py[mcp]` (includes MCP SDK, Uvicorn, and Nevermined SDK) ```bash theme={null} pip install payments-py[mcp] python-dotenv ``` ### 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 import Payments, PaymentOptions payments = Payments.get_instance( PaymentOptions(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 # OpenClaw Source: https://docs.nevermined.app/docs/integrations/openclaw Integrate Nevermined payments into OpenClaw gateways to monetize AI agents from Telegram, Discord, WhatsApp, and any chat channel. Nevermined integrates with [OpenClaw](https://openclaw.ai) to bring AI agent payments directly into messaging platforms. Install the plugin on an OpenClaw gateway and every connected channel gains full access to the Nevermined payment infrastructure: * **Chat-native payments**: subscribers order plans, check balances, and query paid agents from Telegram, Discord, WhatsApp, or any other connected channel. * **x402 built in**: the plugin handles the full payment lifecycle — token acquisition, permission verification, and credit settlement — automatically. * **Builder and subscriber tools**: 7 payment tools and 2 slash commands cover both sides of the marketplace, from agent registration to paid queries. * **Paid HTTP endpoint**: enable a single config flag and the gateway exposes an x402-compatible endpoint that verifies, processes, and settles requests without custom code. ## Architecture ```mermaid theme={null} sequenceDiagram participant User participant Gateway as OpenClaw Gateway participant Nevermined User->>Gateway: Send message from chat channel Gateway->>Nevermined: getAccessToken(planId, agentId) Nevermined-->>Gateway: x402 access token Gateway->>Nevermined: POST /agent + payment-signature Nevermined->>Nevermined: verifyPermissions → settle credits Nevermined-->>Gateway: Agent response + payment-response Gateway-->>User: Return response in chat ``` ## Features The [Nevermined OpenClaw plugin](https://github.com/nevermined-io/openclaw-plugin) provides a complete set of tools for both builders and subscribers: * **Payment Signature Authentication**: the paid endpoint extracts access tokens from the `payment-signature` header and verifies them against Nevermined. * **Credit Verification and Settlement**: validates subscriber balance before processing and burns credits only after successful execution. * **Multi-Channel Support**: one plugin instance handles payments across every channel your OpenClaw gateway connects to. * **Agent Registration from Chat**: register agents and create payment plans directly from any messaging channel — no dashboard required. * **Flexible Pricing**: supports fiat (Stripe/USD), ERC20 tokens (USDC), and native cryptocurrency payment plans. * **Balance Awareness**: when configured, the plugin injects the current credit balance into the agent's context so your Claw can proactively warn users about low credits. ## Quickstart To interact with the Nevermined API, you need an API key. Follow the [Get Your API Key](/docs/getting-started/get-your-api-key) guide to create one. From your OpenClaw gateway server: ```bash theme={null} openclaw plugin install @nevermined-io/openclaw-plugin ``` Restart the gateway after installation: ```bash theme={null} openclaw gateway restart ``` You should see in the logs: ``` Registered 7 Nevermined payment tools ``` Send `/nvm_login` from any connected chat channel to authenticate with your Nevermined account. ``` /nvm_login ``` This opens a browser window for authentication. The API key is captured and stored automatically. For headless servers, pass the key directly: ``` /nvm_login sandbox:eyJhbG... ``` Use `sandbox` for testing, `live` for production. ## Register an Agent and Payment Plan Once authenticated, register your agent directly from chat. Send a message like: > Register a Nevermined agent called "Legal Assistant" at URL `https://my-gateway.example.com/nevermined/agent` with a plan named "Legal Queries" priced at 1000000 to address `0xYourWalletAddress` with token `0x036CbD53842c5426634e7929541eC2318f3dCF7e` granting 10 credits. The gateway calls `nevermined_registerAgent` and returns an `agentId` and `planId`. Save these for configuration. ### Enable the Paid Endpoint Add the returned IDs to your gateway config (`~/.openclaw/openclaw.json`) and enable the paid endpoint: ```json theme={null} { "plugins": { "entries": { "nevermined": { "enabled": true, "config": { "nvmApiKey": "sandbox:eyJhbG...", "environment": "sandbox", "planId": "", "agentId": "", "enablePaidEndpoint": true, "agentEndpointPath": "/nevermined/agent", "creditsPerRequest": 1 } } } } } ``` Restart the gateway. The paid endpoint handles the full x402 lifecycle automatically: 1. Extracts the `payment-signature` header from incoming requests 2. Calls `verifyPermissions` to check the subscriber has credits 3. Processes the request through your agent handler 4. Calls `settlePermissions` to burn credits 5. Returns the response with a `payment-response` header ## Query a Paid Agent From the subscriber side (same or different gateway), the full flow works from chat: **Order a plan:** > Order the Legal Assistant plan `` The gateway calls `nevermined_orderPlan` and credits are granted. **Query the agent:** > Ask the Legal Assistant at `https://my-gateway.example.com/nevermined/agent` to summarize the latest GDPR amendments The gateway calls `nevermined_queryAgent`, which acquires an x402 token, sends the prompt with a `payment-signature` header, and returns the response — all in one step. **Check your balance:** > Check my Nevermined balance for plan `` Credits are decremented after each successful query. ## Custom Agent Handlers The plugin includes a default handler for demonstration. To use your own logic, pass a custom `agentHandler` when registering the plugin: ```typescript theme={null} import neverminedPlugin from '@nevermined-io/openclaw-plugin' neverminedPlugin.register(api, { agentHandler: async (body) => { const response = await myAIModel.generate(body.prompt) return { result: response } }, }) ``` The handler receives `{ prompt: string }` and returns any JSON-serializable object. ## Learn More Install and configure the plugin Full configuration reference All tools and slash commands Step-by-step tutorial # Embed Nevermined Widgets Source: https://docs.nevermined.app/docs/integrations/organization-widgets Drop Nevermined checkout, card enrollment, and delegation flows into your own site as iframes signed by your organization. If you run a Nevermined organization, you can embed Nevermined-hosted UI flows directly into your site instead of redirecting customers to nevermined.app. Your end-users stay on your domain; the iframe handles auth, payments, and card delegations against the Nevermined backend. This guide is for organization admins. If you haven't upgraded to an organization yet, see [Platform Partners](/docs/integrations/organizations) first. ## What you can embed Sell agent plans from your own pricing page. Reports a transaction hash on success. Let users add a payment method without leaving your site. Returns the resulting `paymentMethodId`. List enrolled cards, create delegations, and revoke either — without redirecting to nevermined.app. ## How it works ``` Your server → createInitToken({ userId, orgId, secretKey }) → initToken (JWT, valid 5 minutes) Browser → NeverminedWidgets.initialize({ initToken, environment }) → exchanges initToken for a session, mounts iframe(s) Iframe → postMessage events: nvm:ready, nvm:success, nvm:error, nvm:close ``` The widget secret never leaves your server. Your backend mints a short-lived `initToken` per browser session; the SDK exchanges it for a 2-hour widget session that scopes every API call to a single end-user from your system. The `secretKey` is the equivalent of a password for your organization's widget surface. Anyone who has it can mint tokens that act as any end-user of your org. Keep it in environment variables on a server you control — never commit it, never bundle it into client-side JavaScript. ## Setup Open [Settings > Organization](https://nevermined.app/organization) in the Nevermined App and scroll to the **Widget Integration** section. Widget Integration section before any key exists Click **Generate widget key** and fill in the dialog: * **Name** — a descriptive label, e.g. `Production website`. * **Allowed origins** — every domain where the widget will be mounted, e.g. `https://yourcompany.com` and `https://staging.yourcompany.com`. At least one origin is required. Use **Add origin** to add more rows. No path, query, fragment, or trailing slash. Generate widget key dialog with name and allowed origins The Nevermined backend rejects widget sessions whose `parentOrigin` doesn't match this allowlist — it's your second line of defense if the secret ever leaks. You can edit the list later from the key's actions menu without rotating the secret. Click **Generate key**. The raw secret is shown **once**. Copy it and store it as an environment variable on your server (for example, `NVM_WIDGET_SECRET`). You won't see it again. Widget secret shown once after creation If you lose the secret, revoke the key from the dashboard and generate a new one. There's no recovery path — that's by design. Install the server SDK on your backend and expose a small endpoint that signs an `initToken` for the currently logged-in end-user. ```bash theme={null} npm install @nevermined-io/ui-widgets-server ``` The call is the same regardless of framework: ```ts theme={null} import { createInitToken } from '@nevermined-io/ui-widgets-server' const initToken = await createInitToken({ userId: '', orgId: '', secretKey: process.env.NVM_WIDGET_SECRET!, }) ``` Pick the framework that matches your stack: ```ts theme={null} // vite.config.ts import { defineConfig } from 'vite' export default defineConfig({ plugins: [ { name: 'nvm-init-token', configureServer(server) { server.middlewares.use('/api/init-token', async (req, res) => { const { createInitToken } = await import('@nevermined-io/ui-widgets-server') const initToken = await createInitToken({ userId: req.headers['x-user-id'] as string, // resolve from your auth orgId: process.env.NVM_ORG_ID!, secretKey: process.env.NVM_WIDGET_SECRET!, }) res.setHeader('content-type', 'application/json') res.end(JSON.stringify({ initToken, environment: 'sandbox' })) }) }, }, ], }) ``` ```ts theme={null} import express from 'express' import { createInitToken } from '@nevermined-io/ui-widgets-server' const app = express() app.get('/api/init-token', async (req, res) => { const initToken = await createInitToken({ userId: req.user!.id, // from your auth middleware orgId: process.env.NVM_ORG_ID!, secretKey: process.env.NVM_WIDGET_SECRET!, }) res.json({ initToken, environment: 'sandbox' }) }) ``` ```ts theme={null} // app/api/init-token/route.ts import { NextResponse } from 'next/server' import { createInitToken } from '@nevermined-io/ui-widgets-server' import { auth } from '@/lib/auth' export async function GET() { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const initToken = await createInitToken({ userId: session.user.id, orgId: process.env.NVM_ORG_ID!, secretKey: process.env.NVM_WIDGET_SECRET!, }) return NextResponse.json({ initToken, environment: 'sandbox' }) } ``` `userId` identifies the **end-user of your site**, not the org admin. Resolve it from your auth session — Nevermined uses it as the `sub` of the session JWT to scope every widget action to that user. Install the browser SDK on your frontend. ```bash theme={null} npm install @nevermined-io/ui-widgets ``` Fetch the `initToken` from your endpoint and initialize the SDK. The same `nvm` instance can mount any of the available widgets. ```ts theme={null} import { NeverminedWidgets } from '@nevermined-io/ui-widgets' const { initToken, environment } = await fetch('/api/init-token').then(r => r.json()) const nvm = await NeverminedWidgets.initialize({ initToken, environment }) ``` Then mount whichever widget you need into a container element on your page: ```ts theme={null} nvm.checkout.start({ did: 'did:nv:', // planId is optional; omit to show the plan carousel container: document.getElementById('checkout-widget')!, onReady: () => console.log('Widget rendered'), onSuccess: ({ did, planId, txHash }) => { console.log('Purchase complete', { did, planId, txHash }) }, onError: (err) => console.error('Checkout failed', err), onClose: () => console.log('User closed the widget'), }) ``` ```ts theme={null} nvm.delegations.enrollCard({ container: document.getElementById('enroll-widget')!, onSuccess: ({ paymentMethodId }) => { console.log('Card enrolled', paymentMethodId) }, onError: (err) => console.error('Enrollment failed', err), }) ``` ```ts theme={null} nvm.delegations.listCards({ container: document.getElementById('cards-widget')!, onCardAction: ({ action, paymentMethodId }) => { if (action === 'delegate') { nvm.delegations.createDelegation({ paymentMethodId, container: document.getElementById('cards-widget')!, onSuccess: ({ delegationId }) => console.log('Delegated', delegationId), }) } }, }) ``` ## Available widgets | Method | Mounts iframe | Result | | ----------------------------------------------------------------------- | -------------------- | --------------------------------------------------------- | | `nvm.checkout.start({ did, planId?, container, ... })` | yes | `onSuccess`: `{ did, planId, txHash }` | | `nvm.delegations.enrollCard({ container, ... })` | yes | `onSuccess`: `{ paymentMethodId }` | | `nvm.delegations.listCards({ container, onCardAction, ... })` | yes | `onCardAction`: `{ action: 'delegate', paymentMethodId }` | | `nvm.delegations.createDelegation({ paymentMethodId, container, ... })` | yes | `onSuccess`: `{ delegationId, paymentMethodId }` | | `await nvm.delegations.revokeCard(paymentMethodId)` | no — direct API call | resolves on 2xx | | `await nvm.delegations.revokeDelegation(delegationId)` | no — direct API call | resolves on 2xx | All iframe-mounting methods share the same lifecycle: | Callback | When | | ----------- | ------------------------------------------- | | `onBooted` | iframe DOM mounted (auth not yet validated) | | `onReady` | session validated, widget interactive | | `onSuccess` | terminal success — widget destroys itself | | `onError` | terminal error — widget destroys itself | | `onClose` | user closed the widget — instance destroyed | Once a widget instance fires `onSuccess`, `onError`, or `onClose`, it's terminal: calling its method again throws. Construct a fresh widget via the same `nvm` instance to mount another flow. ## Environments Pick the environment that matches the Nevermined dashboard you used to mint the widget key. | Environment | API base | Webapp base | When to use | | ----------- | ------------------------------------ | ------------------------ | ------------------------------------------------ | | `sandbox` | `https://api.sandbox.nevermined.app` | `https://nevermined.app` | Testing on Base Sepolia with sandbox credentials | | `live` | `https://api.live.nevermined.app` | `https://nevermined.app` | Production on Base mainnet | ## Production checklist Keep `NVM_WIDGET_SECRET` in your runtime env vars. Never commit it, never ship it to the browser, never put it in `NEXT_PUBLIC_*` or `VITE_*` variables. Add every domain where you mount the widget. The backend rejects sessions from any other origin. Serve both your site and your initToken endpoint over HTTPS in production. `parentOrigin` includes the scheme, so origins must match exactly. Pass the actual end-user identifier from your auth — never a placeholder, never the same value for every visitor. If the secret might have leaked, revoke the key from the dashboard and generate a new one. Update your env var and redeploy. Default `initToken` expiry is 5 minutes — leave it. Don't cache the token server-side. ## Troubleshooting | Symptom | Cause | Fix | | ------------------------------------------------------------ | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `onError`: `UNAUTHORIZED`, apiCode `BCK.WIDGET_SESSION.0001` | initToken expired or signed with a wrong/revoked secret | Mint a fresh token; verify your secret matches the active key in the dashboard | | `onError`: `UNAUTHORIZED`, apiCode `BCK.WIDGET_SESSION.0002` | The org has no active widget keys | Generate one in the dashboard | | `onError`: `UNAUTHORIZED`, apiCode `BCK.WIDGET_SESSION.0005` | Session token expired (default 2h) | The SDK auto-refreshes; if it fails persistently, re-fetch the initToken from your server | | `onError`: `UNAUTHORIZED`, apiCode `BCK.WIDGET_SESSION.0007` | initToken missing `orgId` or `wallet` claim | Upgrade `@nevermined-io/ui-widgets-server` to the latest version | | `onError`: `UNAUTHORIZED`, apiCode `BCK.WIDGET_SESSION.0011` | The widget key was revoked while the session was alive | Mint a new `initToken` with the new key | | `onError`: `NETWORK` | Fetch failed before getting a response | Check your network, CORS, and that the `environment` matches the API you minted the key against | | Widget mounts but never fires `onReady` | `parentOrigin` doesn't match `allowedOrigins` | Add your site's origin to the key's allowlist (mind the scheme: `https://` vs `http://`) | | `[NeverminedWidgets] initialize: invalid environment` | `environment` is not a supported value | Use `'sandbox'` or `'live'` | ## Related Background on Nevermined organizations and how to upgrade your account. How Nevermined card enrollment works under the hood — the widget surfaces this same flow. # Platform Partners Source: https://docs.nevermined.app/docs/integrations/organizations Turn your AI marketplace into a revenue-sharing network. Let your builders monetize their agents while you earn a commission on every sale. You run an AI platform. Your builders create agents. Nevermined lets you monetize both sides — builders earn from their agents, and you take a cut on every transaction. No custom billing infrastructure required. ## Who is this for? Platform Partners is designed for **AI marketplaces and developer platforms** that host communities of agent builders. If you're running a platform where developers build and publish AI agents, tools, or services, this is how you turn that ecosystem into a revenue engine. Set your commission rate. When any builder on your platform sells a plan, you automatically receive your cut. Your logo, your colors, your messaging on the checkout page where clients purchase plans from your builders. ## How It Works Go to [Settings > Organization](https://nevermined.app/organization) in the Nevermined App and subscribe to the Platform Partner plan. Set your organization name, commission rates (for both crypto and fiat payments), and customize the checkout appearance. Add builders to your organization. They register agents and create plans as usual — your branding and commission apply automatically. When a client purchases a plan from one of your builders, fees are deducted and your commission is disbursed automatically. ## Revenue Flow When a client purchases a plan from a builder on your platform: ``` Client pays for plan | |-> Plan value |-> Stripe fees (deducted) |-> Nevermined fees (deducted) |-> Your platform commission (sent to you) |-> Builder payout (remainder) ``` You set the commission rate. Nevermined handles the split and disbursement. ## What You Get | Feature | Description | | ------------------------ | ------------------------------------------------------------------------------ | | **Commission control** | Set your own percentage for crypto and fiat transactions | | **Branded checkout** | Custom logo, colors, and messaging on all purchase flows | | **Builder management** | Manage who can sell through your platform | | **Automatic settlement** | No manual invoicing — commissions are calculated and disbursed per transaction | ## Getting Started Builder onboarding is currently handled with our team. Contact [contact@nevermined.ai](mailto:contact@nevermined.ai) to set up your organization and add your first builders. Manage your platform from the dashboard Understand how fiat payments and payouts work ## Widgets Embed Nevermined checkout, card enrollment, and delegation flows directly into your site as iframes — without redirecting your customers to nevermined.app. Generate a widget key from your organization settings and use the official SDKs to render the flows on any page you control. Step-by-step guide: generate a widget key, mint init tokens server-side, and mount checkout / card / delegation widgets on your site. # 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="sandbox: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 # Braintree Onboarding Source: https://docs.nevermined.app/docs/products/nvm-pay/braintree-onboarding Connect your Braintree merchant account to Nevermined Pay so buyers can pay your plans by card and receive funds in your supported currencies. ## Overview Nevermined Pay supports **Braintree** as a fiat settlement provider alongside Stripe. Sellers who connect a Braintree merchant account can price their plans in Braintree-supported currencies and receive funds directly into their own Braintree gateway, while Nevermined collects the platform fee on the same card transaction. This page covers the seller-side setup: connecting your Braintree account, configuring per-currency merchant accounts, and what to expect at plan creation and settlement time. If you only need to *pay* for a Braintree-priced plan as a buyer, see [Card Enrollment → Braintree](/docs/products/nvm-pay/card-enrollment) instead. ## Why Braintree? Use Braintree when: * You already operate on Braintree and want funds to land directly in your existing gateway. * You need merchant-of-record relationships under your own underwriting agreements (per currency). * You want a uniform `transaction.sale` settlement on both subscription checkout and per-request x402 charges, without holding a Stripe Connected Account. Stripe remains the right choice when you want the simplest onboarding (Stripe Connect express) and don't have a multi-currency requirement that maps cleanly to Braintree's child merchant account model. The choice is per plan, not per seller — `fiatPaymentProvider` is set on each plan at creation time, so a single seller can run some plans on Stripe and others on Braintree. ## Prerequisites Before connecting Braintree to Nevermined Pay, you need: Sign up at [braintreepayments.com](https://www.braintreepayments.com/). Sandbox is enough for development; you'll need a production gateway before charging real cards. Each Braintree merchant account is bound to a single currency. To accept USD-priced plans you need a USD merchant account; to accept EUR-priced plans you need an EUR merchant account. Sellers add new currencies in their own Braintree dashboard — Nevermined never creates merchant accounts on your behalf. Nevermined Pay uses Braintree's OAuth Connect (Braintree Auth) to talk to your gateway. The user who authorizes the connection must be able to grant gateway scopes on your account. Currently supported plan currencies for Braintree are **USD** and **EUR**. You can configure both, either, or just one — Nevermined gates plan creation per currency based on what your gateway actually supports. ## Connecting Your Braintree Account Connection happens once, from your Nevermined profile page: Go to your profile in the [Nevermined App](https://nevermined.app) and find the **Payment Provider** section. You'll be redirected to Braintree's OAuth consent screen, which lists the gateway scopes Nevermined Pay needs (transaction creation, refunds, settlement management, and merchant account discovery). Approve the connection. Braintree redirects you back to Nevermined Pay. The facilitator stores the OAuth access and refresh tokens server-side, then immediately calls Braintree to enumerate the child merchant accounts on your gateway. Your profile now shows one badge per currency Nevermined Pay can charge on your behalf — for example **USD** and **EUR**. If a currency is missing, add the corresponding child merchant account in your Braintree dashboard, then disconnect and reconnect to re-discover. ### What gets stored After connecting, Nevermined Pay holds three pieces of information about your account: | Stored | Purpose | | ---------------------------------- | ------------------------------------------------------------------------------------------- | | OAuth access + refresh tokens | Authorize server-side calls to your Braintree gateway. Refresh tokens rotate automatically. | | Braintree merchant ID | Identifies your gateway when constructing per-request `BraintreeGateway` clients. | | `currency → merchantAccountId` map | Routes each charge to the right child merchant account at settlement time. | Raw card data, customer details, and transaction PII never leave Braintree — Nevermined only stores the routing handles. ### Disconnecting Click **Disconnect Braintree** on your profile to revoke the connection. Nevermined Pay clears all four fields above (access token, refresh token, merchant ID, and the per-currency map). Existing delegations against your account stop being chargeable on the next request. ## Per-Currency Merchant Accounts Braintree binds each merchant account to a single currency. To price a plan in EUR you need an EUR merchant account on your gateway; pricing the same plan in USD additionally needs a USD merchant account. This is a Braintree-side requirement, not a Nevermined one. Adding a merchant account to your gateway is a self-service action in the Braintree dashboard for sandbox accounts; production accounts may require Braintree to complete underwriting first. After adding a new currency in Braintree: 1. Disconnect Nevermined Pay's Braintree connection. 2. Reconnect — re-authorization completes in two clicks because Braintree skips the consent screen for the same `client_id`. 3. The newly available currency badge appears on your profile. There is no manual "Refresh" button. Currency changes on your side are rare enough that the disconnect+reconnect path is the simplest way to re-trigger discovery. ## Plan Creation: Currency Guard When you create a plan with `fiatPaymentProvider: 'braintree'`, Nevermined Pay checks the plan's currency against the merchant accounts discovered at connection time: * **Currency available** → plan creation succeeds. Buyers can purchase the plan and receive credits. * **Currency missing** → plan creation is rejected with `BCK.BRAINTREE.0008`. Add the child merchant account in your Braintree dashboard, then disconnect and reconnect Nevermined Pay before retrying. The plan-creation form in the Nevermined App enforces this client-side too: when you select Braintree as the provider, the currency dropdown only shows the currencies your gateway supports, with a tooltip pointing back to your profile if none are available yet. ## Settlement Model Once a plan is live, Nevermined Pay settles each fiat charge with a **two-transaction split** on the buyer's vaulted card: 1. **Platform fee** is charged on Nevermined's own Braintree gateway, against Nevermined's per-currency platform merchant account. 2. **Merchant share** is charged on your OAuth-connected gateway, against your per-currency child merchant account that matches the plan's currency. If the merchant leg fails after the platform fee has been collected, Nevermined automatically voids the platform transaction so you never end up paying a fee for a charge the buyer didn't complete. This split applies to both: * One-time **subscription checkouts** through the Nevermined App's Braintree Drop-in. * Per-request **x402 settlements** for credit top-ups when a buyer's balance is insufficient (see [the x402 card-delegation spec](/docs/specs/x402-card-delegation) for the protocol-level details). For very small charges where the platform fee would round to zero or negative, Nevermined falls back to a single merchant transaction with no fee collected — preserving correctness over revenue. ## Limitations * **Currencies**: USD and EUR only. Reach out if you need additional currencies; we'll add them when there's a clear use case. * **Refresh tokens rotate**: long-lived CI tokens against real Braintree accounts are not feasible. Test integrations should mock the gateway or use a developer's interactive sandbox session. * **Re-discovery requires reconnect**: changing your gateway's set of merchant accounts requires disconnecting and reconnecting Nevermined Pay. Currency changes on the seller side are rare enough that this is acceptable; let us know if it becomes a friction point. ## Troubleshooting | Symptom | Likely cause | Fix | | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | "No currency-specific merchant accounts found" on your profile | Your Braintree gateway has merchant accounts but they weren't discovered (e.g. legacy OAuth scope, or all accounts are inactive) | Disconnect and reconnect; the OAuth flow re-runs discovery with the latest scope. | | Plan creation rejected with `BCK.BRAINTREE.0008` | The chosen currency has no child merchant account on your gateway | Add the merchant account in Braintree, then disconnect and reconnect Nevermined Pay. | | x402 charge fails with `BCK.BRAINTREE.0007` at settlement time | Your gateway no longer has a merchant account in the plan's currency (e.g. it was deactivated after plan creation) | Re-add the merchant account in Braintree and reconnect. Existing credit balances on buyers' accounts can still be redeemed; only new card charges are blocked. | ## Related Pages How buyers vault a Braintree card before purchasing your plans. Protocol-level details for per-request fiat settlement, including the two-transaction split. # Card Enrollment Source: https://docs.nevermined.app/docs/products/nvm-pay/card-enrollment Enroll Visa, Stripe, or Braintree payment cards into NVM Pay with PCI-compliant tokenization and secure authentication. ## Overview Before an agent can charge a payment card, the card must be enrolled in NVM Pay. Enrollment tokenizes the card data (NVM Pay never stores raw card numbers), verifies the cardholder's identity, and registers the card with the payment provider. NVM Pay supports three enrollment providers, each available as a separate tab in the [Nevermined Pay dashboard](https://pay.nevermined.app). The right provider for a given plan is determined by the seller's `fiatPaymentProvider` metadata: Stripe-priced plans require a Stripe-vaulted card, and Braintree-priced plans require a Braintree-vaulted card. ## Enrollment Paths ### Visa Enrollment Visa cards are tokenized via Visa Token Service (VTS) and enrolled in Visa Intelligent Commerce (VIC). This path requires passkey (FIDO2) authentication. Enter your card details through the enrollment form. Card data is captured in a PCI-compliant VGS Collect iframe and tokenized in the browser before submission. NVM Pay registers the card with Visa Token Service (VTS), producing a **provisioned token ID** that replaces the real card number. The tokenized card is enrolled in VIC, which enables mandate-based charging. Complete passkey authentication (FIDO2/WebAuthn) to prove you authorized the enrollment. If passkeys aren't available, a one-time password (OTP) is sent to your email instead. After enrollment, create a [spending mandate](/docs/products/nvm-pay/mandates) to define how agents can charge this card. #### Visa Card Lifecycle ``` Enrollment started | |-> PAN enrolled (VTS tokenization) | |-> Token provisioned (provisioned token ID created) | |-> VIC enrolled (payment network registration) | |-> Authentication (passkey or OTP) | |-> Active -> Mandates can be created | |-> Inactive (card deactivated or removed) ``` You can track enrollment progress in the dashboard. Each step is recorded, and if enrollment fails at any point, the specific step and error are captured. ### Stripe Enrollment Stripe cards are captured via Stripe Elements and tokenized through Stripe SetupIntents. This is a simpler flow with no passkey requirement. Enter your card number, expiry, and CVC through the Stripe Elements form. Card data goes directly to Stripe and never touches the NVM Pay backend. Stripe creates a SetupIntent and confirms the card. This validates the card and saves it as a reusable payment method. NVM Pay finalizes the enrollment by linking the Stripe payment method to your account. The card appears in your dashboard immediately. After enrollment, create a [spending delegation](/docs/products/nvm-pay/mandates) to define how agents can charge this card. #### Stripe Card Lifecycle ``` SetupIntent created | |-> Card confirmed (Stripe tokenization) | |-> Enrollment finalized (linked to NVM Pay account) | |-> Active -> Delegations can be created | |-> Removed (card and all delegations revoked) ``` ### Braintree Enrollment Braintree cards are captured via the Braintree Drop-in UI (or hosted fields) and exchanged for a vaulted payment-method token. The flow is similar to Stripe — no passkey is required — but uses Braintree's tokenization and vaulting primitives so the resulting token can be charged by the seller's OAuth-connected Braintree merchant at settlement time. The Nevermined App requests a short-lived **Braintree client token** from the facilitator. The client token authorizes the in-browser Drop-in / hosted fields to talk directly to Braintree. Enter your card number, expiry, and CVC into the Braintree Drop-in. Card data goes directly to Braintree and never touches the Nevermined backend. Drop-in returns a single-use **payment-method nonce** to the browser. The browser submits the nonce to the facilitator. The facilitator looks up (or creates) your Braintree customer record and exchanges the nonce for a permanent **`paymentMethodToken`** by vaulting the card with `verifyCard: true`. From this point on, NVM Pay only ever stores the token — never raw card data. After enrollment, create a [spending delegation](/docs/products/nvm-pay/mandates) backed by the vaulted Braintree token. The delegation can then settle x402 charges against any Braintree-priced plan in the supported currency. #### Braintree Card Lifecycle ``` Client token requested | |-> Card details captured (Braintree Drop-in tokenization) | |-> Nonce returned to browser (single-use) | |-> Card vaulted (paymentMethodToken issued, verifyCard pass) | |-> Active -> Delegations can be created | |-> Removed (card and all delegations revoked) ``` A Braintree-vaulted card can only be charged for plans whose seller has a Braintree merchant account in the card's transaction currency. See [Braintree onboarding](/docs/products/nvm-pay/braintree-onboarding) for the seller-side per-currency requirements. ## Card Properties Visa, Stripe, and Braintree cards share these core properties: | Property | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | Unique card identifier | | `userId` | Owner of the card | | `last4` | Last 4 digits of the card number (for display) | | `alias` | User-friendly name (e.g., "Work Visa", "Dev Card") | | `status` | `active` or `inactive` | | `spendingCeiling` | Maximum total amount that can be allocated across all active mandates or delegations on this card (default: \$10.00) | ## Spending Ceiling Every card has a **spending ceiling**, a cumulative limit on the total amount allocated to active authorizations. This prevents over-allocation and provides a safety net. Both Visa and Stripe cards enforce the same ceiling. For example, if your card has a \$10.00 ceiling: * Authorization A: \$5.00 * Authorization B: \$3.00 * Remaining capacity: \$2.00 The default spending ceiling is \$10.00 during the pilot phase. Contact the Nevermined team if you need a higher limit. When you create or update a mandate/delegation, NVM Pay validates that the total allocated amount across all active authorizations on the card doesn't exceed the ceiling. If it would, the request is rejected with a clear error showing the remaining capacity. ## Managing Cards ### Via the Nevermined Pay Dashboard The [Nevermined Pay dashboard](https://pay.nevermined.app) provides a visual interface for managing your enrolled cards. Visa, Stripe, and Braintree cards appear in separate tabs: * View all enrolled cards with their status and spending ceiling * See active mandates/delegations per card with remaining capacity * View transaction history for each card * Deactivate or remove cards ### Via the API | Endpoint | Method | Description | | ------------------------------ | -------- | --------------------------------------------- | | `/cards` | `GET` | List enrolled Visa cards (filter by `userId`) | | `/cards/{id}` | `GET` | Get card details including mandates | | `/cards/{id}` | `PUT` | Update card properties | | `/cards/{id}` | `DELETE` | Remove a card and its mandates | | `/cards/{cardId}/transactions` | `GET` | Get transaction history (paginated) | | Endpoint | Method | Description | | -------------------------------------- | -------- | -------------------------------------- | | `/api/v1/payment-methods` | `GET` | List enrolled Stripe cards | | `/api/v1/payment-methods/{id}` | `PATCH` | Update card alias or allowed API keys | | `/api/v1/payment-methods/{id}` | `DELETE` | Remove card and revoke all delegations | | `/api/v1/delegation/{id}/transactions` | `GET` | Get transaction history per delegation | ## Choosing a Provider | | Visa | Stripe | Braintree | | ------------------------- | ----------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------- | | **Enrollment complexity** | Multi-step with passkey auth | Simple card form | Simple card form (Braintree Drop-in) | | **Authentication** | Passkeys (FIDO2) or OTP | Stripe handles verification | Braintree `verifyCard` at vault time | | **Best for** | High-security use cases, production deployments | Quick setup, developer testing, broader card support | Plans whose seller settles on Braintree (per-currency merchant accounts, OAuth Connect) | | **Settlement** | Visa credentials charged via Stripe | Stripe PaymentIntents directly | Braintree `transaction.sale` against vaulted `paymentMethodToken` | All three providers support the same spending controls (ceilings, usage limits, expiration) and API key linking. The right enrollment provider for a given plan is determined by the plan's `fiatPaymentProvider` metadata: a Braintree-priced plan can only be paid by a Braintree-vaulted card. ## What's Next? Once your card is enrolled, create a spending authorization to control how agents can charge it. Define spending limits, usage caps, and expiration for agent payments # FAQ Source: https://docs.nevermined.app/docs/products/nvm-pay/faq Frequently asked questions about NVM Pay, card delegation, mandate selection, Visa virtual cards, and Stripe settlement. ## Agent Payments How agents pay with your real card, within limits you control. Card Delegation uses your real credit or debit card, not a separate prepaid balance. At enrollment, the card number is tokenized by a PCI-compliant vault. Nevermined never sees or stores the raw PAN. What the agent receives is a scoped credential that encodes identity, spending limits, and merchant restrictions. The actual charge still runs on existing card rails, so it appears on your normal statement. No prefunding, no separate balance to manage. The transaction is rejected instantly before it reaches the payment processor. Mandates are enforced server-side on every request, with no client-side honor system. The agent receives a clear 402 error explaining which limit was hit (amount cap, transaction count, or time window), so it can adapt its behavior or notify the user. Yes. Any software that can make an HTTP request can use x402 payments. That includes OpenAI Assistants, Anthropic Claude, LangChain, CrewAI, Strands, MCP tool servers, and custom agents. Nevermined also provides TypeScript and Python SDKs, a CLI, and an MCP server for convenience, but none are required. One click in the [dashboard](https://pay.nevermined.app) or one API call. Revocation is immediate: no grace period, no cached tokens, and no pending window. The agent loses payment capability the instant you revoke, and any in-flight requests using that mandate will fail. From the agent's perspective, nothing changes. It sends the same x402 payment header either way. The difference is in how the payment settles under the hood: * **Stripe path** - Your card is tokenized by Stripe, and the payment settles through Stripe's network. The merchant connects their Stripe account when they enroll with Nevermined. * **Visa path** - Your card is tokenized by Visa Token Service (VTS), and each payment generates a one-time VIC cryptogram that settles through standard Visa acquiring rails. The merchant doesn't need Stripe; they enroll with Nevermined and receive payments through their existing acquirer. In both cases, merchants enroll with Nevermined to accept payments from AI agents. Nevermined handles the x402 protocol, agent authentication, and settlement on their behalf. Card data is captured via a PCI-compliant vault (VGS) and tokenized before it enters the system. Nevermined holds ISO 27001 and SOC 2 Type II certifications and operates at PCI SAQ-D level. Strong Customer Authentication (3DS or FIDO2 passkeys) is enforced at enrollment. Every transaction is logged with agent ID, amount, merchant, and timestamp for full auditability. ## Mandate Selection How agents pick which mandate or delegation to charge. There are three ways the system resolves which mandate to charge, from simplest to most explicit: 1. **Automatic** - If your API key has access to exactly one active mandate, it is selected automatically. Just pass your API key and go. If there are zero or multiple mandates, you get a clear error asking you to be more specific. 2. **Key-linked** - Link a specific mandate to a specific API key, either when you create the mandate or afterwards. The system always routes that key to its linked mandate, even if you have multiple mandates active. Only the linked key can use that mandate. 3. **Explicit mandate ID** - Pass the mandate ID directly in your API request for full control. Useful when one agent works with multiple cards or mandates and needs to pick per-request. See [Mandate Selection](/docs/products/nvm-pay/mandate-selection) for the full resolution algorithm. If you have one mandate and one API key, you don't need to link them. Automatic selection handles it. Linking becomes useful when you have multiple mandates and want each agent (or API key) to use a specific one without passing a mandate ID on every request. It's the recommended approach for multi-agent setups. The automatic selection will fail because the system can't determine which mandate to charge. Your agent receives a 402 error explaining that multiple mandates are available. You can resolve this by either linking your API key to a specific mandate, or passing the mandate ID explicitly in the request. Yes. API keys are provider-agnostic. The same key works for both Visa mandates and Stripe delegations. However, key-to-mandate linking is per-provider. If you link a key to a Visa mandate, that linking does not affect Stripe delegations, and vice versa. On the [enrollment page](https://pay.nevermined.app), click **Update** on the mandate and use the API Key dropdown to assign or reassign the key. You can also set it to "None" to unlink. For Stripe delegations, use the **Edit** button on the delegation card. ## Stripe Settlement through Stripe's network. When you enroll a card via Stripe, Stripe tokenizes the card number into a platform token. When your agent makes a payment, Nevermined uses this token to create a charge through Stripe's network. The payment is wrapped in the x402 protocol, so the merchant (or a facilitator) triggers the flow by responding with a 402 status code. The merchant enrolls with Nevermined and connects their Stripe account. Once enrolled, they can receive payments from AI agents via the x402 protocol. The merchant needs an active Stripe account since the Stripe path settles through Stripe's network. Nevermined handles agent authentication, mandate enforcement, and the x402 flow on the merchant's behalf. Stripe supports most Visa, Mastercard, American Express, and Discover cards. Both credit and debit cards are eligible. The card is tokenized at enrollment and charges appear on your normal statement. ## Visa Virtual Cards Settlement through production Visa infrastructure. Production. Nevermined is live on Visa Token Service (VTS) and Visa Intelligent Commerce (VIC), the same infrastructure behind billions of real-world transactions. Your enrolled card is tokenized by Visa into a virtual card token (DPAN), and every agent payment generates a one-time virtual card credential that is settled through real acquiring banks. At enrollment, your real card number (PAN) is sent directly from a PCI-compliant form (VGS) to Visa. It never passes through or is stored by Nevermined. Visa replaces it with a virtual card token (DPAN) that is bound specifically to Nevermined as a token requestor. Even if the DPAN were compromised, it can't be used outside of the Nevermined ecosystem, and Visa can revoke it instantly. Visa Intelligent Commerce (VIC) is the programmable layer on top of tokenization. When your agent needs to pay, VIC generates a one-time payment credential that authorizes exactly one transaction, then it's gone. This means even if an agent is compromised, there's no reusable payment credential to steal. It's purpose-built for the kind of high-frequency, autonomous micropayments that AI agents make. The merchant enrolls with Nevermined to start receiving agent payments. They don't need a Stripe account. The Visa path settles through standard Visa acquiring rails, so payments arrive through the merchant's existing acquirer as normal Visa transactions. Nevermined handles agent authentication, mandate enforcement, and the x402 flow on their behalf. During the pilot, Visa virtual cards can be issued for Visa cards from a limited number of US issuers (excluding Chase, Citi). Most corporate and prepaid cards are not eligible. ## Limits & Merchant Onboarding Pilot ceilings, increased limits, and accepting agent payments. During the pilot, each enrolled card has a \$10.00 cumulative ceiling across all active mandates. This applies to both Stripe and Visa paths independently. Individual mandate amounts are validated against the remaining ceiling at creation and update time. Yes. The \$10 ceiling is a pilot default. Contact the Nevermined team to request increased limits for your account. Contact the Nevermined team to schedule a merchant onboarding call. Once enrolled, your business can accept payments from AI agents via the x402 protocol with no changes to your existing payment infrastructure. Nevermined handles agent authentication, mandate enforcement, and the settlement flow on your behalf. # Mandate Selection Source: https://docs.nevermined.app/docs/products/nvm-pay/mandate-selection How NVM Pay automatically resolves which mandate or delegation to charge when an agent makes a payment request. Three-tier priority with explicit, key-linked, and fallback modes. ## The Problem When a subscriber has multiple active mandates or delegations across their enrolled cards, the agent needs to know which one to use. Should it charge the $5 mandate on Card A or the $10 delegation on Card B? NVM Pay solves this with a **three-tier selection algorithm** that automatically resolves the right authorization based on priority rules. The three-tier selection is currently implemented for Visa mandates via the `POST /access-token/from-nvm-key` endpoint. Stripe delegations use the separate delegation API (`POST /api/v1/delegation/create`) with explicit delegation IDs. The same API key linking and selection concepts apply to both providers. ## Three-Tier Resolution When an agent calls `POST /access-token/from-nvm-key`, NVM Pay resolves the mandate in this order: If the agent passes a `mandateId` in the request body, NVM Pay uses that mandate directly. No ambiguity, the agent knows exactly which mandate it wants. ```json theme={null} POST /access-token/from-nvm-key Authorization: Bearer { "amount": "1.00", "mandateId": "your-specific-mandate-id" } ``` NVM Pay validates: * The mandate exists and is `active` * The mandate has remaining usage (`usageCount < maxUsage`) * The mandate amount covers the requested amount * If the mandate is linked to an API key, it must match the calling key * The associated card is active and belongs to the authenticated user If no `mandateId` is passed, NVM Pay checks if any mandate is **linked to the calling API key**. If exactly one key-linked mandate matches, it's selected automatically. This is the recommended approach for production setups with multiple mandates. Link each API key to a specific mandate and the routing is deterministic. ```json theme={null} POST /access-token/from-nvm-key Authorization: Bearer { "amount": "1.00" } ``` NVM Pay collects all mandates where `mandate.apiKeyId === calling_key.skId`, then filters for active, valid, and unused mandates. Key-linked mandates **always take priority** over unlinked ones. If no key-linked mandates exist, NVM Pay falls back to **unlinked mandates** (mandates with no `apiKeyId` set). If exactly one unlinked mandate matches, it's selected. This mode is convenient when you only have one mandate. You don't need to link anything. ## Decision Flowchart ``` Agent calls POST /access-token/from-nvm-key | |- mandateId in body? | |- YES -> Use that mandate (validate ownership, status, amount, usage) | |- NO -> Continue to auto-resolution | |- Collect all user's mandates across all cards | |- Key-linked: mandate.apiKeyId === current key's skId | |- Unlinked: mandate.apiKeyId is null | |- Filter each group: active, has instructionId, amount >= requested, usage not exhausted | |- Key-linked mandates found? | |- Exactly 1 -> Use it | |- Multiple -> Error: "Multiple active mandates found" | |- None -> Fall through to unlinked | |- Unlinked mandates found? |- Exactly 1 -> Use it |- Multiple -> Error: "Multiple active mandates found" |- None -> Error: "No active mandate found" ``` ## When to Use Each Mode Multiple mandates, full control. Pass `mandateId` per request. One mandate per API key. Set it once, forget it. Single mandate, zero config. Just create and go. ## Linking an API Key API key linking works the same way for both Visa mandates and Stripe delegations. ### When Creating In the [Nevermined Pay dashboard](https://pay.nevermined.app), open the Create Mandate or Create Delegation dialog and select an API key from the dropdown. Only active, non-browser API keys that aren't already linked to another authorization are shown. ### When Updating Use the Update dialog in the dashboard to change or remove the linked API key. You can: * Link to a different API key * Unlink (set to "none") to make the authorization available as a fallback Linking an API key is optional but recommended when you have two or more active mandates or delegations. Without it, NVM Pay can't automatically determine which one to use and will return an error. ## Error Handling ### Multiple Mandates Found ``` "Multiple active mandates found (a1b2c3d4, e5f6g7h8). Pass a mandateId to select one, or link a mandate to your API key." ``` **Fix:** Either pass `mandateId` explicitly, or link one of the mandates to your API key in the dashboard. ### No Active Mandate Found ``` "No active mandate found (check mandate amount, usage, status, and key restrictions)" ``` **Possible causes:** * All mandates are exhausted (usage limit reached) * All mandates have expired * Mandate amount is less than the requested amount * Mandates are linked to a different API key ### Mandate Linked to Different Key ``` "This mandate is linked to a different API key" ``` **HTTP 403.** The explicit `mandateId` you passed is linked to a different API key than the one you're authenticating with. Use the correct API key, or update the mandate's key link. ### Amount Insufficient ``` "Mandate amount ($5.00) is less than requested ($10.00)" ``` The mandate's spending ceiling is lower than the transaction amount. Create a new mandate with a higher amount, or reduce the transaction amount. ## API Reference ### Generate Token from NVM Key (Visa) ``` POST /access-token/from-nvm-key Authorization: Bearer Content-Type: application/json ``` **Request:** ```json theme={null} { "amount": "1.00", "mandateId": "optional-mandate-id", "resource": "https://your-api.com/endpoint", "payTo": "merchant-identifier" } ``` | Field | Required | Description | | ----------- | -------- | ------------------------------------------------------------------ | | `amount` | Yes | Payment amount in USD | | `mandateId` | No | Explicit mandate ID (Mode 1). If omitted, auto-resolution is used. | | `resource` | No | Resource URL for the x402 payload | | `payTo` | No | Merchant/payee identifier | **Response (success):** ```json theme={null} { "success": true, "payload": { "x402Version": 2, "resource": { "url": "https://your-api.com/endpoint" }, "accepted": { "scheme": "visa", "network": "visa:vts", "amount": "1.00", "asset": "USD" }, "payload": { "vProvisionedTokenID": "...", "instructionId": "...", "validAfter": 1700000000, "validBefore": 1700003600, "nonce": "unique-nonce" } }, "payloadEncoded": "base64-encoded-payload-for-http-transport" } ``` The `payloadEncoded` value goes in the `payment-signature` HTTP header when calling a protected resource. **Error responses:** | Status | Reason | | ------ | -------------------------------------------------------------------------- | | 400 | Multiple mandates, mandate exhausted, amount insufficient, invalid request | | 403 | Mandate linked to a different API key, mandate doesn't belong to user | | 404 | No active mandate found, mandate ID not found | ### Stripe Delegation Flow For Stripe, the delegation is selected explicitly when creating it. Use the delegation API: ```bash theme={null} # Create a delegation (returns delegationId + delegationToken) POST /api/v1/delegation/create # List delegations to find the right one GET /api/v1/delegation # Revoke a delegation DELETE /api/v1/delegation/{delegationId} ``` API key linking on Stripe delegations enables the same selection behavior when routing through the NVM Pay platform. ## Best Practices 1. **Start simple.** If you have one mandate or delegation, you don't need to link API keys or pass explicit IDs. Just create the authorization and call the API. 2. **Link keys for production.** When you add a second authorization, link each API key to a specific mandate or delegation. This gives you deterministic routing without code changes. 3. **Use explicit IDs for complex setups.** If an agent works with multiple mandates dynamically (e.g., different mandates for different pricing tiers), pass the `mandateId` in each request. 4. **Monitor usage.** Check usage counts against limits to know when authorizations are close to exhaustion. Create new ones before the old ones run out. 5. **Set reasonable expiration times.** Don't create authorizations that last indefinitely. Set expiration aligned with your billing cycles. # Mandates & Delegations Source: https://docs.nevermined.app/docs/products/nvm-pay/mandates Create scoped spending authorizations that control how much agents can charge, how many transactions they can make, and when the authorization expires. ## What Are Mandates and Delegations? A mandate (Visa) or delegation (Stripe) is a **scoped spending authorization** tied to an enrolled payment card. It defines the boundaries of what an agent can charge: the maximum amount per transaction, how many transactions are allowed, and when the authorization expires. Think of it as a controlled allowance: you're giving an agent permission to spend within strict limits, rather than handing over your card. **Mandates** and **delegations** are the same concept for different providers. Visa calls them mandates, Stripe calls them delegations. The spending controls, API key linking, and selection logic work identically for both. ## Authorization Properties Both mandates and delegations support the same core controls, though the field names differ slightly: | Control | Visa (Mandate) | Stripe (Delegation) | Description | | -------------------- | ------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- | | **Spending limit** | `amount` | `spendingLimitCents` | Maximum amount per transaction | | **Max transactions** | `maxUsage` | `maxTransactions` | Total transactions allowed | | **Expiration** | `expiresAt` (ISO date) | `durationSecs` (seconds from creation) | When the authorization expires | | **Currency** | `currency` (default: USD) | `currency` (default: usd) | Payment currency | | **API key link** | `apiKeyId` | `apiKeyId` | Link to a specific NVM API key for [automatic selection](/docs/products/nvm-pay/mandate-selection) | ## Creating an Authorization ### Via the Nevermined Pay Dashboard After enrolling a card, create a mandate or delegation from the [Nevermined Pay dashboard](https://pay.nevermined.app): 1. Navigate to your enrolled Visa card 2. Click **Create Mandate** 3. Set the spending ceiling (amount per transaction) 4. Set the maximum number of transactions 5. Set an expiration date 6. Optionally link an NVM API key (recommended if you have multiple mandates) 7. Complete passkey authentication to authorize Creating a Visa mandate requires passkey (FIDO2) authentication. This ensures only the cardholder can authorize agent spending. 1. Navigate to your enrolled Stripe card 2. Click **Create Delegation** 3. Set the spending limit in cents (e.g., 1000 = \$10.00) 4. Set the duration in seconds (e.g., 86400 = 24 hours) 5. Optionally set a max transactions limit 6. Optionally link an NVM API key 7. Confirm to create ### Via the API ```bash theme={null} POST /payment-mandate ``` ```json theme={null} { "cardId": "your-card-id", "vProvisionedTokenID": "token-from-enrollment", "amount": 5.00, "currency": "USD", "maxUsage": 100, "expiresAt": "2026-12-31T23:59:59Z", "apiKeyId": "sk_...", "fidoBlob": "passkey-assertion-data", "identifier": "device-attestation-id", "dfpSessionId": "device-fingerprint-session", "verificationResults": "01", "verificationMethod": "23", "userId": "your-user-id" } ``` The response includes an `instructionId`, the Visa instruction that enables agent charging. ```bash theme={null} POST /api/v1/delegation/create ``` ```json theme={null} { "provider": "stripe", "providerPaymentMethodId": "pm_...", "spendingLimitCents": 1000, "durationSecs": 86400, "maxTransactions": 100, "apiKeyId": "sk_...", "currency": "usd" } ``` The response includes a `delegationId` and `delegationToken` for the new authorization. ## Spending Controls Both mandates and delegations enforce multiple layers of spending control: ### Per-Transaction Amount The spending limit sets the maximum a single transaction can charge. If an agent requests more than this amount, the payment is rejected. ### Usage Limits The max transactions cap limits the total number of transactions. NVM Pay tracks usage against this limit. Once the limit is reached, the authorization is exhausted and can't be used for further payments. ### Time-Based Expiration After the expiration time, the authorization can't be used regardless of remaining usage. Visa mandates use an explicit `expiresAt` date. Stripe delegations calculate expiration from `durationSecs` added to the creation timestamp. ### Card Spending Ceiling Each card has a cumulative spending ceiling (default \$10.00). The sum of all active authorization amounts on a card can't exceed this ceiling. This applies to both Visa and Stripe cards. **Example:** | Card Ceiling | Authorization A | Authorization B | Remaining | | ------------ | --------------- | --------------- | --------- | | \$10.00 | \$5.00 | \$3.00 | \$2.00 | If you try to create a third authorization for $3.00, it will be rejected because only $2.00 of ceiling remains. ## Updating an Authorization You can update a mandate's amount, usage limit, expiration, or API key link. ```bash theme={null} PUT /payment-mandate ``` The request targets an existing mandate's `instructionId`. Updating a Visa mandate revokes the existing instruction and creates a new one. Any unspent budget from the previous mandate doesn't carry over. Stripe delegations can't be updated in-place. To change a delegation, you revoke the existing one and create a new one. ```bash theme={null} # 1. Revoke existing delegation DELETE /api/v1/delegation/{delegationId} # 2. Create new delegation with updated params POST /api/v1/delegation/create ``` Revoking and recreating a Stripe delegation means any unspent budget from the old delegation does not carry over to the new one. ## Cancelling an Authorization Cancel a mandate to revoke the agent's spending authorization: ```bash theme={null} POST /cancel-purchase-instruction ``` ```json theme={null} { "instructionId": "the-mandate-instruction-id", "vProvisionedTokenID": "token-from-enrollment", "fidoBlob": "passkey-assertion-data", "identifier": "device-attestation-id", "dfpSessionId": "device-fingerprint-session", "verificationResults": "01", "verificationMethod": "23" } ``` Cancellation requires passkey authentication, just like creation. Revoke a delegation to remove the agent's spending authorization: ```bash theme={null} DELETE /api/v1/delegation/{delegationId} ``` No additional authentication is required beyond your API credentials. ## Status Lifecycle Both mandate and delegation statuses follow the same pattern: ``` Active -> Exhausted (usage limit reached) | |-> Expired (past expiration time) | |-> Revoked / Cancelled (manually removed) ``` Only `active` authorizations can be used for payments. NVM Pay checks status, usage, and expiration on every verify and settle request. Stripe delegations track additional status detail: `remainingBudgetCents` (spending limit minus amount already spent) and `transactionCount` (current usage). ## API Key Linking You can optionally link a mandate or delegation to a specific NVM API key. This tells NVM Pay "when this API key is used, charge this authorization." This is especially useful when you have multiple active authorizations and want deterministic routing. Instead of the agent guessing which one to use, the API key determines it automatically. Both Visa mandates and Stripe delegations support API key linking. See [Mandate Selection](/docs/products/nvm-pay/mandate-selection) for the full resolution algorithm. ## Transaction History Every payment processed through a mandate or delegation is recorded as a transaction: | Field | Description | | ----------------------- | ----------------------------- | | `amount` | Transaction amount | | `currency` | Currency code | | `status` | `completed` or `failed` | | `providerTransactionId` | Stripe charge ID | | `failureReason` | Error details (if failed) | | `createdAt` | When the transaction occurred | ```bash theme={null} GET /cards/{cardId}/transactions?page=1&pageSize=20&status=completed ``` ```bash theme={null} GET /api/v1/delegation/{delegationId}/transactions?offset=0 ``` ## Provider Comparison | | Visa Mandate | Stripe Delegation | | ------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ | | **Create** | `POST /payment-mandate` | `POST /api/v1/delegation/create` | | **Update** | `PUT /payment-mandate` (revoke + recreate internally) | Revoke + create new (explicit two-step) | | **Cancel** | `POST /cancel-purchase-instruction` | `DELETE /api/v1/delegation/{id}` | | **Auth required** | Passkey (FIDO2) for create, update, cancel | API credentials only | | **Expiration format** | ISO date (`expiresAt`) | Duration in seconds (`durationSecs`) | | **Spending limit format** | USD amount (`amount: 5.00`) | Cents (`spendingLimitCents: 500`) | | **Budget tracking** | `usageCount` / `maxUsage` | `amountSpentCents` / `remainingBudgetCents` + `transactionCount` / `maxTransactions` | ## What's Next? If you have multiple mandates or delegations, you'll want to understand how NVM Pay decides which one to use for a given payment request. Learn the three-tier resolution algorithm for automatic mandate and delegation selection # Agent Cards Source: https://docs.nevermined.app/docs/products/nvm-pay/overview Enroll a real card, set spending limits, and let your agents pay autonomously. Enroll a Visa or Stripe card. Set spending limits. Your agents charge it directly via x402, no checkout pages, no human in the loop. Manage everything at [pay.nevermined.app](https://pay.nevermined.app). Enroll Visa or Stripe cards Set spending limits and expiration Auto-resolve which card to charge ## How It Works Add a Visa or Stripe card at [pay.nevermined.app](https://pay.nevermined.app). Card data is tokenized by the provider. NVM Pay never stores raw card numbers. Create a mandate (Visa) or delegation (Stripe): max amount, max transactions, expiration. Optionally link it to a specific API key. Agents call the NVM Pay API with their API key. NVM Pay resolves the right authorization and generates an x402 token. The resource server verifies, executes, and settles. ## Payment Flow ``` Agent (no token) | |-> Resource Server returns 402 + payment-required header | |-> Agent calls NVM Pay: POST /access-token/from-nvm-key | |-> Resolves mandate/delegation -> generates x402 token | |-> Agent retries with payment-signature header | |-> Resource Server: |-> POST /verify -> validates token, authorization, usage |-> Executes workload |-> POST /settle -> charges card, returns payment-response ``` ## Visa vs Stripe Both paths give you the same spending controls and API key linking. Tokenized via Visa Token Service (VTS). Passkey (FIDO2) authentication. Settlement through Visa credentials charged via Stripe. Captured via Stripe Elements. No passkeys required. Settlement via Stripe PaymentIntents directly. ## Key Concepts | Concept | Description | | ------------------------ | ---------------------------------------------------------------------------------------------------- | | **Mandate / Delegation** | A scoped spending authorization on a card. Defines amount ceiling, max transactions, and expiration. | | **Spending Ceiling** | Per-card cumulative limit across all active authorizations (default \$10.00). | | **API Key Linking** | Link a mandate to a specific API key so agents automatically use the right one. | | **x402 Token** | Payment token used in the `payment-signature` HTTP header. | ## Environments * **Sandbox** - Testing and development. Stripe test mode. * **Live** - Production. Real payments through Visa and Stripe. You need an [NVM API Key](/docs/getting-started/get-your-api-key). Set it as `NVM_API_KEY`. ## What's Next? Set up your first payment card Create a mandate or delegation # 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. Token generation requires a `delegationConfig` that controls spending limits and duration. You can either auto-create a delegation inline or reuse an existing one. ```typescript theme={null} import { Payments } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY, environment: 'sandbox' }) // Pattern A: Auto-create a delegation inline const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId, { delegationConfig: { spendingLimitCents: 10000, durationSecs: 604800 } }) // Pattern B: Create a delegation explicitly, then reuse its ID const delegation = await payments.delegation.createDelegation({ provider: 'erc4337', spendingLimitCents: 10000, durationSecs: 604800 }) const { accessToken: token } = await payments.x402.getX402AccessToken(planId, agentId, { delegationConfig: { delegationId: delegation.delegationId } }) ``` ```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') ) # Pattern A: Auto-create a delegation inline token_res = payments.x402.get_x402_access_token(plan_id, agent_id, { 'delegationConfig': { 'spendingLimitCents': 10000, 'durationSecs': 604800 } }) access_token = token_res['accessToken'] # Pattern B: Create a delegation explicitly, then reuse its ID delegation = payments.delegation.create_delegation({ 'provider': 'erc4337', 'spendingLimitCents': 10000, 'durationSecs': 604800 }) token_res = payments.x402.get_x402_access_token(plan_id, agent_id, { 'delegationConfig': { 'delegationId': delegation['delegationId'] } }) 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: Create delegation + 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). The Facilitator sits in the [x402 payment flow](/docs/development-guide/nevermined-x402) — when a server receives a payment token, it delegates verification and settlement to the Facilitator rather than handling it directly. ## 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 # Monetize Static Assets 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 * [Register Agents](/docs/products/nevermined-app/register-agents) in the Nevermined App * [5-Minute Setup](/docs/integrate/quickstart/5-minute-setup) to get started # Monetize Your AI 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. ## Payment Methods Nevermined supports two payment methods for agent monetization: Accept credit cards via Stripe. Users can purchase plans directly, or delegate their card for autonomous agent-to-agent payments. Accept USDC or other ERC-20 tokens on-chain. The native method for agent-to-agent commerce with the lowest fees (1%). ## 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 `payment-signature` tokens and credit balances. 5. **Publish** your agent so other agents can discover and subscribe. ## Learn More * [Register Agents](/docs/products/nevermined-app/register-agents) in the Nevermined App * [Manage Plans](/docs/products/nevermined-app/manage-plans) for pricing and access * [5-Minute Setup](/docs/integrate/quickstart/5-minute-setup) to get your first paid request working * 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) # Cost 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) # API Providers Source: https://docs.nevermined.app/docs/solutions/api-providers Let AI agents buy and top up your API autonomously. One server-side endpoint, settled on a delegated card via x402, no human in the loop. Agents are a new buyer of your API, and they don't onboard the way humans do. They don't sit at a browser, fill out a Stripe Checkout, or remember to top up a credit balance. Today they either stall at your paywall or hardcode a human's API key. Nevermined turns that failure mode into revenue with a small server-side integration. Agents holding a delegated card mint an [x402](/docs/development-guide/nevermined-x402) access token, send it to your endpoint, and you provision or top up access in response. You verify and settle through Nevermined's [x402 facilitator](/docs/products/x402-facilitator/overview); the buyer's card is charged on Stripe or Visa Intelligent Commerce rails, depending on how it was enrolled. ## Featured provider [**Exa**](https://exa.ai) is the live reference. Agents pay Exa \$7 via a Nevermined-delegated card and receive an Exa API key with \$7 of credits. When the key runs out, the agent tops up through the same endpoint. Same key, more credits, no human touched it. * Exa integration mirror: [exa.ai/docs/integrations/nevermined.md](https://exa.ai/docs/integrations/nevermined.md) ## What you build, in five steps On the [Nevermined App](https://nevermined.app/), choose **pay-as-you-go** pricing (one charge per purchase), set your price (e.g. \$7), and grant **1 credit per purchase**. The 1-credit grant means each call to your purchase endpoint maps cleanly to one card charge of your plan price. See [Manage Plans](/docs/products/nevermined-app/manage-plans). Accept a `payment-signature` header on a new route or an existing one. Existing customers keep their flow unchanged. Call `payments.facilitator.verifyPermissions(...)`. Missing or invalid token returns `402 Payment Required` with the payment requirements body. Provision an API key, top up credits, unlock a resource, whatever your product is. Call `payments.facilitator.settlePermissions(...)`. The card is charged, credits are minted and burned, you get paid. ## Server-side handler Your handler does three things: read the `payment-signature` header, verify the token with the Nevermined facilitator, and settle after running your business logic. Examples below for TypeScript, Python, and raw HTTP. ```typescript theme={null} import { Payments, buildPaymentRequired } from '@nevermined-io/payments' const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'live', }) const paymentRequired = buildPaymentRequired(process.env.PLAN_ID!, { endpoint: '/purchase-key', httpVerb: 'POST', scheme: 'nvm:card-delegation', }) // Pre-encode for the `payment-required` response header (per x402 spec §4.3). const paymentRequiredHeader = Buffer.from( JSON.stringify(paymentRequired), ).toString('base64') const send402 = (res) => res .status(402) .setHeader('payment-required', paymentRequiredHeader) .json(paymentRequired) app.post('/purchase-key', async (req, res) => { const token = req.header('payment-signature') if (!token) return send402(res) // maxAmount = credits to burn (not cents). PAYG plan with 1 credit // per purchase means burning 1n triggers one card charge. const verification = await payments.facilitator.verifyPermissions({ paymentRequired, x402AccessToken: token, maxAmount: 1n, }) if (!verification.isValid) return send402(res) const apiKey = await provisionOrTopUp(req) const settlement = await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken: token, maxAmount: 1n, }) // Settlement receipt in the `payment-response` header (per x402 spec §4.3). res .setHeader( 'payment-response', Buffer.from(JSON.stringify(settlement)).toString('base64'), ) .json({ apiKey }) }) ``` ```python theme={null} import base64 import os from fastapi import FastAPI, Request from fastapi.responses import JSONResponse 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=os.environ['NVM_API_KEY'], environment='live') ) payment_required = build_payment_required( plan_id=os.environ['PLAN_ID'], endpoint='/purchase-key', http_verb='POST', scheme='nvm:card-delegation', ) # Pre-encode for the `payment-required` response header (per x402 spec §4.3). payment_required_header = base64.b64encode( payment_required.model_dump_json(by_alias=True).encode() ).decode() def send_402() -> JSONResponse: return JSONResponse( status_code=402, content=payment_required.model_dump(by_alias=True), headers={'payment-required': payment_required_header}, ) @app.post('/purchase-key') async def purchase_key(request: Request): token = request.headers.get('payment-signature') if not token: return send_402() # max_amount = credits to burn (not cents). PAYG plan with 1 credit # per purchase means burning "1" triggers one card charge. verification = payments.facilitator.verify_permissions( payment_required=payment_required, x402_access_token=token, max_amount='1', ) if not verification.is_valid: return send_402() api_key = await provision_or_top_up(request) settlement = payments.facilitator.settle_permissions( payment_required=payment_required, x402_access_token=token, max_amount='1', ) # Settlement receipt in the `payment-response` header (per x402 spec §4.3). settlement_header = base64.b64encode( settlement.model_dump_json(by_alias=True).encode() ).decode() return JSONResponse( content={'apiKey': api_key}, headers={'payment-response': settlement_header}, ) ``` The facilitator API is plain HTTP, no SDK required. Pseudo-code below shows the verify/settle calls your handler needs to make. **Endpoints** (no `Authorization` header, auth is in the x402 access token): ``` POST https://api.live.nevermined.app/api/v1/x402/verify POST https://api.live.nevermined.app/api/v1/x402/settle ``` Use `https://api.sandbox.nevermined.app/api/v1/...` for sandbox. **Verify request body:** ```json theme={null} { "paymentRequired": { "x402Version": 2, "resource": { "url": "/purchase-key", "mimeType": "application/json" }, "accepts": [{ "scheme": "nvm:card-delegation", "network": "stripe", "planId": "", "extra": { "version": "1", "httpVerb": "POST" } }], "extensions": {} }, "x402AccessToken": "", "maxAmount": "1" } ``` Response: `{ "isValid": true, ... }` or `{ "isValid": false, "invalidReason": "..." }`. **Settle** uses the same body shape posted to `/x402/settle`. Response contains `creditsRedeemed`, `remainingBalance`, `transaction`, and (when auto top-up fired) `orderTx`. **Your response headers** (per x402 spec §4.3): * On 402: set `payment-required: ` so buyer-side clients can discover requirements * On 200: set `payment-response: ` so buyers have a structured settlement receipt **Pseudo-handler** (curl-style): ```bash theme={null} # 0. Pre-build the PaymentRequired JSON for your endpoint (see body shape above) PAYMENT_REQUIRED='{"x402Version":2,"resource":{"url":"/purchase-key","mimeType":"application/json"},"accepts":[{"scheme":"nvm:card-delegation","network":"stripe","planId":"","extra":{"version":"1","httpVerb":"POST"}}],"extensions":{}}' # 1. On request: read the payment-signature header TOKEN="$(http_header payment-signature)" # 2. Verify curl -s -X POST https://api.live.nevermined.app/api/v1/x402/verify \ -H 'Content-Type: application/json' \ -d "$(jq -n --arg pr "$PAYMENT_REQUIRED" --arg t "$TOKEN" '{ paymentRequired: ($pr | fromjson), x402AccessToken: $t, maxAmount: "1" }')" # → on isValid=false, return 402 with payment-required header set # 3. Run business logic (provision/top-up the API key) # 4. Settle curl -s -X POST https://api.live.nevermined.app/api/v1/x402/settle \ -H 'Content-Type: application/json' \ -d "$(jq -n --arg pr "$PAYMENT_REQUIRED" --arg t "$TOKEN" '{ paymentRequired: ($pr | fromjson), x402AccessToken: $t, maxAmount: "1" }')" # 5. Return 200 with the credential ``` See the [Verify](/docs/api-reference/x402/verify-permission) and [Settle](/docs/api-reference/x402/settle-permission) API references for full request/response schemas, and the [card-delegation spec](/docs/specs/x402-card-delegation) §3.2 for the PaymentRequired JSON shape. The handlers above set the `payment-required` (on 402) and `payment-response` (on 200) response headers per the [card-delegation spec](/docs/specs/x402-card-delegation) §4.3. The optional middleware below handles this for you automatically. ### Optional: middleware (recommended) If you're on Express or FastAPI, the SDK ships a payment middleware that handles the verify/settle dance and the spec-correct response headers for you. One config line gates your endpoint, your handler stays focused on business logic. ```typescript 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()) const payments = Payments.getInstance({ nvmApiKey: process.env.NVM_API_KEY!, environment: 'live', }) // credits: 1 maps to a PAYG plan with 1 credit per purchase, so one // call to /purchase-key triggers one card charge for the plan price. app.use( paymentMiddleware(payments, { 'POST /purchase-key': { planId: process.env.PLAN_ID!, credits: 1, scheme: 'nvm:card-delegation', }, }), ) app.post('/purchase-key', async (req, res) => { const apiKey = await provisionOrTopUp(req) res.json({ apiKey }) }) ``` ```python theme={null} import os from fastapi import FastAPI, Request 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='live') ) # credits=1 maps to a PAYG plan with 1 credit per purchase, so one # call to /purchase-key triggers one card charge for the plan price. app.add_middleware( PaymentMiddleware, payments=payments, routes={ 'POST /purchase-key': { 'plan_id': os.environ['PLAN_ID'], 'credits': 1, 'scheme': 'nvm:card-delegation', }, }, ) @app.post('/purchase-key') async def purchase_key(request: Request): api_key = await provision_or_top_up(request) return {'apiKey': api_key} ``` The middleware reads the `payment-signature` header, calls `verifyPermissions`, runs your handler, then calls `settlePermissions` and attaches the `payment-response` header before sending the response. See the [Express](/docs/integrate/add-to-your-agent/express) and [FastAPI](/docs/integrate/add-to-your-agent/fastapi) integration pages for middleware options (dynamic credits, hooks, custom error handlers). ### Two things to remember **Idempotency.** Agents retry. Cache `token → result` so a replayed token returns the same response without re-running your business logic or re-charging the card. **402 on your regular endpoints.** When the agent's credits run dry, return `402` with a top-up hint on the resource endpoints (not just the purchase endpoint). The agent loops back, mints a fresh token, and pays again on the same purchase route. ## Vend-the-key vs full metering Two patterns are supported. Pick whichever matches how you already meter usage. A Nevermined purchase returns a credential (e.g. an API key with \$X of credits). You meter ongoing usage internally with your existing infrastructure. Exa works this way. Best when you already have per-key billing. Nevermined settles each underlying API call. Best when you don't have internal billing infra or prefer per-request settlement. Use the [validate-requests](/docs/integrate/patterns/validate-requests) and [charge-credits](/docs/integrate/patterns/charge-credits) patterns directly on your resource endpoints. ## Make it agent-discoverable Agents don't read your HTML. They read your `llms.txt` and the `.md` mirrors next to your docs pages. Two small additions get you listed. ### 1. Add a line to your `llms.txt` Drop an entry into your root `llms.txt` (under an `## Integrations` heading or similar): ``` - [Nevermined](/integrations/nevermined.md): Accept autonomous agent payments via x402 card delegation. Plan ID, endpoint, and SDK usage in the link. ``` ### 2. Publish an integration `.md` mirror Put a markdown file at `/integrations/nevermined.md` (or wherever your integration docs live). Cover the plan ID, the purchase endpoint, the response shapes, and the agent-side snippet. A starter skeleton with placeholders is hosted here: * [api-provider-template.md](https://nevermined.ai/integrations/api-provider-template.md) - copy and fill in * Live example: [Exa's `/docs/integrations/nevermined.md`](https://exa.ai/docs/integrations/nevermined.md) Updating your human-facing HTML docs is optional and less agent-native. ## Agent-side call (for context) What the agent runs to mint a token and call your endpoint: ```typescript theme={null} const { accessToken } = await payments.x402.getX402AccessToken( YOUR_PLAN_ID, undefined, { scheme: 'nvm:card-delegation', delegationConfig: { providerPaymentMethodId: 'pm_...', spendingLimitCents: 700, durationSecs: 3600, }, }, ) const res = await fetch('https://your-api.com/purchase-key', { method: 'POST', headers: { 'payment-signature': accessToken }, }) ``` ```python theme={null} token_res = payments.x402.get_x402_access_token( YOUR_PLAN_ID, None, { 'scheme': 'nvm:card-delegation', 'delegationConfig': { 'providerPaymentMethodId': 'pm_...', 'spendingLimitCents': 700, 'durationSecs': 3600, }, }, ) access_token = token_res['accessToken'] res = requests.post( 'https://your-api.com/purchase-key', headers={'payment-signature': access_token}, ) ``` ## Why Nevermined vs DIY * Stripe Checkout needs a browser and a human, useless mid-task * Roll your own x402 means months building card enrollment, delegation lifecycle, KYC, dispute handling, and a buyer-side SDK * You own PCI scope on the buyer side * Drop in `verifyPermissions` and `settlePermissions` * Card acquisition via Stripe or Visa Intelligent Commerce, payouts via Stripe Connect today (PayPal Braintree coming soon) * PCI scope stays with Nevermined and the PSP * Standard x402 protocol, no proprietary buyer SDK ## FAQ Settled funds are paid out via your connected Stripe Connect account on Stripe's standard schedule. PayPal Braintree as a second payout rail is coming soon. No. Your existing API and billing surface stay as-is. Agent-payable signup is a parallel path you opt into with a new endpoint. Yes. Per-call, top-ups, and credit bundles map cleanly to Nevermined plans. For subscriptions, talk to us, recurring billing is supported case-by-case. [Stripe](/docs/products/nvm-pay/overview) (for direct card enrollment) and Visa Intelligent Commerce (VIC). The x402 token your endpoint verifies is the same regardless of rail; Nevermined handles the rail-specific details. See the [card-delegation spec](/docs/specs/x402-card-delegation) for the underlying flow. Yes, on the Stripe rail. Integrate, run end-to-end test transactions, and validate your handler before flipping to live. VIC doesn't have a sandbox today, so VIC delegations are tested with small amounts in live. TypeScript and Python SDKs ship today, with framework helpers for [Express.js](/docs/integrate/add-to-your-agent/express) and [FastAPI](/docs/integrate/add-to-your-agent/fastapi). The facilitator API is plain HTTP, so any language can call verify/settle directly. See the [Generic HTTP guide](/docs/integrate/add-to-your-agent/generic-http). Today, yes: you connect a Stripe Connect account as your payout destination. PayPal Braintree as a second payout option is coming soon (see [Braintree onboarding](/docs/products/nvm-pay/braintree-onboarding) for the planned flow). Nevermined handles card acquisition and PCI scope on the buyer side; you just bring your payout destination. On Stripe destination charges, Nevermined is the merchant of record: Stripe routes disputes and refunds through Nevermined, and we surface the chargeback or charge reference so you can match it to specific agent transactions. Stripe Connect handles currency conversion to your destination account, so non-USD payouts work today. VIC currently supports predominantly US-based issuers. ## Related * [x402 Card Delegation Spec](/docs/specs/x402-card-delegation) - full verify/settle protocol with JWT claims, error codes, and the four-phase flow * [x402 Facilitator: How it works](/docs/products/x402-facilitator/how-it-works) * end-to-end walk-through of the facilitator API * [Validate Requests](/docs/integrate/patterns/validate-requests) and [Charge Credits](/docs/integrate/patterns/charge-credits) - copy-paste patterns for the verify and settle steps * [Manage Plans](/docs/products/nevermined-app/manage-plans) - set up a pay-as-you-go plan * [Nevermined Pay overview](/docs/products/nvm-pay/overview) - how buyers enroll cards and create delegations # 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 Extends x402 with credit/debit card delegation via Stripe for fiat-based payment 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 Delegation Extension Source: https://docs.nevermined.app/docs/specs/x402-card-delegation Specification for extending x402 with delegated payment permissions for crypto (ERC-4337) and fiat (Stripe, Braintree) settlement ## Abstract This specification defines a **delegation extension** to the [x402 protocol](https://x402.org/) that enables payment settlement through on-chain credit burns with delegated spending permissions. The delegation model is shared between crypto and fiat payment providers: * **Crypto delegations** (`provider: 'erc4337'`): Use ERC-4337 smart accounts and session keys for on-chain settlement, with crypto-funded ordering when the subscriber's balance is insufficient. * **Fiat delegations** (`provider: 'stripe' | 'braintree'`): Use the configured Payment Service Provider (Stripe PaymentIntents or Braintree `transaction.sale`) backed by pre-authorized card delegations for automatic card-funded credit top-ups when the subscriber's balance is insufficient. All providers use the same `DelegationConfig` interface with `spendingLimitCents` and `durationSecs` to control delegation scope. The PSP is selected per plan via the seller's `fiatPaymentProvider` metadata. The fiat scheme identifier is: `nvm:card-delegation` The extension is designed to be **fully compatible** with existing x402 clients and servers, requiring only the addition of the delegation extension payload. ``` Version: 0.1 Status: Draft Last Updated: February 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, and the smart accounts extension uses ERC-4337 UserOperations for programmable on-chain settlement. Both approaches require clients to hold cryptocurrency and interact with blockchain infrastructure. ### 1.2 Motivation Many real-world payment scenarios involve users and organizations that prefer or require traditional payment methods: * **Enterprise adoption** --- organizations with existing credit card infrastructure should not need crypto wallets to consume AI services * **Regulatory compliance** --- some jurisdictions require fiat-denominated billing and traditional payment rails * **User experience** --- end users are familiar with credit card payments and may not want to manage blockchain wallets * **Budgetary controls** --- organizations need spending limits, approval workflows, and reconciliation against traditional accounting systems * **Instant onboarding** --- new users can start consuming services immediately with an existing payment card, without acquiring tokens This extension enables these use cases by funding on-chain credit purchases through a Payment Service Provider (PSP) — currently Stripe or Braintree — authorized through pre-established delegations managed by the facilitator. Settlement itself uses the same on-chain credit burn mechanism as the smart accounts extension. The delegation model (`DelegationConfig`) is shared across all providers, giving the SDK and middleware a single interface for creating and managing payment permissions regardless of which PSP settles the charge. ### 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 * **MUST** support spending limits and transaction caps per delegation * **SHOULD** leverage the PSP's off-session payment capabilities for seamless settlement * **SHOULD** support multi-party settlement so funds reach merchant-owned accounts (Stripe Connect, Braintree OAuth Connect) * **MUST** ensure PCI compliance by never exposing raw card data to the facilitator or server * **MUST** allow facilitators to verify and settle payments 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. Authenticates via JWT tokens issued by the facilitator. | | **Server** | The entity providing the protected resource (API, agent, service). Coordinates with the facilitator. | | **Facilitator** | A third-party service that verifies delegations, manages spending limits, and executes PSP charges (Stripe PaymentIntents, Braintree `transaction.sale`) on behalf of clients. | | **Payment Provider** | The payment processor (Stripe or Braintree) that handles card authorization, capture, and fund transfer. The active provider for a given plan is selected via the plan's `fiatPaymentProvider` metadata. | ### 2.2 Definitions | Term | Definition | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Delegation** | A pre-authorized permission granting the facilitator the ability to execute payment operations on behalf of a client, within defined spending limits and time constraints. Delegations use the unified `DelegationConfig` interface for crypto (`erc4337`) and fiat (`stripe`, `braintree`) providers. | | **SetupIntent** | A Stripe object used to collect and confirm a customer's payment method for future off-session charges, without immediately charging the card. | | **PaymentIntent** | A Stripe object representing the intent to collect a payment. Used during settlement to charge the delegated card. | | **Payment Method Nonce** | A single-use Braintree token returned by the Braintree Drop-in / hosted fields once the buyer enters card details. The facilitator exchanges the nonce for a permanent `paymentMethodToken` by vaulting the card. | | **Payment Method Token** | A permanent Braintree handle that identifies a vaulted card. Used at settlement time as the equivalent of Stripe's `pm_...` ID. | | **Off-session payment** | A PSP-side payment executed without the cardholder being actively present, using a previously saved payment method. | | **Connected Account** | A merchant-owned PSP account (Stripe Connected Account or Braintree OAuth-connected merchant) that receives funds from settlements. | | **Spending Limit** | The maximum amount (in cents) that can be charged against a delegation over its lifetime. | | **VGS (Very Good Security)** | A PCI-compliant proxy service used to tokenize and vault sensitive card data before it reaches the facilitator. Used for the Stripe enrollment path; the Braintree path performs equivalent in-browser tokenization via the Braintree Drop-in / hosted fields. | ## 3. Extension Structure ### 3.1 Scheme Identifier The scheme identifier is: `nvm:card-delegation` ### 3.2 Payload Schema #### PaymentRequired Response (402) When a server requires payment via card delegation, 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:card-delegation", "network": "stripe", // or "braintree" "planId": "plan_abc123", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015", "httpVerb": "POST" } }], "extensions": {} } ``` #### PaymentPayload (Client Response) The client responds with a PaymentPayload containing a JWT token that encodes the delegation authorization: ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepted": { "scheme": "nvm:card-delegation", "network": "stripe", // or "braintree" "planId": "plan_abc123", "extra": { "version": "1", "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015", "httpVerb": "POST" } }, "payload": { "token": "eyJhbGciOiJSUzI1NiIs...", "authorization": { "from": "0xD4f58B60330bC59cB0A07eE6A1A66ad64244eC8c", "sessionKeys": [{ "id": "redeem", "data": "0xabc123..." }] } }, "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:card-delegation"`. | | `network` | `string` | Yes | Payment network identifier. MUST be `"stripe"` or `"braintree"`, matching the plan's `fiatPaymentProvider` metadata. The SDK auto-resolves this from plan metadata; clients may also supply it explicitly. | | `planId` | `string` | No | Plan identifier associated with this delegation. | | `extra` | `object` | Yes | Additional scheme-specific fields. | #### Extra Fields | Field | Type | Required | Description | | ---------- | -------- | -------- | --------------------------------------------------- | | `version` | `string` | Yes | Scheme version (e.g., `"1"`). | | `agentId` | `string` | No | Agent identifier (if resource 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 | Field | Type | Required | Description | | ---------------------------------- | -------- | ------------------------------ | -------------------------------------------------------------------------------------- | | `token` | `string` | Yes | JWT token encoding the card delegation authorization. See JWT Claims below. | | `authorization` | `object` | No | On-chain authorization for credit burns. Contains subscriber address and session keys. | | `authorization.from` | `string` | Yes (if authorization present) | Subscriber's blockchain address (smart account). | | `authorization.sessionKeys` | `array` | Yes (if authorization present) | Array of session key references for on-chain operations. | | `authorization.sessionKeys[].id` | `string` | Yes | Session key identifier. Must be `"redeem"` for burn session keys. | | `authorization.sessionKeys[].data` | `string` | Yes | Keccak-256 hash of the serialized session key. | #### JWT Claims The `token` field contains a signed JWT with the following claims: | Claim | Type | Required | Description | | ----------------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `iss` | `string` | Yes | Token issuer. MUST be the facilitator URL (e.g., `"https://api.nevermined.app"`). | | `sub` | `string` | Yes | Subject identifier. The userId of the client who created the delegation. | | `aud` | `string` | Yes | Audience. MUST be `"nvm:card-delegation"`. | | `jti` | `string` | Yes | JWT ID. The delegationId (UUID) that uniquely identifies this delegation. | | `iat` | `number` | Yes | Issued-at timestamp (Unix epoch seconds). | | `exp` | `number` | Yes | Expiration timestamp (Unix epoch seconds). | | `nvm.delegationId` | `string` | Yes | Unique delegation identifier (UUID). MUST match the `jti` claim. | | `nvm.provider` | `string` | Yes | Payment provider identifier. MUST be `"stripe"`, `"braintree"`, or `"erc4337"`. | | `nvm.providerCustomerId` | `string` | Yes | PSP-side customer identifier. For Stripe, the Customer ID (e.g., `"cus_PaBcDeFgHiJk"`). For Braintree, the customer ID derived from the buyer's userId. | | `nvm.providerPaymentMethodId` | `string` | Yes | PSP-side payment method handle for the enrolled card. For Stripe, the PaymentMethod ID (e.g., `"pm_1AbCdEfGhIjKlM"`). For Braintree, the `paymentMethodToken` returned when the card was vaulted. | | `nvm.spendingLimitCents` | `number` | Yes | Maximum amount in cents that can be charged over the delegation lifetime. | | `nvm.currency` | `string` | Yes | ISO 4217 currency code (e.g., `"usd"`, `"eur"`). | | `nvm.merchantAccountId` | `string` | No | Merchant-side account identifier for routing settlement. For Stripe, the Connected Account ID (e.g., `"acct_1AbCdEfGhIjKlM"`). For Braintree, the seller's per-currency child `merchantAccountId` matching `nvm.currency`. | | `nvm.planId` | `string` | No | Plan identifier associated with this delegation. | | `nvm.maxTransactions` | `number` | No | Maximum number of individual transactions allowed under this delegation. | The `nvm` claims are namespaced under a single `nvm` object within the JWT payload to avoid collisions with standard JWT claims. The `jti` and `nvm.delegationId` MUST contain the same value for consistency. ## 4. Protocol Flow ### 4.1 Overview The protocol flow extends standard x402 with a card enrollment phase and replaces on-chain settlement with an external Payment Service Provider (PSP). Stripe and Braintree are both supported; the active PSP is selected per plan via the seller's `fiatPaymentProvider` metadata, and the design allows for additional providers to be added in the future: ```mermaid theme={null} --- title: Nevermined + x402 - Card Delegation Flow --- sequenceDiagram autonumber %% ACTORS actor client as Client participant facilitator as Facilitator (Nevermined API) participant vgs as VGS Proxy participant psp as PSP (Stripe, Braintree, ..) participant server as Server (AI Agent) %% PHASE 0: CARD ENROLLMENT rect rgb(240, 248, 255) note over client,psp: Phase 0: Card Enrollment client->>facilitator: POST /payments/card/setup facilitator->>psp: Create SetupIntent psp-->>facilitator: SetupIntent (client_secret) facilitator-->>client: SetupIntent client_secret note over vgs: PI captured by VGS Collect directly (PCI Compliant) client->>vgs: Submit card details (using VGS Collect) vgs->>psp: Confirm SetupIntent with tokenized card psp-->>vgs: SetupIntent confirmed (PaymentMethod) vgs-->>client: Confirmation client->>facilitator: POST /payments/card/enroll (SetupIntent ID) facilitator->>psp: Retrieve PaymentMethod psp-->>facilitator: PaymentMethod details facilitator->>facilitator: Store customer + payment method mapping facilitator-->>client: Enrollment confirmed end %% PHASE 1: DELEGATION CREATION rect rgb(255, 248, 240) note over client,facilitator: Phase 1: Delegation Creation client->>facilitator: POST /x402/permissions
scheme: nvm:card-delegation
spendingLimitCents, currency, expiry facilitator->>facilitator: Validate customer + payment method
Create delegation record
Sign JWT token facilitator-->>client: Base64-encoded PaymentPayload (x402 access token) end %% PHASE 2: VERIFICATION rect rgb(240, 255, 240) note over client,server: Phase 2: Verification client->>server: GET /api server->>client: HTTP 402 + PAYMENT-REQUIRED header client->>server: Request + PAYMENT-SIGNATURE header (base64-encoded x402 token) activate server server->>facilitator: /verify (paymentRequired + paymentPayload) facilitator->>facilitator: Verify JWT signature facilitator->>facilitator: Check delegation status (Active) facilitator->>facilitator: Check expiry (exp > now) facilitator->>facilitator: Check spending limit (spent + amount <= limit) facilitator->>facilitator: Check transaction count (if maxTransactions set) alt verification failed facilitator-->>server: ERROR: Verification failed server-->>client: HTTP 402 + PAYMENT-REQUIRED header else verification passed facilitator-->>server: OK: Verification passed end end %% PHASE 3: SETTLEMENT rect rgb(255, 240, 255) note over server,psp: Phase 3: Settlement (Credit-Based) server->>server: Execute work (AI task, etc.) server->>facilitator: /settle (paymentRequired + paymentPayload + maxAmount) activate facilitator facilitator->>facilitator: Resolve subscriber address facilitator->>facilitator: Check on-chain credit balance alt Insufficient credits facilitator->>psp: Create PaymentIntent (plan price) alt PSP failure psp-->>facilitator: Payment failed facilitator-->>server: Settlement failed server-->>client: HTTP 402 + PAYMENT-REQUIRED header else PSP success psp-->>facilitator: PaymentIntent succeeded facilitator->>facilitator: Mint credits on-chain end end facilitator->>facilitator: Burn credits on-chain (session key) facilitator-->>server: Settlement OK (txHash, creditsRedeemed) deactivate facilitator server-->>client: Response + PAYMENT-RESPONSE header deactivate server end ``` ### 4.2 Step-by-Step Flow The following steps detail the complete payment and execution flow. Steps are grouped into four phases: **Card Enrollment**, **Delegation Creation**, **Verification**, and **Settlement**. *** #### Phase 0: Card Enrollment (Steps 1-10) Card enrollment is a one-time process that securely saves a client's payment method for future use. **Step 1.** Client requests a SetupIntent from the facilitator. ```http theme={null} POST /payments/card/setup HTTP/1.1 Host: facilitator.example.com Authorization: Bearer ``` **Step 2.** Facilitator creates a SetupIntent and returns the `client_secret` to the client. **Step 3.** Client submits card details through a VGS (Very Good Security) proxy. The VGS proxy tokenizes the card data before it reaches any Nevermined infrastructure, ensuring PCI compliance. **Step 4.** VGS forwards the tokenized card data to the PSP to confirm the SetupIntent. **Step 5.** PSP confirms the SetupIntent and attaches the PaymentMethod. **Step 6.** Client notifies the facilitator that enrollment is complete. ```http theme={null} POST /payments/card/enroll HTTP/1.1 Host: facilitator.example.com Authorization: Bearer Content-Type: application/json { "setupIntentId": "seti_1AbCdEfGhIjKlM" } ``` **Step 7.** Facilitator retrieves the confirmed PaymentMethod from the PSP and stores the customer-to-payment-method mapping. **Step 8.** Facilitator confirms enrollment to the client. Card enrollment only needs to happen once per payment method. A single enrolled card can back multiple delegations with different spending limits and expiry times. *** #### Phase 1: Delegation Creation (Steps 9-12) **Step 9.** Client creates a delegation using the `createDelegation` API, specifying the `provider` (required: `'stripe'`, `'braintree'`, or `'erc4337'`), spending limits, and optional constraints. The `DelegationConfig` interface is shared across all providers. Alternatively, the client can use `getX402AccessToken` with an inline `delegationConfig` to auto-create a delegation during token generation. **Using the `createDelegation` API directly:** ```http theme={null} POST /api/v1/payments/delegation HTTP/1.1 Host: facilitator.example.com Authorization: Bearer Content-Type: application/json { "provider": "stripe", "spendingLimitCents": 10000, "durationSecs": 2592000, "providerPaymentMethodId": "pm_1AbCdEfGhIjKlM", "currency": "usd", "maxTransactions": 100, "merchantAccountId": "acct_1AbCdEfGhIjKlM" } ``` For Braintree-priced plans, the same payload uses `provider: "braintree"`, the `paymentMethodToken` returned at enrollment as `providerPaymentMethodId`, and the seller's per-currency Braintree `merchantAccountId`: ```json theme={null} { "provider": "braintree", "spendingLimitCents": 10000, "durationSecs": 2592000, "providerPaymentMethodId": "abc123token", "currency": "usd", "maxTransactions": 100, "merchantAccountId": "seller-usd-merchant-account-id" } ``` **Using `getX402AccessToken` with inline delegation (combines Steps 9-12):** ```http theme={null} POST /x402/permissions HTTP/1.1 Host: facilitator.example.com Authorization: Bearer Content-Type: application/json { "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepted": { "scheme": "nvm:card-delegation", "network": "stripe", // or "braintree" "planId": "plan_abc123", "extra": { "version": "1" } }, "delegationConfig": { "delegationId": "deleg-8f14e45f-ce34-4797-b88e-968374b0d4b6" } } ``` **Step 10.** Facilitator validates the client's enrolled payment method and creates a delegation record with status `Active`. **Step 11.** Facilitator signs a JWT token containing the delegation claims (see Section 3.3) and wraps it in a standard x402 PaymentPayload structure. The `resource` and `accepted` fields echo back the values from the client's request: ```json theme={null} { "x402Version": 2, "resource": { "url": "/api/v1/agents/80918427023170428029540261117198154464497879145267720259488529685089104529015/tasks", "description": "AI agent task execution", "mimeType": "application/json" }, "accepted": { "scheme": "nvm:card-delegation", "network": "stripe", // or "braintree" "planId": "plan_abc123", "extra": { "version": "1" } }, "payload": { "token": "" }, "extensions": {} } ``` **Step 12.** Facilitator base64-encodes the PaymentPayload and returns it to the client. This token is what the client will include in the `PAYMENT-SIGNATURE` header when accessing protected resources. ```json theme={null} { "accessToken": "eyJ4NDAyVmVyc2lvbiI6Mi...", "permissionHash": "0x1a2b3c..." } ``` *** #### Phase 2: Verification (Steps 13-22) **Step 13.** Client initiates an HTTP request to a Server (AI Agent). ```http theme={null} GET /api/resource HTTP/1.1 Host: agent.example.com ``` **Step 14.** Server validates whether the request contains a valid payment in the `PAYMENT-SIGNATURE` header. **Step 15.** 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 ``` **Step 16.** The Client selects the `nvm:card-delegation` scheme from `accepts[]` and uses the base64-encoded PaymentPayload (received in Step 12) as the x402 access token. **Step 17.** Client sends an HTTP request to the server including the base64-encoded PaymentPayload in the `PAYMENT-SIGNATURE` HTTP header. ```http theme={null} GET /api/resource HTTP/1.1 Host: agent.example.com PAYMENT-SIGNATURE: ``` **Step 18.** Server validates the incoming data and forwards the payment data to the facilitator for verification. ```http theme={null} POST /verify HTTP/1.1 Host: facilitator.example.com Content-Type: application/json { "paymentRequired": { ... }, "x402AccessToken": "", "maxAmount": "500" } ``` **Step 19.** Facilitator base64-decodes the PaymentPayload, extracts the JWT from the `payload.token` field, and performs the following verification checks: 1. **JWT signature verification** --- validates the JWT was signed by the facilitator's private key 2. **Delegation status check** --- confirms the delegation record exists and has status `Active` 3. **Expiry check** --- confirms `exp` claim is in the future 4. **Credit balance check** --- if `planId` is present, checks the subscriber's on-chain credit balance 5. **Session key validation** --- if `authorization` is present, validates the burn session key exists and is active **Step 20.** IF any verification check fails, the facilitator returns an error to the server. **Step 21.** IF all verification checks pass, the facilitator confirms verification to the server. **Step 22.** 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 delegation CAN be settled before the server performs any work. This protects the server from executing expensive operations without guaranteed payment. *** #### Phase 3: Settlement (Steps 23-32) **Step 23.** The Server executes the task to fulfill the client request (AI Task or any necessary work). **Step 24.** 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": { ... }, "x402AccessToken": "", "maxAmount": "500" } ``` The `maxAmount` specifies the number of **credits** to burn for this request (not cents). **Step 25.** Facilitator resolves the subscriber's blockchain address and loads the plan from the on-chain registry. **Step 26.** Facilitator checks the subscriber's on-chain credit balance for the plan. **Step 27.** IF balance is insufficient for the requested `maxAmount`: * Calculate plan price in cents from `plan.price.amounts` * Atomically increment the delegation's spend counter (card charge ceiling) * Create an off-session PSP PaymentIntent to charge the card for the plan price * On PSP failure: roll back spend counter, return settlement failure * On PSP success: mint credits to the subscriber's blockchain address * Wait for the credit balance to update on-chain **Step 28.** Facilitator burns the requested credits on-chain using the burn session key from the token's `authorization.sessionKeys`. **Step 29.** IF the credit burn fails, the facilitator returns a settlement failure. **Step 30.** Facilitator returns the settlement receipt to the server. **Step 31.** Server returns to the client the response with the payment confirmation in the `PAYMENT-RESPONSE` header (base64-encoded). ```http theme={null} HTTP/1.1 200 OK Content-Type: application/json PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJwYXltZW50SW50ZW50SWQiOi4uLg== { "result": "...", "payment": { "creditsRedeemed": 2, "remainingBalance": 98, "orderTx": "pi_1AbCdEfGhIjKlM" } } ``` **Decoded PAYMENT-RESPONSE:** ```json theme={null} { "success": true, "transaction": "0x1234567890abcdef...", "network": "stripe", "creditsRedeemed": "2", "remainingBalance": "98", "orderTx": "pi_1AbCdEfGhIjKlM" } ``` The `PAYMENT-RESPONSE` header contains x402-standard settlement fields. The `network` echoes the value used at verification (`stripe` or `braintree`). For Braintree, `orderTx` is the Braintree merchant transaction ID. Additional Nevermined-specific info (like `creditsRedeemed` and `remainingBalance`) can be included in the response body. Steps 27-28 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), payment data is transmitted via HTTP headers: | Header | Direction | Content | | ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ | | `PAYMENT-REQUIRED` | Server -> Client (402) | Base64-encoded `PaymentRequired` object | | `PAYMENT-SIGNATURE` | Client -> Server | Base64-encoded `PaymentPayload` object (consistent for both `nvm:card-delegation` and `nvm:erc4337` schemes) | | `PAYMENT-RESPONSE` | Server -> Client (200) | Base64-encoded settlement receipt | ## 5. Verification Requirements ### 5.1 JWT Verification The facilitator MUST verify the following aspects of the JWT token: | Check | Requirement | | --------- | ---------------------------------------------------------------------------------------------------- | | Signature | JWT MUST be signed by the facilitator's private key and verifiable with the corresponding public key | | Algorithm | JWT MUST use RS256 or ES256 signing algorithm | | Issuer | `iss` claim MUST match the facilitator's configured issuer URL | | Audience | `aud` claim MUST equal `"nvm:card-delegation"` | | Expiry | `exp` claim MUST be in the future | | Issued At | `iat` claim MUST be in the past | | JWT ID | `jti` claim MUST match an existing delegation record | ### 5.2 Delegation Status Verification The facilitator MUST verify: | Check | Requirement | | -------------- | ----------------------------------------------------------------------- | | Existence | A delegation record matching the `jti` / `nvm.delegationId` MUST exist | | Status | Delegation status MUST be `Active` | | Customer Match | `nvm.providerCustomerId` MUST match the delegation record | | Payment Method | `nvm.providerPaymentMethodId` MUST match a valid, active payment method | ### 5.3 Balance and Permission Checks The facilitator MUST verify: | Check | Requirement | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Credit Balance | If a `planId` is present, the subscriber's on-chain credit balance SHOULD be checked (informational — settle auto-orders if needed) | | Session Key | If `authorization` is present, the burn session key hash MUST match an active `PermissionEntity` | | Delegation Active | The delegation MUST have status `Active` | Unlike the previous cents-based budget check, verification always returns valid for active delegations. The settle step handles insufficient credits by auto-ordering via card charge. The delegation's spending limit serves as a ceiling on total card charges, not a per-request budget. ## 6. Settlement Requirements ### 6.1 Execution Order When executing settlement, the facilitator MUST: 1. Decode the x402 token and verify the delegation JWT 2. Resolve the subscriber's blockchain address from their userId 3. Load the plan from the on-chain asset registry 4. Check the subscriber's on-chain credit balance 5. If balance is insufficient for `maxAmount`: a. Calculate plan price: `sum(plan.price.amounts)` (in cents) b. Atomically increment delegation spend counter c. Create off-session PSP PaymentIntent for the plan price d. On PSP failure: roll back spend counter, return error e. Mint credits to subscriber using the node account f. Wait for balance update on-chain 6. Burn credits on-chain using the burn session key 7. Return settlement receipt with `creditsRedeemed` and `remainingBalance` ### 6.2 Atomicity The spend counter MUST be incremented atomically BEFORE the PSP PaymentIntent is created. If the PSP charge fails, the spend counter MUST be rolled back. This prevents race conditions where concurrent requests could exceed spending limits. ### 6.3 Delegation Lifecycle Delegations transition through the following states: | Status | Description | | ----------- | --------------------------------------------------------------- | | `Active` | Delegation is valid and can be used for payments | | `Exhausted` | Spending limit or transaction count has been reached | | `Expired` | The `exp` timestamp has passed | | `Revoked` | The client or facilitator has explicitly revoked the delegation | The facilitator MUST update the delegation status to `Exhausted` when: * `spentCents >= nvm.spendingLimitCents` * Transaction count reaches `nvm.maxTransactions` (if set) ### 6.4 PSP Connect Routing When `nvm.merchantAccountId` is present, the facilitator MUST route funds to the merchant's connected account on the PSP that backs the plan: * **Stripe**: route via Stripe Connect using the `transfer_data.destination` parameter on the PaymentIntent. The facilitator MAY apply platform fees via `application_fee_amount`. * **Braintree**: settlement is split across two `transaction.sale` calls. The facilitator first charges the platform fee on the platform's gateway against the platform's per-currency `merchantAccountId`, then charges the merchant share on the merchant's OAuth-connected gateway against `nvm.merchantAccountId`. If the merchant leg fails, the platform leg MUST be voided to avoid collecting a fee for a payment that never reached the merchant. In both cases the facilitator MUST select a `merchantAccountId` whose currency matches `nvm.currency`. For Braintree this is enforced both at plan creation (sellers must have a child merchant account in the chosen currency) and at settlement time; a mismatch fails the charge with `CURRENCY_MISMATCH`. ### 6.5 Receipt Format The facilitator MUST return a receipt. The `PAYMENT-RESPONSE` header contains x402-standard fields: | Field | Type | Description | | --------- | --------- | --------------------------------------------------------------------------------------------------------- | | `success` | `boolean` | Whether settlement succeeded. | | `network` | `string` | Payment network. MUST be `"stripe"` or `"braintree"` (matching the `accepted.network` from verification). | Additional settlement details MAY be included in the response body: | Field | Type | Description | | ------------------ | -------- | ---------------------------------------------------------------------------------- | | `creditsRedeemed` | `string` | Number of credits burned on-chain. | | `remainingBalance` | `string` | Subscriber's remaining credit balance. | | `orderTx` | `string` | PSP PaymentIntent ID if auto top-up occurred (only present when card was charged). | | `transaction` | `string` | Blockchain transaction hash of the credit burn. | ## 7. Error Handling ### 7.1 Error Codes | Code | Name | Description | | --------------------------- | ------------------------- | ------------------------------------------------------------------------------- | | `INVALID_PAYLOAD` | Invalid Payload | The payment payload structure is invalid or missing required fields | | `INVALID_TOKEN` | Invalid Token | The JWT token signature verification failed | | `EXPIRED_TOKEN` | Expired Token | The JWT token `exp` claim is in the past | | `DELEGATION_NOT_FOUND` | Delegation Not Found | No delegation record found for the given `jti` / `delegationId` | | `DELEGATION_INACTIVE` | Delegation Inactive | Delegation status is not `Active` (may be `Exhausted`, `Expired`, or `Revoked`) | | `BUDGET_EXCEEDED` | Budget Exceeded | **Deprecated.** The requested amount would exceed the delegation spending limit | | `INSUFFICIENT_BALANCE` | Insufficient Balance | Credit balance too low and card charge failed | | `MINT_FAILED` | Mint Failed | Credit minting failed after successful card charge | | `BURN_FAILED` | Burn Failed | On-chain credit burn failed | | `TRANSACTION_LIMIT_REACHED` | Transaction Limit Reached | The maximum number of transactions has been reached | | `PAYMENT_FAILED` | Payment Failed | PSP PaymentIntent creation or confirmation failed | | `CARD_DECLINED` | Card Declined | The card was declined by the issuing bank | | `CURRENCY_MISMATCH` | Currency Mismatch | The request currency does not match the delegation currency | | `MERCHANT_ACCOUNT_INVALID` | Merchant Account Invalid | The specified PSP Connected Account is invalid or inactive | ### 7.2 Error Response Format ```json theme={null} { "error": { "code": "BUDGET_EXCEEDED", "message": "Requested amount exceeds remaining delegation budget", "details": { "delegationId": "deleg-8f14e45f-ce34-4797-b88e-968374b0d4b6", "spendingLimitCents": 10000, "spentCents": 9800, "requestedAmountCents": 500 } } } ``` ## 8. Security Considerations ### 8.1 JWT Security * JWT tokens MUST be signed using asymmetric cryptography (RS256 or ES256) * The facilitator's signing private key MUST be stored in a secure key management system * JWT tokens SHOULD have limited validity periods (RECOMMENDED: 30 days maximum) * Replay protection is inherent through the atomic spend counter and transaction tracking ### 8.2 Spending Limits * Every delegation MUST have a finite spending limit (`nvm.spendingLimitCents`) * Spend counters MUST be incremented atomically to prevent race conditions * The facilitator MUST reject requests that would exceed the spending limit, even by a single cent ### 8.3 Atomic Operations * The spend counter increment and PSP PaymentIntent creation MUST be treated as a logical unit * If the PSP charge fails after the counter is incremented, the counter MUST be rolled back * Concurrent settlement requests for the same delegation MUST be serialized or use optimistic concurrency control ### 8.4 PCI Compliance * Raw card numbers (PAN), CVV, and expiry dates MUST NEVER be transmitted to or stored by the facilitator * Card enrollment MUST use a PCI-compliant proxy (VGS) that tokenizes card data before it reaches Nevermined infrastructure * Only PSP-issued tokens (Customer IDs, PaymentMethod IDs) are stored by the facilitator * The facilitator MUST maintain PCI DSS compliance for the handling of any cardholder data references ### 8.5 Delegation Revocation * Clients MUST be able to revoke delegations at any time * Revoked delegations MUST immediately fail verification * In-flight settlements at the time of revocation MAY complete if the PSP PaymentIntent has already been confirmed ### 8.6 Session Key Security * Burn session keys MUST be scoped to specific plan IDs and subscriber addresses * Session keys MUST have expiration times aligned with the delegation expiry * Session keys MUST be stored securely in the facilitator's database * The facilitator MUST validate that the session key referenced in the token matches an active permission record * Session key policies MUST limit the maximum burn amount per operation ## 9. Implementation Notes ### 9.1 Stripe Integration For Stripe-backed plans, this extension uses: | Feature | Purpose | | -------------------- | --------------------------------------------- | | SetupIntents | Securely save payment methods for future use | | PaymentIntents | Charge saved payment methods off-session | | Stripe Connect | Route payments to merchant connected accounts | | Off-session payments | Execute charges without cardholder presence | Implementations MUST use Stripe API version `2023-10-16` or later. ### 9.2 Braintree Integration For Braintree-backed plans, this extension uses: | Feature | Purpose | | ------------------------------ | -------------------------------------------------------------------------------------------- | | Drop-in / hosted fields | PCI-compliant in-browser card capture; returns a single-use payment-method nonce | | Vaulted payment methods | Exchange the nonce for a permanent `paymentMethodToken` via `paymentMethod.create` | | `transaction.sale` | Charge a vaulted payment method off-session at settlement time | | OAuth Connect (Braintree Auth) | Onboard merchants and discover their per-currency child merchant accounts | | Per-currency merchant accounts | Route each charge to a `merchantAccountId` whose `currencyIsoCode` matches the plan currency | Sellers MUST have at least one child merchant account in the plan's currency on their Braintree account; the facilitator discovers the available currencies at OAuth time via `merchantAccount.all()` and persists the resulting `currency → merchantAccountId` map on the seller's profile. Plan creation in a currency the seller does not support is rejected up-front. ### 9.3 PCI-Compliant Card Capture Card enrollment flows MUST tokenize card data in the browser before any data reaches Nevermined infrastructure: * **Stripe path**: card details are collected in a VGS (Very Good Security) forward-proxy iframe and tokenized by VGS before being forwarded to Stripe. The facilitator only ever sees Stripe-issued tokens (SetupIntent IDs, PaymentMethod IDs). * **Braintree path**: card details are collected by the Braintree Drop-in or hosted fields, which post directly to Braintree and return a single-use payment-method nonce. The facilitator exchanges the nonce for a permanent `paymentMethodToken` via the server-side Braintree SDK; raw card data never reaches Nevermined infrastructure. Both paths satisfy the requirement that raw card numbers, CVVs, and expiry dates never transit the facilitator. ### 9.4 Connect / OAuth for Merchant Routing When merchants (server operators) have a connected PSP account, the facilitator routes settlement funds to that account: * **Stripe**: merchants complete Stripe Connect onboarding. Direct charges use `transfer_data.destination`; the facilitator MAY deduct a platform fee using `application_fee_amount`. The `nvm.merchantAccountId` in the JWT identifies the destination account. * **Braintree**: merchants complete OAuth Connect (Braintree Auth) authorization, granting the platform scoped access to their gateway. The facilitator builds a merchant-scoped `BraintreeGateway` per request and charges the merchant share on the merchant's connected account, using a child `merchantAccountId` whose currency matches the plan. The platform fee is charged separately on the platform's own gateway against a platform-side per-currency `merchantAccountId`. ### 9.5 Idempotency Implementations SHOULD pass an idempotency key when creating Stripe PaymentIntents (`idempotency_key`) or Braintree transactions (transaction-level idempotency) to prevent duplicate charges in case of network failures or retries. The idempotency key SHOULD be derived from the delegation ID and a request-specific nonce. ## 10. References * [x402 Protocol](https://x402.org/) * [Stripe PaymentIntents API](https://docs.stripe.com/api/payment_intents) * [Stripe SetupIntents API](https://docs.stripe.com/api/setup_intents) * [Stripe Connect](https://docs.stripe.com/connect) * [Stripe Off-session Payments](https://docs.stripe.com/payments/save-and-reuse) * [Braintree Drop-in UI](https://developer.paypal.com/braintree/docs/guides/drop-in/overview) * [Braintree Vault](https://developer.paypal.com/braintree/docs/guides/payment-methods) * [Braintree OAuth Connect (Braintree Auth)](https://developer.paypal.com/braintree/docs/guides/extend/oauth/overview) * [VGS (Very Good Security)](https://www.verygoodsecurity.com/) * [RFC 7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) * [RFC 2119: Key words for use in RFCs](https://www.rfc-editor.org/rfc/rfc2119) # 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)