Skip to content

Quickstart

This walks the whole loop: stand up a paid endpoint, then pay it from an agent. Both sides use mainnet USDC on Base, but the only thing that changes for another chain is the chain value.

requirePayment returns Express/Connect middleware. The route answers 402 Payment Required until a payment for the right amount, asset, and recipient verifies on-chain — then it runs.

import express from 'express'
import { requirePayment } from '@piprail/sdk'
const app = express()
app.get(
'/report',
requirePayment({
chain: 'base',
token: 'USDC',
amount: '0.10', // human units — 0.10 USDC
payTo: '0xYourWallet', // paid straight to you; PipRail never touches it
}),
(req, res) => {
res.json({ report: 'the goods behind the paywall' })
},
)
app.listen(3000)

Not on Express? createPaymentGate gives you the same logic framework-free for Hono, Fastify, Workers, Next, Bun, or Deno — see Accepting Payments.

PipRailClient.fetch is a drop-in for fetch. When the server answers 402, it reads the challenge, pays on-chain, and retries — all in one call.

import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({
chain: 'base',
wallet: { privateKey: process.env.AGENT_KEY }, // a 0x-hex key, from the environment
})
const res = await client.fetch('http://localhost:3000/report')
const data = await res.json()
// ^ { report: 'the goods behind the paywall' } — paid for and unlocked

That’s the entire round-trip: 402 → pay on-chain → verify locally → 200.

Section titled “3. Look before you pay (recommended for agents)”

An autonomous agent should learn the price and check it can actually settle before spending. The read-only trio never moves funds; each returns null when the URL isn’t payment-gated (no 402), so null-guard the result before using it.

const url = 'https://api.example.com/report'
const quote = await client.quote(url) // the price, with the token's TRUE decimals
// → { amountFormatted: '0.10', symbol: 'USDC', chain: 'base', withinPolicy: true, … }
const plan = await client.planPayment(url) // can I pay? balance + gas + recipient readiness
// → { payable: true, best: { … }, options: [ … ], fundingHint: null, … }
if (plan?.payable) {
await client.fetch(url)
} else if (plan) {
console.log(plan.fundingHint) // one-line, human-readable: what's missing
}

See quote() for the priced requirement, estimateCost() for the gas, and planPayment() for the full PaymentPlan — per-rail blockers, a fundingHint, and best.

Try it against a live endpoint (no server needed)

Section titled “Try it against a live endpoint (no server needed)”

Want to pay a real 402 before standing up your own server? PipRail runs a live one on Base mainnet — a $0.01 USDC 402 you can hit right now:

Terminal window
curl -i https://piprail.com/x402/demo # see a real 402 challenge + the accepts[]
// Pay it from a client (needs ~$0.01 USDC + a little ETH for gas on Base):
const client = new PipRailClient({ chain: 'base', wallet: { privateKey: process.env.AGENT_KEY } })
const res = await client.fetch('https://piprail.com/x402/demo') // 402 → pay → 200

It’s a real, backendless endpoint — dual-rail (PipRail’s onchain-proof and a gasless standard exact rail settled via the free PayAI facilitator), and it’s listed on x402scan and 402 Index. Prefer to watch the round-trip in your browser first? Try the interactive demo at piprail.com/demo.