# 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](https://vehcdj664efetfrsolne5umanq.srv.us/docs/recipes). The same check for other frameworks: [LangChain](https://vehcdj664efetfrsolne5umanq.srv.us/examples/langchain.md), [LlamaIndex](https://vehcdj664efetfrsolne5umanq.srv.us/examples/llamaindex.md), [OpenAI Agents SDK](https://vehcdj664efetfrsolne5umanq.srv.us/examples/openai-agents.md), [Vercel AI SDK](https://vehcdj664efetfrsolne5umanq.srv.us/examples/vercel-ai-sdk.md) and [Claude Agent SDK](https://vehcdj664efetfrsolne5umanq.srv.us/examples/claude-agent-sdk.md).

## Install

Python 3.10 to 3.13.

```sh
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

```python
"""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()
```

```sh
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`, `fuzzy` or `none`, in the order of `quotes`. Read `page.content_kind` first: `none` on a `bot_wall` or `js_shell` page 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/verify` checks 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](https://vehcdj664efetfrsolne5umanq.srv.us/docs/payments), [Limits](https://vehcdj664efetfrsolne5umanq.srv.us/docs/limits) and the [Terms](https://vehcdj664efetfrsolne5umanq.srv.us/terms).
