Skip to main content
This guide provides a deep dive into integrating Nevermined. We’ll cover the core components you’ll interact with and the different patterns you can use to add a payment layer to your AI agents.

Core Integration Concepts

Before diving into integration patterns, it’s important to understand the fundamental components of the Nevermined SDK.

1. The Payments Client

The Payments Client is your primary interface for all interactions with the Nevermined protocol. It’s a singleton instance that abstracts away the complexities of blockchain interactions. Key Responsibilities:
  • Agent & Plan Management: Handles the registration and configuration of your agents and their payment plans.
  • Access Validation: Verifies subscriber permissions through bearer token validation.
  • Credit Redemption: Manages the lifecycle of credits, from issuance to consumption.
  • Query Routing: Facilitates communication between subscribers and agents.
Modular Architecture:
const payments = Payments.getInstance({ nvmApiKey, environment })

payments.agents     // Agent registration and management
payments.plans      // Plan creation and ordering
payments.requests   // Request validation and tracking
payments.a2a        // Agent-to-Agent communication

2. AI Agents

In Nevermined, an AI Agent is your monetizable service. It’s defined by its metadata, its API endpoints, and the payment plans associated with it. Agent Structure:
interface Agent {
  agentId: string;
  metadata: {
    name: string;
    description?: string;
    tags: string[];
    dateCreated: Date;
    image?: string;
  };
  api: {
    endpoints: Array<{ [method: string]: string }>; // Protected endpoints
    openEndpoints?: string[]; // Public, non-monetized endpoints
  };
  plans: PaymentPlan[];
}

3. The Credits System

Credits are the unit of consumption in Nevermined, providing a flexible way to bill for AI services. Credit Lifecycle:
  1. Purchase: A subscriber buys a plan and receives credits.
  2. Validation: On each API call, the system checks if the subscriber has sufficient credits.
  3. Consumption: After a successful request, the specified number of credits is redeemed (burned).
  4. Tracking: The subscriber’s balance is updated in real-time.
  5. Expiration: For time-based plans, access expires after the duration ends.

4. Access Control

Nevermined uses bearer token authentication to secure your agent’s endpoints, ensuring only authorized subscribers can access them. How it Works:
1

Plan Purchase

A subscriber purchases a plan through the SDK or Web App.
2

Token Generation

Upon purchase, the subscriber receives a bearer token via getAgentAccessToken().
3

Request Authentication

The subscriber includes the token in the Authorization header of their requests.
4

Validation

The Nevermined Proxy or your agent validates the token, checks credit balance, and authorizes access.
5

Service Delivery

If validated, your agent processes the request and credits are automatically deducted.

Integration Patterns

Choose the integration pattern that best fits your application’s architecture.

Direct Integration

This pattern is best for new services or when you want maximum control over the request lifecycle. Example: Protecting an Express.js Endpoint
app.post('/api/query', async (req, res) => {
  try {
    // Extract bearer token from Authorization header
    const authHeader = req.headers['authorization'];
    if (!authHeader?.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    const token = authHeader.substring(7);
    
    // 1. Validate access token
    const validation = await payments.requests.isValidRequest(
      token,
      req.body
    );
    
    if (!validation.isValid) {
      // Return available payment plans
      return res.status(402).json({
        error: 'Payment Required',
        plans: validation.plans
      });
    }

    // 2. Process the AI request
    const result = await processAIRequest(req.body.prompt);

    // 3. Credits are automatically redeemed by the proxy
    // For direct integration without proxy, you can manually track:
    // await payments.requests.logRequest(token, { usage: 1 });

    // 4. Return response
    res.json(result);

  } catch (error) {
    console.error('Request failed:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

Proxy Integration

For existing services that can’t be modified, use the Nevermined Proxy. The proxy handles all payment validation automatically. How It Works:
  1. Register your existing API endpoints with Nevermined
  2. Subscribers access your service through the proxy URL
  3. The proxy validates tokens and manages credits
  4. Your service receives forwarded requests without modification
Example Registration:
const { agentId, planId } = await payments.agents.registerAgentAndPlan(
  {
    name: 'My Existing Service',
    tags: ['ai', 'proxy']
  },
  {
    endpoints: [
      { POST: 'https://my-api.com/process' },
      { GET: 'https://my-api.com/status' }
    ],
    openEndpoints: ['https://my-api.com/health']
  },
  planMetadata,
  priceConfig,
  creditsConfig
);

// Your service is now accessible at:
// https://proxy.nevermined.app/api/v1/agents/{agentId}/invoke

Middleware Pattern

For Node.js applications, create reusable middleware to protect multiple routes. Example: Express.js Auth Middleware
export function createNeverminedAuth(payments: Payments) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const authHeader = req.headers['authorization'];
      
      if (!authHeader?.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'Missing bearer token' });
      }
      
      const token = authHeader.substring(7);
      const validation = await payments.requests.isValidRequest(token, req.body);
      
      if (!validation.isValid) {
        return res.status(402).json({
          error: 'Payment required',
          plans: validation.plans
        });
      }

      // Attach validation info to request
      (req as any).nevermined = {
        token,
        planId: validation.planId,
        subscriberAddress: validation.subscriberAddress
      };
      
      next();
    } catch (error) {
      console.error('Auth error:', error);
      res.status(500).json({ error: 'Authentication failed' });
    }
  };
}

// Usage
const auth = createNeverminedAuth(payments);

app.post('/api/query', auth, handleQuery);
app.post('/api/generate', auth, handleGenerate);

Direct Integration

Best for new services or when you want full control over the payment flow.

Step 1: Install Dependencies

npm install @nevermined-io/payments express
npm install --save-dev @types/express typescript

Step 2: Initialize Nevermined

import { Payments } from '@nevermined-io/payments'
import express from 'express'

const app = express()
app.use(express.json())

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY,
  environment: 'production'
})

Step 3: Create Protected Endpoint

app.post('/api/query', async (req, res) => {
  try {
    // Extract bearer token
    const authHeader = req.headers['authorization']
    if (!authHeader?.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'Unauthorized - Bearer token required' })
    }
    
    const token = authHeader.substring(7)
    
    // Validate access
    const validation = await payments.requests.isValidRequest(token, req.body)
    
    if (!validation.isValid) {
      return res.status(402).json({
        error: 'Payment required',
        message: 'Please purchase a plan to access this service',
        plans: validation.plans // Available payment plans
      })
    }

    // Process the AI request
    const result = await processAIRequest(req.body.prompt)

    // For direct integration, optionally track usage:
    // await payments.requests.logRequest(token, { 
    //   creditsUsed: 1,
    //   requestData: { prompt: req.body.prompt.substring(0, 100) }
    // })
    
    // Return response
    res.json({
      result,
      usage: {
        creditsUsed: 1,
        planId: validation.planId
      }
    })

  } catch (error) {
    console.error('Request failed:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
})

async function processAIRequest(prompt: string) {
  // Your AI logic here
  return { response: `Processed: ${prompt}` }
}

Step 4: Add Health Check

app.get('/health', (req, res) => {
  res.json({ 
    status: 'healthy',
    service: 'ai-agent',
    version: '1.0.0'
  })
})

Step 5: Register Your Agent

import { 
  getERC20PriceConfig, 
  getFixedCreditsConfig 
} from '@nevermined-io/payments'

async function setupAgent() {
  const agentMetadata = {
    name: 'My AI Service',
    tags: ['ai', 'nlp'],
    dateCreated: new Date(),
    description: 'Advanced AI processing service'
  }

  const agentApi = {
    endpoints: [
      { POST: 'https://api.myservice.com/api/query' }
    ],
    openEndpoints: [
      'https://api.myservice.com/health'
    ]
  }
  
  const planMetadata = {
    name: 'Basic Plan',
    description: '100 queries with advanced AI processing',
    dateCreated: new Date()
  }

  const priceConfig = getERC20PriceConfig(
    10_000_000n, // 10 USDC (6 decimals)
    USDC_ADDRESS,
    process.env.BUILDER_ADDRESS
  )

  const creditsConfig = getFixedCreditsConfig(100n, 1n)

  const { agentId, planId } = await payments.agents.registerAgentAndPlan(
    agentMetadata,
    agentApi,
    planMetadata,
    priceConfig,
    creditsConfig
  )

  console.log('Agent registered:', agentId)
  console.log('Plan created:', planId)
  console.log('Access via proxy:', `https://proxy.nevermined.app/api/v1/agents/${agentId}`)
}

Middleware Pattern

Create reusable authentication middleware for Express/Node.js applications.

Create Auth Middleware

import { Request, Response, NextFunction } from 'express'
import { Payments } from '@nevermined-io/payments'

interface NeverminedRequest extends Request {
  nevermined?: {
    planId: string
    agentId: string
    subscriberAddress: string
  }
}

export function createNeverminedAuth(payments: Payments) {
  return (agentId: string) => {
    return async (
      req: NeverminedRequest, 
      res: Response, 
      next: NextFunction
    ) => {
      try {
        const { planId, subscriberAddress } = req.body
        const signature = req.headers['x-nvm-query-signature'] as string

        if (!planId || !subscriberAddress || !signature) {
          return res.status(400).json({ 
            error: 'Missing required payment parameters' 
          })
        }

        const isValid = await payments.isValidRequest(
          planId,
          agentId,
          subscriberAddress,
          signature
        )

        if (!isValid) {
          const paymentCard = await payments.getAgentPaymentCard(agentId)
          return res.status(402).json({
            error: 'Payment required',
            paymentCard
          })
        }

        // Attach payment info to request
        req.nevermined = { planId, agentId, subscriberAddress }
        next()

      } catch (error) {
        console.error('Auth middleware error:', error)
        res.status(500).json({ error: 'Authentication failed' })
      }
    }
  }
}

Use Middleware

const app = express()
const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY,
  environment: 'testing'
})

const auth = createNeverminedAuth(payments)

// Apply to all protected routes
app.use('/api/*', auth)

// Or apply to specific routes
app.post('/api/query', auth, async (req, res) => {
  // Access validation info from req.nevermined
  const { token, planId } = req.nevermined
  
  // Process request...
  const result = await processQuery(req.body)
  
  // Optionally log usage for direct integration
  // await payments.requests.logRequest(token, { creditsUsed: 1 })
  
  res.json(result)
})

Advanced Middleware Features

export function createAdvancedAuth(payments: Payments, options?: {
  checkBalance?: boolean
  minCredits?: bigint
  rateLimitPerMinute?: number
}) {
  const requestCounts = new Map<string, number[]>()

  return async (req: NeverminedRequest, res: Response, next: NextFunction) => {
    try {
      // Extract and validate token
      const authHeader = req.headers['authorization']
      if (!authHeader?.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'Unauthorized' })
      }
      
      const token = authHeader.substring(7)
      const validation = await payments.requests.isValidRequest(token, req.body)
      
      if (!validation.isValid) {
        return res.status(402).json({ 
          error: 'Payment required',
          plans: validation.plans 
        })
      }

        // Check balance if requested
        if (options?.checkBalance) {
          const balance = await payments.getPlanBalance(
            req.body.planId,
            req.body.subscriberAddress
          )

          if (balance.credits < (options.minCredits || 1n)) {
            return res.status(402).json({
              error: 'Insufficient credits',
              required: options.minCredits?.toString() || '1',
              available: balance.credits.toString()
            })
          }

          res.setHeader('X-Credits-Remaining', balance.credits.toString())
        }

        // Rate limiting per subscriber
        if (options?.rateLimitPerMinute) {
          const now = Date.now()
          const minute = 60 * 1000
          const key = req.body.subscriberAddress
          
          const requests = requestCounts.get(key) || []
          const recentRequests = requests.filter(t => now - t < minute)
          
          if (recentRequests.length >= options.rateLimitPerMinute) {
            return res.status(429).json({
              error: 'Rate limit exceeded',
              retryAfter: minute - (now - recentRequests[0])
            })
          }
          
          recentRequests.push(now)
          requestCounts.set(key, recentRequests)
        }

        req.nevermined = {
          planId: req.body.planId,
          agentId,
          subscriberAddress: req.body.subscriberAddress
        }

        next()

      } catch (error) {
        console.error('Auth error:', error)
        res.status(500).json({ error: 'Authentication failed' })
      }
    }
  }
}

// Usage
const advancedAuth = createAdvancedAuth(payments, {
  checkBalance: true,
  minCredits: 5n,
  rateLimitPerMinute: 60
})

app.post('/api/advanced-query', advancedAuth(agentId), handler)

Proxy Integration

For existing services that can’t be modified, use the Nevermined Proxy.

How It Works

1

Register Your Service

Register your existing endpoints with Nevermined
2

Proxy Handles Payments

All payment validation happens at the proxy layer
3

No Code Changes

Your service continues to work as before

Setup Process

async function setupProxyIntegration() {
  const payments = Payments.getInstance({
    nvmApiKey: process.env.NVM_API_KEY,
    environment: 'production'
  })

  // Register your existing service
  const metadata = {
    name: 'Legacy AI Service',
    tags: ['ai', 'legacy', 'proxy'],
    dateCreated: new Date(),
    description: 'Existing AI service with payment layer'
  }

  const api = {
    endpoints: [
      // Your actual service endpoints
      { POST: 'https://legacy-api.example.com/process' },
      { GET: 'https://legacy-api.example.com/status' }
    ],
    openEndpoints: [
      // Public endpoints (no payment required)
      'https://legacy-api.example.com/health'
    ]
  }

  const price = getERC20PriceConfig(15_000_000n, USDC_ADDRESS, builderAddress)
  const credits = getFixedCreditsConfig(100n, 1n)

  const { agentId, planId } = await payments.registerAgentAndPlan(
    metadata,
    api,
    price,
    credits
  )

  console.log(`
    Proxy Integration Complete!
    
    Your service is now accessible at:
    https://proxy.nevermined.app/agent/${agentId}
    
    The proxy handles:
    - Payment validation
    - Credit redemption  
    - Access control
    - Usage tracking
  `)
}

Client Usage

Subscribers access your proxied service:
// For subscribers
async function useProxiedService() {
  const payments = Payments.getInstance({
    nvmApiKey: process.env.NVM_API_KEY,
    environment: 'production'
  })

  // Purchase plan
  await payments.orderPlan(planId)

  // Get access credentials
  const options = await payments.getAgentHTTPOptions(planId, agentId)

  // Make requests through proxy
  const response = await fetch(options.neverminedProxyUri, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${options.accessToken}`
    },
    body: JSON.stringify({
      // Your original API payload
      data: 'process this'
    })
  })

  const result = await response.json()
  console.log(result)
}

Error Handling

Implement comprehensive error handling for all integration patterns:
import { PaymentsError } from '@nevermined-io/payments'

// Express error handler
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  if (error instanceof PaymentsError) {
    const statusMap = {
      'INVALID_API_KEY': 401,
      'INSUFFICIENT_BALANCE': 402,
      'INVALID_PLAN_CONFIG': 400,
      'NETWORK_ERROR': 503,
      'VALIDATION_ERROR': 400
    }

    const status = statusMap[error.code] || 500
    
    return res.status(status).json({
      error: error.message,
      code: error.code,
      details: error.details
    })
  }

  // Handle other errors
  console.error('Unexpected error:', error)
  res.status(500).json({ 
    error: 'Internal server error',
    message: process.env.NODE_ENV === 'development' ? error.message : undefined
  })
})

Request Tracking

Track and manage the lifecycle of subscriber requests for analytics, debugging, and compliance purposes.

Request Lifecycle Management

The SDK provides methods to track requests from initialization through completion:
// Initialize a new request when subscriber starts
const requestId = await payments.requests.initializeSubscriberRequest(
  planId,
  agentId,
  subscriberAddress,
  {
    method: 'POST',
    endpoint: '/api/query',
    timestamp: new Date(),
    metadata: {
      feature: 'advanced-analysis',
      version: '1.0'
    }
  }
)

// Update request progress (optional)
await payments.requests.updateSubscriberRequest(
  requestId,
  {
    status: 'processing',
    progress: 50,
    checkpoint: 'Model inference started'
  }
)

// Finish request and record results
await payments.requests.finishSubscriberRequest(
  requestId,
  {
    status: 'completed',
    creditsUsed: 5n,
    responseTime: 1250, // milliseconds
    metadata: {
      tokensGenerated: 450,
      modelUsed: 'gpt-4'
    }
  }
)

Integration with Request Handler

Add request tracking to your API endpoints:
app.post('/api/query', authenticate(agentId), async (req, res) => {
  let requestId: string | null = null
  
  try {
    // Initialize request tracking
    requestId = await payments.requests.initializeSubscriberRequest(
      req.nevermined.planId,
      req.nevermined.agentId,
      req.nevermined.subscriberAddress,
      {
        method: req.method,
        endpoint: req.path,
        timestamp: new Date(),
        metadata: {
          prompt: req.body.prompt,
          parameters: req.body.parameters
        }
      }
    )
    
    // Update progress during processing
    await payments.requests.updateSubscriberRequest(requestId, {
      status: 'processing',
      progress: 25,
      checkpoint: 'Request validated'
    })
    
    // Process AI request
    const startTime = Date.now()
    const result = await processAIRequest(req.body.prompt)
    const responseTime = Date.now() - startTime
    
    // Finish request tracking
    await payments.requests.finishSubscriberRequest(requestId, {
      status: 'completed',
      creditsUsed: 1n,
      responseTime,
      metadata: {
        outputLength: result.response.length,
        success: true
      }
    })
    
    res.json({
      result,
      requestId,
      usage: {
        creditsUsed: 1,
        responseTime
      }
    })
    
  } catch (error) {
    // Track failed requests
    if (requestId) {
      await payments.requests.finishSubscriberRequest(requestId, {
        status: 'failed',
        creditsUsed: 0n,
        error: error.message,
        metadata: {
          errorType: error.constructor.name
        }
      })
    }
    
    res.status(500).json({ error: 'Processing failed' })
  }
})

Analytics and Monitoring

Use request tracking data for insights:
// Get request history for a subscriber
async function getSubscriberUsageAnalytics(
  subscriberAddress: string,
  planId: string
) {
  // This would integrate with your analytics backend
  // The request tracking provides the raw data
  
  return {
    totalRequests: 156,
    successRate: 0.98,
    averageResponseTime: 850,
    creditsConsumed: 312,
    popularFeatures: {
      'basic-query': 89,
      'advanced-analysis': 45,
      'batch-processing': 22
    },
    timeDistribution: {
      // Requests by hour of day
    }
  }
}

Best Practices for Request Tracking

  • Use async operations to avoid blocking request processing
  • Consider batching updates for high-volume scenarios
  • Implement request sampling for very high traffic
  • Store detailed metadata only when necessary
  • Don’t store sensitive user data in request metadata
  • Implement data retention policies
  • Allow users to request deletion of their tracking data
  • Comply with GDPR and other privacy regulations
  • Always finish requests even if they fail
  • Track different types of failures separately
  • Use request IDs for debugging and support
  • Implement retry logic for tracking failures

Request Tracking Middleware

Create reusable middleware for automatic request tracking:
export function createRequestTracker(payments: Payments) {
  return (agentId: string) => {
    return async (req: NeverminedRequest, res: Response, next: NextFunction) => {
      if (!req.nevermined) {
        return next()
      }
      
      const requestId = await payments.requests.initializeSubscriberRequest(
        req.nevermined.planId,
        agentId,
        req.nevermined.subscriberAddress,
        {
          method: req.method,
          endpoint: req.path,
          timestamp: new Date()
        }
      )
      
      // Store request ID for later use
      req.nevermined.requestId = requestId
      
      // Override res.json to track completion
      const originalJson = res.json.bind(res)
      res.json = function(data: any) {
        payments.requests.finishSubscriberRequest(requestId, {
          status: 'completed',
          creditsUsed: req.nevermined.creditsUsed || 1n,
          responseTime: Date.now() - req.nevermined.startTime
        }).catch(console.error)
        
        return originalJson(data)
      }
      
      // Track request start time
      req.nevermined.startTime = Date.now()
      
      next()
    }
  }
}

// Usage
const requestTracker = createRequestTracker(payments)
app.post('/api/query', 
  authenticate(agentId), 
  requestTracker(agentId),
  handler
)

Testing Your Integration

Unit Tests

import { Payments } from '@nevermined-io/payments'

describe('Payment Integration', () => {
  let payments: Payments
  
  beforeEach(() => {
    payments = Payments.getInstance({
      nvmApiKey: process.env.NVM_TEST_API_KEY,
      environment: 'testing'
    })
  })

  test('should validate authorized requests', async () => {
    const isValid = await payments.isValidRequest(
      testPlanId,
      testAgentId,
      testSubscriber,
      validSignature
    )
    
    expect(isValid).toBe(true)
  })

  test('should reject unauthorized requests', async () => {
    const isValid = await payments.isValidRequest(
      testPlanId,
      testAgentId,
      'wrong-address',
      'invalid-signature'
    )
    
    expect(isValid).toBe(false)
  })
})

Integration Tests

import request from 'supertest'
import { app } from './app'

describe('API Integration', () => {
  test('should return 402 without payment', async () => {
    const response = await request(app)
      .post('/api/query')
      .send({ prompt: 'test' })
    
    expect(response.status).toBe(402)
    expect(response.body).toHaveProperty('paymentCard')
  })

  test('should process request with valid payment', async () => {
    const response = await request(app)
      .post('/api/query')
      .set('x-nvm-query-signature', validSignature)
      .send({
        planId: testPlanId,
        agentId: testAgentId,
        subscriberAddress: testSubscriber,
        prompt: 'test query'
      })
    
    expect(response.status).toBe(200)
    expect(response.body).toHaveProperty('result')
    expect(response.body.usage).toHaveProperty('creditsRemaining')
  })
})

Production Checklist

Before going live:
  • Environment variables configured
  • Error handling implemented
  • Rate limiting in place
  • Logging and monitoring set up
  • Security headers configured
  • SSL/TLS enabled
  • Health check endpoint working
  • Payment flow tested end-to-end
  • Documentation updated

Next Steps