AttestPage · Docs · Pricing · OpenAPI · llms.txt

LangChain: check a quote before citing it

Last updated: 2026-10-10

A LangChain agent with two AttestPage tools: verify_quote (US$0.01 a call) and check_links (US$0.005 per batch of up to 10 URLs). The client below uses only the Python standard library. It calls the free trial; when a call needs payment it returns the price to the agent instead of raising, so the agent can say what the call would cost. To pay per call, see fetch-x402.md or claude-agent-sdk.md. For JavaScript, see LangChain in JavaScript below: its client can pay per call.

Install

Python 3.10 or later.

pip install "langchain>=1.0" langchain-anthropic
export ANTHROPIC_API_KEY=...

Any chat model LangChain supports works; set MODEL (for example openai:gpt-5) and install its package.

attestpage.py

"""AttestPage client for agent tools (Python standard library only).

Sends the free-trial header (Quoteproof-Trial: 1). When the trial does not cover a call (a batch over
3 URLs gets 402; a trial used up for the day gets 429, and this asks again without it for the 402), this
returns {"payment_required": True, "price_usd": ..., "network": ...} instead of raising.
"""
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:
        first = json.loads(base64.b64decode(header))["accepts"][0]
        return {"price_usd": int(first["amount"]) / 1_000_000, "network": first["network"]}
    except (ValueError, KeyError, IndexError, TypeError):
        return {}


def call(path, body, trial=True, 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 + 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 == 429 and trial and err.get("code") == "trial_exhausted":
            return call(path, body, trial=False, timeout=timeout)  # ask again without the trial, for the price
        if e.code == 402:
            return {"payment_required": True, **_offer(e.headers.get("payment-required", "")),
                    "message": "This call needs an x402 payment; 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"):
    return call("/v1/verify/quote", {"url": url, "quote": quote, "match": match})


def check_links(urls):
    return call("/v1/check/links", {"urls": list(urls)})


def verify_receipt(receipt):
    return call("/v1/receipt/verify", {"receipt": receipt}, trial=False)

agent.py

"""LangChain agent that checks a quote with AttestPage before it cites it."""
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 quoting a web page: is this exact or near-exact quote on the page at url?
    Returns result.match (exact, fuzzy or none), page.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 (up to 3 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. Cite a quote only when result.match is exact or fuzzy "
        "and page.content_kind is real. If a tool returns payment_required, give the price 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)

Run

python agent.py
python agent.py 'Is "Example Domain" the title of https://example.com?'

What comes back

verify_quote returns the route's JSON. The parts an agent needs:

On a paraphrased claim, call attestpage.verify_quote(url, claim, match="passages"): it returns the 3 passages that share most of the claim's words, as evidence to read, not a verdict.

LangChain in JavaScript

The same agent in JavaScript. It uses the attestpage.mjs client from openai-agents-sdk.md: with no key it calls the free trial, and with ATTESTPAGE_X402_KEY set it pays each call with x402 on Base Sepolia, a test network, in test USDC.

Node.js 20 or later. Save the client as attestpage.mjs next to langchain.mjs.

npm init -y && npm pkg set type=module
npm install langchain @langchain/core @langchain/anthropic zod
npm install @x402/fetch @x402/evm viem   # only needed to pay
export ANTHROPIC_API_KEY=...

langchain.mjs

// LangChain.js agent that checks a quote with AttestPage before it cites it.
import { createAgent, tool } from 'langchain';
import * as z from 'zod';
import { createClient } from './attestpage.mjs';

const attestpage = await createClient();

// Tools return the route's JSON as text.
const verifyQuote = tool(async ({ url, quote }) => JSON.stringify(await attestpage.verifyQuote({ url, quote })), {
  name: 'verify_quote',
  description: 'Use before quoting a web page: is this exact or near-exact quote on the page at url? '
    + 'Returns result.match (exact, fuzzy or none), page.content_kind and a signed receipt. US$0.01.',
  schema: z.object({ url: z.string(), quote: z.string() }),
});

const checkLinks = tool(async ({ urls }) => JSON.stringify(await attestpage.checkLinks({ urls })), {
  name: 'check_links',
  description: 'Use before putting links in an answer: status, final URL and content_kind for each URL. '
    + 'Up to 10 URLs (3 on the free trial). US$0.005 per batch.',
  schema: z.object({ urls: z.array(z.string()).min(1).max(10) }),
});

const agent = createAgent({
  model: process.env.MODEL || 'anthropic:claude-sonnet-5-5',
  tools: [verifyQuote, checkLinks],
  systemPrompt: 'Before you quote a web page, call verify_quote. Cite a quote only when result.match is exact or fuzzy '
    + 'and page.content_kind is real. If a tool returns payment_required, give the price and stop.',
});

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 agent.invoke({ messages: [{ role: 'user', content: question }] });
console.log(result.messages.at(-1).content);
node langchain.mjs                              # free trial
ATTESTPAGE_X402_KEY=0x... node langchain.mjs    # pays US$0.01 per verify_quote in test USDC