Documentation Index
Fetch the complete documentation index at: https://docs.nevermined.app/llms.txt
Use this file to discover all available pages before exploring further.
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 | Crypto payments | ERC-4337 UserOps + session keys |
nvm:card-delegation | stripe | Fiat/credit card | Stripe PaymentIntent + 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. The SDK auto-detects the scheme via resolveScheme().
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:
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:
- Visit nevermined.app/permissions/agent-permissions
- Select the plan you’ve purchased
- Configure token parameters (agent, expiration, limits)
- Generate and copy the X402 access token
- 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:
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:
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:
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:
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 | Stripe payment method ID (e.g., pm_...) |
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 | Stripe Connect account ID |
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:
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:
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:
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:
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 (e.g., stripe) |
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:
{
"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:
{
"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 for fiat
- 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:
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
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:
// 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
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
}
Subscribers include this header in requests:
POST /api/v1/tasks HTTP/1.1
Host: agent.example.com
Content-Type: application/json
PAYMENT-SIGNATURE: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{"prompt": "Hello"}
Agents return this header when payment is required:
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:
{
"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:
{
"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": {}
}
Agents include this header in successful responses:
HTTP/1.1 200 OK
Content-Type: application/json
PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4Li4uIn0=
{"result": "Task completed"}
The header contains base64-encoded settlement details:
{
"success": true,
"network": "eip155:84532",
"transaction": "0x...",
"creditsRedeemed": "2",
"remainingBalance": "98"
}
Complete X402 Flow
Subscriber Side
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
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:
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 automatically set to 'stripe'.
Best Practices
- Always Verify Before Execute: Never skip token verification
- Settle After Success: Only burn credits after successful execution
- Use X402 v2 Headers: Prefer
PAYMENT-SIGNATURE over Authorization
- Return 402 Properly: Include
PAYMENT-REQUIRED header with details
- Log Transactions: Record settlement transaction hashes
- Handle Errors: Provide clear error messages in 402 responses
- Token Reuse: Subscribers can reuse tokens for multiple requests
- Restrict Cards to API Keys: When running multiple agents, restrict each card to specific NVM API Keys using
allowedApiKeyIds to prevent unauthorized spending
- Reuse Delegations: Pass
delegationId to reuse existing delegations instead of creating new ones on each request — this avoids delegation sprawl and keeps spending consolidated
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)