# How to check a quote an LLM cites, step by step with curl

Last updated: 2026-10-10

To check that a quote an LLM cites really appears on the source page, send the page URL and the quote, word for word, to `POST /v1/verify/quote` (US$0.01 per call). AttestPage fetches the live page, searches its text for the quote and answers `exact`, `fuzzy` or `none`, with a verdict on the page and a signed receipt.

This guide runs the check with `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`, then shows the same call as an MCP tool. For why a plain string search falls short, see [How to verify a quote an LLM cites](/docs/verify-a-quote-an-llm-cites).

## What the check does

- **Fetches the page.** It follows redirects and reads the page text as our fetcher receives it at that moment.
- **Matches the quote.** Both texts are normalised first (Unicode NFKC, plain quotes and dashes, one space for any whitespace). `exact` means the words are there after normalisation; `fuzzy` means a close match scored at or above `threshold` (default 0.9); `none` means neither.
- **Judges the page.** `page.content_kind` is `real` when the page loaded as content, or `bot_wall`, `js_shell`, `paywall`, `http_error` and others when it did not.
- **Signs a receipt.** An Ed25519 receipt records the URL, the time, the page hashes and the match. Anyone can check it later against our published keys.

## Price

US$0.01 per call, paid in USDC with x402 v2 on Base Sepolia (a test network). No account, key or sign-up. A request that fails validation gets 400 before payment and is never charged. All prices: [pricing.json](https://vehcdj664efetfrsolne5umanq.srv.us/pricing.json).

## 1. See an answer for free

The free sample runs the same route on a demo page:

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/quote | jq '{match: .result.match, score: .result.score, kind: .page.content_kind, advice}'
```

```json
{
  "match": "fuzzy",
  "score": 0.963,
  "kind": "real",
  "advice": "The page loaded and looks like real content."
}
```

## 2. Read the price from the 402 offer

A request with an empty body gets the payment offer without being charged. The `PAYMENT-REQUIRED` header is base64 JSON:

```sh
curl -si -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/verify/quote -H 'content-type: application/json' -d '{}' \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' | base64 -d \
  | jq '.accepts[0] | {scheme, network, amount, payTo}'
```

`amount` is in USDC base units (6 decimals), so `10000` is US$0.01.

## 3. Check a quote

Put the cited URL and the quote, exactly as the LLM gave it, in a file:

```sh
cat > body.json <<'EOF'
{ "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" }
EOF
```

Pay with any x402 v2 client: it reads the offer from step 2, signs it and sends the same request again with a `PAYMENT-SIGNATURE` header. [One x402 payment step by step](/docs/examples/fetch-x402) shows this with plain `fetch`, and the [MCP server](/docs/mcp) does it for you. Save the answer as `answer.json` for the steps below.

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.

On the trial, the same check runs from `curl`:

```sh
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/verify/quote -H 'content-type: application/json' \
  -H 'quoteproof-trial: 1' -d @body.json > answer.json
jq '{match: .result.match, score: .result.score, context: .result.context, kind: .page.content_kind, tier}' answer.json
```

## 4. Read the answer

Read `page.content_kind` first, then `result.match`:

| `content_kind` | `match` | What it means |
|---|---|---|
| `real` | `exact` | The quoted words are on the page. |
| `real` | `fuzzy` | A close match. Compare `context` with the quote: a word may have been changed or two sentences joined. |
| `real` | `none` | The quote is not on the page our fetcher received. Do not cite it to this source. |
| `bot_wall`, `js_shell`, `paywall`, `http_error` | any | The page could not be read. The quote is unchecked, not false. |

If the LLM paraphrased rather than quoted, send `"match": "passages"` with the claim as `quote`. You get the 3 passages that share most of its words, as evidence to read, not a verdict. Details: [Verify a quote](/docs/verify-quote).

## 5. Keep the receipt

The `receipt` field is a compact JWS. Check it with the free verify route:

```sh
jq '{receipt}' answer.json \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- | jq .valid
```

Or check it offline with our single-file checker: [Verify receipts offline](/docs/verify-offline).

## With MCP

The same check is the MCP tool `attestpage_verify_quote`, with the body from step 3 as its arguments. To use it from an MCP client with nothing to install, add the remote endpoint:

```json
{
  "mcpServers": {
    "attestpage": { "type": "http", "url": "https://vehcdj664efetfrsolne5umanq.srv.us/mcp" }
  }
}
```

Or run the local server, which pays from your own wallet key within limits you set: [MCP server](/docs/mcp).

One call to the remote endpoint with `curl`. `_meta["attestpage/trial"]` set to `false` asks for the payment offer rather than a trial call, so nothing is charged:

```sh
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/mcp -H 'content-type: application/json' -d @- <<'EOF' \
  | jq '.result.structuredContent.accepts[0] | {scheme, network, amount}'
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "attestpage_verify_quote",
    "arguments": { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" },
    "_meta": { "attestpage/trial": false } } }
EOF
```

```json
{
  "scheme": "exact",
  "network": "eip155:84532",
  "amount": "10000"
}
```

The result has `isError: true` and the x402 payment requirements in `structuredContent`, with the same `amount` as step 2. Sign one of `accepts`, put the payload in `_meta["x402/payment"]` and call again: `structuredContent` is then the same JSON as `answer.json`, and `_meta["x402/payment-response"]` holds the settlement.

## Questions

### How much does one check cost?

US$0.01 per quote with `POST /v1/verify/quote`. To check up to 10 quotes from one answer in one call, `POST /v1/verify/citations` costs US$0.10 per batch: [Verify citations](/docs/verify-citations).

### Does an exact match mean the quoted claim is right?

No. It means those words were on the page our fetcher received at that time. Whether the page itself is right is a separate question.

### Is a call charged if the page will not load?

Only answers below 400 are charged. A request that fails validation, a host name that does not resolve and a blocked URL are never charged. A page that answers with a 404 or a bot wall is charged, because that result is what the call checks; `content_kind` says so. Every case is in [Payments](/docs/payments) and [Errors](/docs/errors).
