CrewAI: check several quotes on one page
Last updated: 2026-10-13
A CrewAI agent in Python that checks up to 20 quotes from one web page in one call to POST /v1/verify/quotes before it cites them. Price: US$0.02 per call, for 1 to 20 quotes. Each quote comes back exact, fuzzy or none, with offsets and context, plus the page's verdict and an Ed25519-signed receipt. Payments are in USDC with x402 v2 on Base mainnet (real USDC).
The tool calls the route over HTTP and pays with the x402 Python package.
To check one quote at a time or a batch of links, on the free trial with no payment code, see Example recipes. The same check for other frameworks: LangChain, LlamaIndex, OpenAI Agents SDK, Vercel AI SDK and Claude Agent SDK.
Install
Python 3.10 to 3.13.
pip install "crewai[anthropic]" "x402[requests,evm]"
export ANTHROPIC_API_KEY=...
export ATTESTPAGE_X402_KEY=0x...
Any model CrewAI supports works: set MODEL (for example openai/gpt-5) and install its key. ATTESTPAGE_X402_KEY is the private key (0x and 64 hex digits) of a wallet holding a little USDC. The key stays in your process.
agent_crew.py
"""CrewAI agent that checks quotes with POST /v1/verify/quotes, paying per call with x402."""
import os
import sys
from crewai import LLM, Agent, Crew, Process, Task
from crewai.tools import tool
from eth_account import Account
from x402 import x402ClientSync
from x402.http.clients import x402_requests
from x402.mechanisms.evm.exact import ExactEvmScheme
from x402.mechanisms.evm.signers import EthAccountSigner
ORIGIN = os.environ.get("ATTESTPAGE_URL", "https://vehcdj664efetfrsolne5umanq.srv.us").rstrip("/")
MAX_UNITS = 20000 # US$0.02 in USDC (6 decimals): the most one call may cost
client = x402ClientSync()
client.register("eip155:8453", ExactEvmScheme(signer=EthAccountSigner(Account.from_key(os.environ["ATTESTPAGE_X402_KEY"]))))
client.register_policy(lambda _version, reqs: [
r for r in reqs if r.network == "eip155:8453" and int(r.get_amount()) <= MAX_UNITS
])
session = x402_requests(client)
@tool("verify_quotes")
def verify_quotes(url: str, quotes: list[str]) -> dict:
"""Use before quoting one web page several times: checks 1 to 20 quotes against the page at url.
Returns results[i].match (exact, fuzzy or none), page.content_kind and a signed receipt. US$0.02 per call."""
try:
res = session.post(f"{ORIGIN}/v1/verify/quotes", json={"url": url, "quotes": quotes}, timeout=60)
return res.json()
except Exception as e: # payment refused by the policy above, network error: tell the agent why
return {"error": str(e)}
checker = Agent(
role="Source checker",
goal="Check every quote against its page before an answer cites it.",
backstory="You check quotes with verify_quotes and cite only the quotes that match.",
tools=[verify_quotes],
llm=LLM(model=os.environ.get("MODEL", "anthropic/claude-sonnet-5-5")),
allow_delegation=False,
verbose=True,
)
def 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" and "without prior coordination with us"?'
)
check = Task(
description=(
f"{question}\n\nBefore you quote a web page, call verify_quotes once with every quote you plan to use from it. "
"Cite a quote only when its match is exact or fuzzy and page.content_kind is real. "
"If the tool returns an error, say what it was and stop."
),
expected_output="A short answer that names each quote checked and its match result.",
agent=checker,
)
crew = Crew(agents=[checker], tasks=[check], process=Process.sequential, verbose=True)
print(crew.kickoff().raw)
if __name__ == "__main__":
main()
python agent_crew.py # pays US$0.02 per call in USDC
The first request gets a 402 with the price in the PAYMENT-REQUIRED header; the x402 session signs the payment and sends it again. The policy keeps only offers on Base (eip155:8453) of at most US$0.02, so a higher price or another network is refused before anything is signed. A request the service refuses (bad URL, empty quote) is refused before payment and not charged.
What comes back
results[i].match:exact,fuzzyornone, in the order ofquotes. Readpage.content_kindfirst:noneon abot_wallorjs_shellpage means the page could not be read.receipt: signed evidence of what the page said and when our fetcher saw it, as a JWS.POST /v1/receipt/verifychecks it for free, or check it offline with [verify-receipt.mjs](https://vehcdj664efetfrsolne5umanq.srv.us/docs/verify-offline). A receipt records what was on the page at that time, not whether a statement on it is correct.- Try the answer shape first with the free sample:
GET https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/quotes.
Terms
Pay per call, no account. See Payments, Limits and the Terms.