Example recipes
Last updated: 2026-10-18
Five short agents that check a source before citing it with AttestPage: LangChain and LlamaIndex in Python, the OpenAI Agents SDK, the Vercel AI SDK and the Claude Agent SDK in JavaScript. Each returns the price of a call it cannot pay to the agent as data instead of failing, and (OpenAI Agents SDK, Vercel AI SDK, or the Claude Agent SDK through MCP) can pay per call with x402 on Base. The same files, each with a README, are in the examples/ folder of the source. A CrewAI agent in Python that checks several quotes on one page, paying per call with x402, is on its own page. For a step-by-step x402 payment with plain fetch, see More recipes.
Every recipe reads ATTESTPAGE_URL; set it to https://s2h6qt73jtzlgbgnnfacln2poq.srv.us.
How the clients call a route
- Send the request.
- A 200 is the answer, with a signed
receipt. Check it with the free POST /v1/receipt/verify. - A 429 with code
rate_limitedmeans too many calls from this IP in a minute; it is not charged, andRetry-Aftergives the seconds to wait. - A 402 means the call needs payment: the call carried no payment. The offer is in the
PAYMENT-REQUIREDheader (base64 JSON, USDC amounts with 6 decimals). The clients return{ "payment_required": true, "price_usd": …, "network": "eip155:8453" }; an x402 client signs the offer instead (see Payments). - A 400 is bad input and is never charged. See Errors.
There is no free trial at the moment; use the free samples instead.
curl -s https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/quote # free: what a verify/quote answer looks like
curl -si -X POST https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/verify/quote -H 'content-type: application/json' -d '{}' | grep -i payment-required # the 402 offer
Several quotes in one call
The recipes call POST /v1/verify/quote once per quote. Two batch routes check several quotes in one call, under one signed receipt:
- POST /v1/verify/quotes: up to 20 quotes on one page, US$0.02 per call. The page is fetched once; from 3 quotes up it costs less than one verify/quote call per quote.
- POST /v1/verify/citations: up to 10 quote and URL pairs, US$0.10 per batch, each page fetched once. A cited paper with a DOI also gets its Crossref data and a retracted flag.
LangChain (Python)
pip install "langchain>=1.0" langchain-anthropic, save the client below as attestpage.py next to it, then python agent.py.
"""LangChain agent that checks its sources with AttestPage before it cites them."""
import os
import sys
from langchain.agents import create_agent
from langchain.tools import tool
import attestpage
@tool
def verify_quote(url: str, quote: str) -> dict:
"""Use before citing a page: does this exact or near-exact quote appear on the page at url?
Returns match (exact, fuzzy or none), the page's content_kind and a signed receipt."""
return attestpage.verify_quote(url, quote)
@tool
def check_links(urls: list[str]) -> dict:
"""Use before putting links in an answer: status, final URL and content_kind for each URL."""
return attestpage.check_links(urls)
agent = create_agent(
model=os.environ.get("MODEL", "anthropic:claude-sonnet-5-5"),
tools=[verify_quote, check_links],
system_prompt="Before you quote a web page, call verify_quote. Only cite a quote whose match is exact or fuzzy "
"on a page whose content_kind is real. If a tool returns payment_required or the error trial_exhausted, say so and stop.",
)
if __name__ == "__main__":
question = " ".join(sys.argv[1:]) or (
'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?'
)
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
print(result["messages"][-1].content)
LlamaIndex (Python)
pip install "llama-index-core>=0.12" llama-index-llms-anthropic, with the same attestpage.py.
"""LlamaIndex agent that checks its sources with AttestPage before it cites them."""
import asyncio
import os
import sys
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import FunctionTool
from llama_index.llms.anthropic import Anthropic
import attestpage
def verify_quote(url: str, quote: str) -> dict:
"""Use before citing a page: does this exact or near-exact quote appear on the page at url?
Returns match (exact, fuzzy or none), the page's content_kind and a signed receipt."""
return attestpage.verify_quote(url, quote)
def fetch_page(url: str) -> dict:
"""Use when you need a page's clean text and a verdict on what came back (real, bot_wall, js_shell, paywall, http_error)."""
return attestpage.fetch_page(url)
agent = FunctionAgent(
tools=[FunctionTool.from_defaults(fn=verify_quote), FunctionTool.from_defaults(fn=fetch_page)],
llm=Anthropic(model=os.environ.get("MODEL", "claude-sonnet-5-5")),
system_prompt="Before you quote a web page, call verify_quote. Only cite a quote whose match is exact or fuzzy "
"on a page whose content_kind is real. If a tool returns payment_required or the error trial_exhausted, say so and stop.",
)
async def main(question):
print(await agent.run(user_msg=question))
if __name__ == "__main__":
asyncio.run(main(" ".join(sys.argv[1:]) or (
'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?'
)))
The Python client
Standard library only. trial=False skips the trial header.
"""AttestPage client for agent tools (Python standard library only).
Sends the Quoteproof-Trial: 1 header by default. The free trial is off, so a paid route answers 402, and the tool returns the x402 offer (price, network) instead of raising, so the agent can say what a call would cost. Pass trial=False to skip the header.
ATTESTPAGE_URL is the AttestPage origin.
"""
import base64
import json
import os
import urllib.error
import urllib.request
BASE_URL = os.environ.get("ATTESTPAGE_URL", "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us").rstrip("/")
def _offer(header):
# PAYMENT-REQUIRED is base64 JSON; amounts are USDC atomic units (6 decimals).
try:
req = json.loads(base64.b64decode(header))
first = req["accepts"][0]
return {"price_usd": int(first["amount"]) / 1_000_000, "network": first["network"], "scheme": first["scheme"]}
except (ValueError, KeyError, IndexError, TypeError):
return {}
def call(path, body, trial=True, base_url=None, timeout=60):
"""POST body to path; returns the JSON answer, a payment_required dict, or an error dict."""
headers = {"content-type": "application/json", "accept": "application/json"}
if trial:
headers["quoteproof-trial"] = "1"
req = urllib.request.Request((base_url or BASE_URL) + path, data=json.dumps(body).encode(), headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout) as res:
return json.loads(res.read())
except urllib.error.HTTPError as e:
try:
err = json.loads(e.read()).get("error", {})
except ValueError:
err = {}
if e.code == 402:
return {"payment_required": True, **_offer(e.headers.get("payment-required", "")),
"message": "This call needs an x402 payment; pay with an x402 v2 client (see /docs/payments)."}
return {"error": err.get("code", "http_%d" % e.code), "status": e.code, "message": err.get("message", "")}
def verify_quote(url, quote, match="fuzzy", **kw):
return call("/v1/verify/quote", {"url": url, "quote": quote, "match": match}, **kw)
def check_links(urls, **kw):
return call("/v1/check/links", {"urls": list(urls)}, **kw)
def fetch_page(url, return_text=True, **kw):
return call("/v1/fetch", {"url": url, "return_text": return_text}, **kw)
def verify_receipt(receipt, **kw):
return call("/v1/receipt/verify", {"receipt": receipt}, trial=False, **kw)
OpenAI Agents SDK (JavaScript)
npm install @openai/agents zod, save the client below as attestpage.mjs, then node agent.mjs.
// OpenAI Agents SDK agent that checks its sources with AttestPage before it cites them.
import { Agent, run, tool } from '@openai/agents';
import { z } from 'zod';
import { createClient } from './attestpage.mjs';
const attestpage = await createClient();
const verifyQuote = tool({
name: 'verify_quote',
description: 'Use before citing a page: does this exact or near-exact quote appear on the page at url? '
+ 'Returns match (exact, fuzzy or none), the page content_kind and a signed receipt.',
parameters: z.object({ url: z.string(), quote: z.string() }),
execute: async ({ url, quote }) => attestpage.verifyQuote({ url, quote }),
});
const checkLinks = tool({
name: 'check_links',
description: 'Use before putting links in an answer: status, final URL and content_kind for each URL (up to 10 paid).',
parameters: z.object({ urls: z.array(z.string()).min(1).max(10) }),
execute: async ({ urls }) => attestpage.checkLinks({ urls }),
});
const agent = new Agent({
name: 'Source checker',
instructions: 'Before you quote a web page, call verify_quote. Only cite a quote whose match is exact or fuzzy '
+ 'on a page whose content_kind is real. If a tool returns payment_required or the error trial_exhausted, say so and stop.',
tools: [verifyQuote, checkLinks],
});
const question = process.argv.slice(2).join(' ')
|| 'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?';
const result = await run(agent, question);
console.log(result.finalOutput);
The JavaScript client
To pay with x402, npm install @x402/fetch @x402/evm viem and set ATTESTPAGE_X402_KEY to the key of a Base wallet holding a little USDC (real money).
// AttestPage client for agent tools. With no key it calls the free trial (header Quoteproof-Trial: 1,
// 10 calls per IP a day). With ATTESTPAGE_X402_KEY (a 0x… key for a Base wallet holding
// USDC, real money) it pays each call with x402 through @x402/fetch. A 402 the client cannot pay comes back as
// { payment_required, price_usd, network } instead of an exception, so the agent can say what a call costs.
// ATTESTPAGE_URL is the AttestPage origin.
const DEFAULT_URL = 'https://s2h6qt73jtzlgbgnnfacln2poq.srv.us';
// A fetch that pays x402 offers from key. The x402 packages load only when a key is set.
export async function payingFetch(key) {
const [{ wrapFetchWithPayment, x402Client }, { registerExactEvmScheme }, { privateKeyToAccount }] = await Promise.all([
import('@x402/fetch'), import('@x402/evm/exact/client'), import('viem/accounts'),
]);
const client = new x402Client();
registerExactEvmScheme(client, { signer: privateKeyToAccount(key) });
return wrapFetchWithPayment(fetch, client);
}
// PAYMENT-REQUIRED is base64 JSON; amounts are USDC atomic units (6 decimals).
function offer(header) {
try {
const first = JSON.parse(Buffer.from(header, 'base64').toString('utf8')).accepts[0];
return { price_usd: Number(first.amount) / 1e6, network: first.network, scheme: first.scheme };
} catch {
return {};
}
}
// fetch: a fetch to use instead (for example one from payingFetch); trial: send the trial header
// (default: only when the client does not pay).
export async function createClient({ url = process.env.ATTESTPAGE_URL || DEFAULT_URL, key = process.env.ATTESTPAGE_X402_KEY, fetch: fetchFn, trial } = {}) {
const paying = Boolean(fetchFn || key);
fetchFn ??= key ? await payingFetch(key) : fetch;
trial ??= !paying;
const base = url.replace(/\/+$/, '');
async function call(path, body, { useTrial = trial } = {}) {
const headers = { 'content-type': 'application/json', accept: 'application/json' };
if (useTrial) headers['quoteproof-trial'] = '1';
const res = await fetchFn(`${base}${path}`, { method: 'POST', headers, body: JSON.stringify(body) });
const json = await res.json().catch(() => ({}));
if (res.ok) return json;
if (res.status === 402) {
return { payment_required: true, ...offer(res.headers.get('payment-required') ?? ''),
message: 'This call needs an x402 payment; set ATTESTPAGE_X402_KEY to pay (see /docs/payments).' };
}
return { error: json.error?.code ?? `http_${res.status}`, status: res.status, message: json.error?.message ?? '' };
}
return {
verifyQuote: ({ url, quote, match = 'fuzzy' }) => call('/v1/verify/quote', { url, quote, match }),
checkLinks: ({ urls }) => call('/v1/check/links', { urls }),
fetchPage: ({ url, return_text = true }) => call('/v1/fetch', { url, return_text }),
verifyReceipt: ({ receipt }) => call('/v1/receipt/verify', { receipt }, { useTrial: false }),
};
}
Vercel AI SDK (JavaScript)
npm install ai @ai-sdk/anthropic zod, with the same attestpage.mjs, then node agent.mjs. Without ANTHROPIC_API_KEY it skips the model and calls verify_quote itself, so you can see what the tool returns.
// Vercel AI SDK agent that checks its sources with AttestPage before it cites them.
// With no ANTHROPIC_API_KEY it skips the model and calls verify_quote itself, so you can see what the tool returns.
import { z } from 'zod';
import { createClient } from './attestpage.mjs';
const attestpage = await createClient();
// tool() from 'ai' only adds types, so plain objects work. Tools return the route's JSON.
const tools = {
verify_quote: {
description: 'Use before citing a page: does this exact or near-exact quote appear on the page at url? '
+ 'Returns match (exact, fuzzy or none), the page content_kind and a signed receipt.',
inputSchema: z.object({ url: z.string(), quote: z.string() }),
execute: ({ url, quote }) => attestpage.verifyQuote({ url, quote }),
},
check_links: {
description: 'Use before putting links in an answer: status, final URL and content_kind for each URL (up to 10 paid).',
inputSchema: z.object({ urls: z.array(z.string()).min(1).max(10) }),
execute: ({ urls }) => attestpage.checkLinks({ urls }),
},
};
const args = process.argv.slice(2);
if (!process.env.ANTHROPIC_API_KEY) {
// No model: node agent.mjs [url] [quote]
const [url = 'https://www.iana.org/help/example-domains', quote = 'These domains may be used as illustrative examples in documents'] = args;
console.log(JSON.stringify(await tools.verify_quote.execute(tools.verify_quote.inputSchema.parse({ url, quote })), null, 2));
} else {
const [{ generateText, isStepCount }, { anthropic }] = await Promise.all([import('ai'), import('@ai-sdk/anthropic')]);
const question = args.join(' ')
|| 'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?';
const { text } = await generateText({
model: anthropic(process.env.MODEL || 'claude-sonnet-5-5'),
tools,
stopWhen: isStepCount(5),
instructions: 'Before you quote a web page, call verify_quote. Only cite a quote whose match is exact or fuzzy '
+ 'on a page whose content_kind is real. If a tool returns payment_required or the error trial_exhausted, say so and stop.',
prompt: question,
});
console.log(text);
}
Claude Agent SDK (JavaScript)
npm install @anthropic-ai/claude-agent-sdk, then node agent.mjs. It connects the remote MCP endpoint POST https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/mcp, so there is no client code. A call that needs payment returns the x402 payment requirements. To pay from the agent's own key, use the stdio server instead (see MCP server).
// Claude Agent SDK agent that checks its sources with AttestPage's remote MCP endpoint (POST /mcp).
// Without a payment the paid tools return the x402 price. A call that needs payment returns the x402 payment requirements.
import { query } from '@anthropic-ai/claude-agent-sdk';
const url = (process.env.ATTESTPAGE_URL || 'https://s2h6qt73jtzlgbgnnfacln2poq.srv.us').replace(/\/+$/, '');
// Tools from an MCP server are named mcp__<server>__<tool>.
const TOOLS = ['attestpage_verify_quote', 'attestpage_check_links', 'attestpage_verify_receipt'];
const prompt = process.argv.slice(2).join(' ')
|| 'Does https://www.iana.org/help/example-domains say "These domains may be used as illustrative examples in documents"?';
for await (const message of query({
prompt,
options: {
mcpServers: { attestpage: { type: 'http', url: `${url}/mcp` } },
allowedTools: TOOLS.map((t) => `mcp__attestpage__${t}`),
systemPrompt: 'Before you quote a web page, call attestpage_verify_quote. Only cite a quote whose match is exact or fuzzy '
+ 'on a page whose content_kind is real. If a tool returns payment requirements, say so and stop.',
maxTurns: 6,
},
})) {
if (message.type === 'result') console.log(message.subtype === 'success' ? message.result : `stopped: ${message.subtype}`);
}