Lucid Agents
Examples

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:

  1. Your request is sent to the server
  2. Server returns 402 Payment Required with payment details
  3. The x402 fetch wrapper automatically signs and submits payment
  4. Request is retried with payment proof
  5. 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/payments

You'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:

call-paid-agent.ts
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.ts

Alternative: 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

  1. Never commit private keys - Use environment variables or secrets managers
  2. Use testnet for development - Base Sepolia is free and fast
  3. Set appropriate max payment limits - Protect against expensive mistakes
  4. Handle errors gracefully - Check for 402, 404, and other status codes
  5. 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 fees
  • sepolia (eip155:11155111) - Ethereum testnet

On this page