Calling Paid Endpoints
Use the SDK to invoke x402-protected agent entrypoints.
This guide demonstrates how to call paid (x402-protected) agent endpoints using the API SDK from external clients like scripts, CLIs, or applications.
Overview
The x402 protocol enables HTTP-native payments. When you call a paid endpoint:
- Your request is sent to the server
- Server returns
402 Payment Requiredwith payment details - The x402 fetch wrapper automatically signs and submits payment
- Request is retried with payment proof
- Server verifies payment and processes request
The createX402Fetch helper handles steps 2-4 automatically.
Prerequisites
Install the required packages:
bun add @lucid-agents/api-sdk @lucid-agents/paymentsYou'll need a wallet with USDC on a supported network:
- Base (recommended) - Low fees, fast confirmations
- Base Sepolia - Testnet for development
- Ethereum - Mainnet
Environment setup
Create a .env file with your private key:
# .env
PRIVATE_KEY=0x... # Your wallet private key (never commit this!)Never commit your private key to version control. Use environment variables or a secrets manager.
Basic usage
import { createClient, createConfig } from '@lucid-agents/api-sdk/client';
import { createX402Fetch, accountFromPrivateKey } from '@lucid-agents/payments';
// Load private key from environment
const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable required');
}
// Create payment-enabled fetch
const x402Fetch = createX402Fetch({
account: accountFromPrivateKey(privateKey),
});
// Create SDK client with x402 fetch
const client = createClient(
createConfig({
baseUrl: 'https://api-lucid-dev.daydreams.systems',
fetch: x402Fetch,
})
);
// Invoke a paid endpoint
const { data, error } = await client.POST('/agents/{agentId}/entrypoints/{key}/invoke', {
params: {
path: { agentId: 'agent-123', key: 'premium-analysis' },
},
body: {
input: { text: 'Analyze this content for me' },
},
});
if (error) {
console.error('Request failed:', error);
} else {
console.log('Result:', data.output);
}Complete example
Here's a full script with proper error handling:
import { createClient, createConfig } from '@lucid-agents/api-sdk/client';
import { createX402Fetch, accountFromPrivateKey } from '@lucid-agents/payments';
async function main() {
// Validate environment
const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
if (!privateKey) {
console.error('Error: PRIVATE_KEY environment variable is required');
process.exit(1);
}
// Create payment-enabled fetch
const x402Fetch = createX402Fetch({
account: accountFromPrivateKey(privateKey),
});
// Create SDK client
const client = createClient(
createConfig({
baseUrl: process.env.API_URL ?? 'https://api-lucid-dev.daydreams.systems',
fetch: x402Fetch,
})
);
// First, discover the agent to get its entrypoints
const { data: agent, error: discoverError } = await client.GET('/agent/{slug}', {
params: { path: { slug: 'my-paid-agent' } },
});
if (discoverError) {
console.error('Failed to discover agent:', discoverError);
process.exit(1);
}
console.log('Found agent:', agent.name);
console.log('Entrypoints:', agent.entrypoints?.map(e => e.key).join(', '));
// Invoke a paid entrypoint
const { data, error, response } = await client.POST(
'/agents/{agentId}/entrypoints/{key}/invoke',
{
params: {
path: { agentId: agent.id, key: 'analyze' },
},
body: {
input: { query: 'What is the meaning of life?' },
},
}
);
if (error) {
// Handle specific error cases
switch (response.status) {
case 402:
console.error('Payment failed - check wallet balance');
break;
case 404:
console.error('Agent or entrypoint not found');
break;
case 500:
console.error('Server error:', error);
break;
default:
console.error('Request failed:', error);
}
process.exit(1);
}
console.log('Success!');
console.log('Output:', JSON.stringify(data.output, null, 2));
}
main().catch(console.error);Run it:
PRIVATE_KEY=0x... bun run call-paid-agent.tsAlternative: Raw @x402 packages
For more control over the wallet and network configuration:
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { ExactEvmScheme, toClientEvmSigner } from '@x402/evm';
import { privateKeyToAccount } from 'viem/accounts';
// Create viem account and signer
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const signer = toClientEvmSigner(account);
// Create x402 client and register networks
const client = new x402Client()
.register('eip155:8453', new ExactEvmScheme(signer)) // Base mainnet
.register('eip155:84532', new ExactEvmScheme(signer)) // Base Sepolia
.register('eip155:1', new ExactEvmScheme(signer)) // Ethereum mainnet
.register('eip155:11155111', new ExactEvmScheme(signer)); // Ethereum Sepolia
// Wrap fetch with payment handling
const x402Fetch = wrapFetchWithPayment(fetch, client);
// Make paid request
const response = await x402Fetch(
'https://api-lucid-dev.daydreams.systems/agents/agent-123/entrypoints/premium/invoke',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: { query: 'Hello' } }),
}
);Error handling
402 Payment Required
If the automatic payment fails (e.g., insufficient funds):
const { error, response } = await client.POST('/agents/{agentId}/entrypoints/{key}/invoke', {
params: { path: { agentId: 'agent-123', key: 'premium' } },
body: { input: {} },
});
if (response.status === 402) {
// Payment was required but failed
// Check your wallet balance
console.error('Payment failed - ensure wallet has sufficient USDC');
}Wallet errors
import { accountFromPrivateKey } from '@lucid-agents/payments';
try {
const account = accountFromPrivateKey(process.env.PRIVATE_KEY as `0x${string}`);
} catch (error) {
// Invalid or missing private key
console.error('Invalid private key:', error.message);
}Max payment limit
The createX402Fetch helper has a default maximum payment limit of $10 USDC to protect against unexpectedly expensive calls. If an endpoint costs more, the payment will be rejected.
When using wrapFetchWithPayment directly, you can control this limit using policies:
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { ExactEvmScheme, toClientEvmSigner } from '@x402/evm';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const signer = toClientEvmSigner(account);
// Create x402 client with a max payment policy
const client = new x402Client()
.register('eip155:84532', new ExactEvmScheme(signer))
.registerPolicy((version, requirements) => {
// Filter out requirements exceeding $1 USDC (1_000_000 base units)
return requirements.filter(r => BigInt(r.amount) <= BigInt(1_000_000));
});
const x402Fetch = wrapFetchWithPayment(fetch, client);Best practices
- Never commit private keys - Use environment variables or secrets managers
- Use testnet for development - Base Sepolia is free and fast
- Set appropriate max payment limits - Protect against expensive mistakes
- Handle errors gracefully - Check for 402, 404, and other status codes
- Verify agent pricing - Check the agent card before calling expensive endpoints
Network selection
The payment network is determined by the agent receiving payment, not your wallet. Your wallet just needs USDC on that network.
Common networks:
base(eip155:8453) - Production, low fees (~$0.001)base-sepolia(eip155:84532) - Development/testing (free testnet USDC)ethereum(eip155:1) - Production, higher feessepolia(eip155:11155111) - Ethereum testnet
Related
- API SDK Documentation - Full SDK reference
- Payments Extension - Agent-side payment configuration
- Payment Policies - Spending limits and controls