Skip to main content
Start here: need to register a service and create a plan first or get your Nevermined API Key? Follow the 5-minute setup.
Complete TypeScript quick start for adding payments to your AI agent or service.

Installation

npm install @nevermined-io/payments

Environment Setup

# Your Nevermined API key from nevermined.app
export NVM_API_KEY="nvm:your-api-key"

# Your wallet address to receive payments
export BUILDER_ADDRESS="0xYourWalletAddress"

# After registration, set these
export AGENT_ID="did:nv:your-agent-id"
export PLAN_ID="did:nv:your-plan-id"

Complete Example

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

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

// In this example we require a payment a payment of 10 USDC for 100 requests
// For that USDC payment we use USDC on Base Sepolia, so we need its contract address:
const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'

// Initialize payments
const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY!,
  environment: 'sandbox' // or 'live'
})

// Store IDs after registration
let agentId: string
let planId: string

// ============================================
// SETUP: Run once to register your agent
// ============================================
async function registerAgent() {
  const result = await payments.agents.registerAgentAndPlan(
    {
      name: 'My TypeScript AI Agent',
      description: 'AI assistant built with TypeScript',
      tags: ['ai', 'typescript'],
      dateCreated: new Date()
    },
    {
      endpoints: [{ POST: `http://localhost:3000/query` }]
    },
    {
      name: 'Starter Plan',
      description: '100 queries',
      dateCreated: new Date()
    },
    payments.plans.getERC20PriceConfig(10_000_000n, USDC_ADDRESS, process.env.BUILDER_ADDRESS!),
    payments.plans.getFixedCreditsConfig(100n, 1n)
  )

  agentId = result.agentId
  planId = result.planId

  console.log(`Agent ID: ${agentId}`)
  console.log(`Plan ID: ${planId}`)
  console.log('Add these to your .env file!')

  return result
}

// ============================================
// MIDDLEWARE: Validate payments
// ============================================
async function validatePayment(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {

  // Get token from payment-signature header
  const x402Token = req.headers['payment-signature']

  if (!x402Token) {
    // Return 402 with payment requirements
    return res.status(402).json({ error: 'Payment Required' })
  }

  // Build payment required specification
  const paymentRequired = buildPaymentRequired(planId, {
    endpoint: req.url,
    agentId: agentId,
    httpVerb: req.method,
  })

  // Verify permissions - facilitator extracts planId and subscriberAddress from token
  const verification = await payments.facilitator.verifyPermissions({
    paymentRequired,
    x402AccessToken: x402Token,
    maxAmount: 1n,
  })

  if (!verification.isValid) {
    return res.status(402).json({ error: 'Payment verification failed' })
  }
  const balance = await payments.plans.getPlanBalance(planId)

  // Attach balance to request
  ;(req as any).credits = balance
  next()
}

// ============================================
// ROUTES
// ============================================

// Health check (public)
app.get('/health', (req, res) => {
  res.json({ status: 'ok', agentId, planId })
})

// Protected AI endpoint
app.post('/query', validatePayment, async (req, res) => {
  const { prompt } = req.body

  // Your AI logic here
  const result = `You asked: "${prompt}". Here's my response...`

  res.json({
    result,
    creditsRemaining: (req as any).credits
  })
})

// Setup endpoint (call once)
app.post('/setup', async (req, res) => {
  try {
    const result = await registerAgent()
    res.json(result)
  } catch (error: any) {
    res.status(500).json({ error: error.message })
  }
})

// ============================================
// START SERVER
// ============================================
const PORT = process.env.PORT || 3000

app.listen(PORT, () => {
  // Load IDs from environment if available
  agentId = process.env.AGENT_ID || ''
  planId = process.env.PLAN_ID || ''

  console.log(`Server running on http://localhost:${PORT}`)

  if (!agentId || !planId) {
    console.log('No agent registered yet. POST to /setup to register.')
  } else {
    console.log(`Agent: ${agentId}`)
    console.log(`Plan: ${planId}`)
  }
})

Run the Example

# 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)
curl -X POST http://localhost:3000/setup
Save the returned agentId and planId to your environment. 2) Try without payment
curl -X POST http://localhost:3000/query \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello"}'
3) As a subscriber, purchase and query
// 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

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

Next Steps