Example recipes
Last updated: 2026-10-10
Four short agents that check a source before citing it with AttestPage: LangChain and LlamaIndex in Python, the OpenAI Agents SDK and the Claude Agent SDK in JavaScript. Each runs on the free trial, returns the price of a call it cannot pay to the agent as data instead of failing, and (OpenAI Agents SDK, or the Claude Agent SDK through MCP) can pay per call with x402 on Base Sepolia, a test network. The same files, each with a README, are in the examples/ folder of the source. For a step-by-step x402 payment with plain fetch, and clients that ask for the price once the trial is used up, see More recipes.
Every recipe reads ATTESTPAGE_URL; set it to https://vehcdj664efetfrsolne5umanq.srv.us.
How the clients call a route
- Send the request. While the free trial is on (below), the clients add its header.
- A 200 is the answer, with a signed
receipt. Check it with the free POST /v1/receipt/verify. - A 429 with code
trial_exhaustedmeans the day's trial calls are used; it is not charged, andRetry-Aftergives the seconds until the trial resets. The clients return it as{ "error": "trial_exhausted", "status": 429, … }. - A 402 means the call needs payment: the trial header was not sent, or a trial batch is over 3 items. The offer is in the
PAYMENT-REQUIREDheader (base64 JSON, USDC amounts with 6 decimals). The clients return{ "payment_required": true, "price_usd": …, "network": "eip155:84532" }; an x402 client signs the offer instead (see Payments). - A 400 is bad input and is never charged. See Errors.
A free trial is on: send the header quoteproof-trial: 1 with a paid route and no payment, and the call runs without charge, 5 calls per IP address per UTC day and 200 a day across all callers. Trial receipts have tier "trial" and payment null. Over the limit you get 429 trial_exhausted with Retry-After. A trial call to verify/citations, verify/quotes or check/links covers at most 3 citations, quotes or URLs; a larger batch is not run as a trial but answered with the normal 402 offer, details.reason trial_too_large.
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/quote # free: what a verify/quote answer looks like
curl -si -X POST https://vehcdj664efetfrsolne5umanq.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.08 per call. The page is fetched once; from 9 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 up to 3 URLs on the trial."""
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).
Calls the free trial by default (header Quoteproof-Trial: 1, a few calls per IP a day). Once the day's
trial calls are used, a trial call answers 429 trial_exhausted (not charged), returned as an error dict.
Pass trial=False to skip the trial header. A paid route then answers 402: the tool returns the x402
offer (price, network) instead of raising, so the agent can say what a call would cost.
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://vehcdj664efetfrsolne5umanq.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 3 on the trial, 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 Sepolia wallet made only for testing, holding test USDC.
// AttestPage client for agent tools. With no key it calls the free trial (header Quoteproof-Trial: 1,
// a few calls per IP a day). With ATTESTPAGE_X402_KEY (a 0x… key for a Base Sepolia test wallet holding
// test USDC) 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://vehcdj664efetfrsolne5umanq.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 }),
};
}
Claude Agent SDK (JavaScript)
npm install @anthropic-ai/claude-agent-sdk, then node agent.mjs. It connects the remote MCP endpoint POST https://vehcdj664efetfrsolne5umanq.srv.us/mcp, so there is no client code; paid tools use the free trial. 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 run on the free trial. 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://vehcdj664efetfrsolne5umanq.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}`);
}