# LlamaIndex: check a quote before citing it

Last updated: 2026-10-12

A LlamaIndex.TS 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). It uses the `attestpage.mjs` client from [openai-agents-sdk.md](/docs/examples/openai-agents-sdk): with no key it calls the free trial, which allows 5 calls per IP address per UTC day and up to 3 URLs per `check_links` call. With `ATTESTPAGE_X402_KEY` set, it pays each call with x402 on Base, in USDC. When a call needs payment and no key is set, the tool returns the price to the agent instead of raising, so the agent can say what the call would cost.

To check up to 20 quotes from one page in one paid call, see [LlamaIndex (Python) with POST /v1/verify/quotes](https://vehcdj664efetfrsolne5umanq.srv.us/examples/llamaindex).

For the same check in other frameworks, see [LangChain](/docs/examples/langchain), [OpenAI Agents SDK](/docs/examples/openai-agents-sdk), [Vercel AI SDK](/docs/examples/vercel-ai-sdk) and [Claude Agent SDK](/docs/examples/claude-agent-sdk).

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

## Install

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

Any chat model LlamaIndex supports works: import its class from its package and pass it as `llm`.

## llamaindex.mjs

```js
// LlamaIndex.TS agent that checks a quote with AttestPage before it cites it.
import { tool } from 'llamaindex';
import { agent } from '@llamaindex/workflow';
import { Anthropic } from '@llamaindex/anthropic';
import { z } from 'zod';
import { createClient } from './attestpage.mjs';

const attestpage = await createClient();

// @llamaindex/anthropic 0.3 lets only claude-3 and claude-4 model names call tools; newer Claude models can too.
class Claude extends Anthropic {
  get supportToolCall() { return true; }
}

// Tools return the route's JSON.
const verifyQuote = tool({
  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.',
  parameters: z.object({ url: z.string(), quote: z.string() }),
  execute: ({ 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 URLs (3 on the free trial). US$0.005 per batch.',
  parameters: z.object({ urls: z.array(z.string()).min(1).max(10) }),
  execute: ({ urls }) => attestpage.checkLinks({ urls }),
});

const checker = agent({
  name: 'quote_checker',
  llm: new Claude({ model: process.env.MODEL || '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 checker.run(question);
console.log(result.data.result);
```

```sh
node llamaindex.mjs                              # free trial
ATTESTPAGE_X402_KEY=0x... node llamaindex.mjs    # pays US$0.01 per verify_quote in USDC
```

## What comes back

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

- `result.match`: `exact`, `fuzzy` or `none`, with `result.score` from 0 to 1.
- `page.content_kind`: read it before trusting `match`. A `none` on a `bot_wall` or `js_shell` page means the page could not be read, not that the quote is missing.
- `receipt`: keep it with the answer. `POST /v1/receipt/verify` checks it for free.
