Server SDK Guide

March 15, 2026 · View on GitHub

Overview

The server SDK lets API providers accept payments from AI agents with one line of middleware per route.

Installation

# Express
pnpm add @openagentpay/server-express @openagentpay/core

# Hono
pnpm add @openagentpay/server-hono @openagentpay/core

# Choose an adapter
pnpm add @openagentpay/adapter-mock     # for testing
pnpm add @openagentpay/adapter-x402     # for real USDC payments
pnpm add @openagentpay/adapter-credits  # for prepaid credits

Basic Setup (Express)

import express from 'express';
import { createPaywall } from '@openagentpay/server-express';
import { mock } from '@openagentpay/adapter-mock';

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

const paywall = createPaywall({
  recipient: '0xYourWalletAddress',
  adapters: [mock()],
});

app.get('/api/data', paywall({ price: '0.01' }), (req, res) => {
  res.json({ data: 'premium content' });
});

app.listen(3000);

Basic Setup (Hono)

import { Hono } from 'hono';
import { createPaywall } from '@openagentpay/server-hono';
import { mock } from '@openagentpay/adapter-mock';

const app = new Hono();

const paywall = createPaywall({
  recipient: '0xYourWalletAddress',
  adapters: [mock()],
});

app.get('/api/data', paywall({ price: '0.01' }), (c) => {
  return c.json({ data: 'premium content' });
});

export default app;

Pricing

Static Pricing

app.get('/api/search', paywall({ price: '0.01' }), handler);
app.get('/api/premium', paywall({ price: '0.10', currency: 'USDC' }), handler);

Dynamic Pricing

Price as a function of the request:

app.post('/api/process', paywall((req) => ({
  price: (0.01 * req.body.pages).toFixed(3),
  description: `Process ${req.body.pages} pages`,
})), handler);

Subscriptions

const paywall = createPaywall({
  recipient: '0x...',
  adapters: [mock()],
  subscriptions: {
    plans: [
      {
        id: 'hourly-unlimited',
        amount: '0.50',
        currency: 'USDC',
        period: 'hour',
        calls: 'unlimited',
      },
      {
        id: 'daily-1000',
        amount: '5.00',
        currency: 'USDC',
        period: 'day',
        calls: 1000,
        rate_limit: 60,
      },
    ],
  },
});

// Register subscription management endpoints
app.use(paywall.routes());

This creates:

  • POST /openagentpay/subscribe — subscribe to a plan
  • GET /openagentpay/subscription — check subscription status
  • POST /openagentpay/unsubscribe — cancel subscription

Events

paywall.on('payment:received', (receipt) => {
  console.log(`Earned ${receipt.payment.amount} ${receipt.payment.currency}`);
});

paywall.on('payment:failed', (error) => {
  console.log(`Payment failed: ${error.message}`);
});

Receipt Storage

const paywall = createPaywall({
  recipient: '0x...',
  adapters: [mock()],
  receipts: {
    emit: true,        // fire events (default)
    store: 'memory',   // or a custom ReceiptStore
  },
});

Multiple Adapters

Adapters are tried in order. The first one that detects a payment handles it:

import { x402 } from '@openagentpay/adapter-x402';
import { credits } from '@openagentpay/adapter-credits';
import { mock } from '@openagentpay/adapter-mock';

const paywall = createPaywall({
  recipient: '0x...',
  adapters: [
    x402({ network: 'base' }),      // Try x402 first
    credits({ store: creditStore }), // Then credits
    mock(),                          // Fallback to mock (dev only)
  ],
});

How the 402 Response Works

When an agent calls your endpoint without payment, they get:

{
  "type": "payment_required",
  "version": "1.0",
  "resource": "/api/search",
  "pricing": {
    "amount": "0.01",
    "currency": "USDC",
    "unit": "per_request"
  },
  "methods": [
    { "type": "x402", "network": "base", ... },
    { "type": "credits", "purchase_url": "...", ... }
  ],
  "subscriptions": [
    { "id": "daily-1000", "amount": "5.00", ... }
  ]
}

The agent parses this, selects a payment method, pays, and retries.