# How to check many quotes against one page in a single call, step by step with curl

Last updated: 2026-10-13

To check up to 20 quotes against the same web page, send the URL and the quotes to `POST /v1/verify/quotes` (US$0.02 per call). AttestPage fetches the page once, looks for each quote on it, says whether the page loaded as real content, and returns one signed receipt for the whole call.

This guide runs the call with `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`, then shows the same call as an MCP tool. For every field and error, see [Verify quotes on one page](/docs/verify-quotes).

## What the check does

- **Fetches the page once.** Every quote is checked against the same copy, following redirects.
- **Matches each quote.** Both texts are normalised first. `exact` means the words are there; `fuzzy` means a close match at or above `threshold` (default 0.9); `none` means neither. With `"match": "passages"` each quote is a claim and you get the closest passages of the page to judge yourself.
- **Judges the 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.** One Ed25519 receipt covers the page, the options and each verdict. It holds the hash of each quote, not its text.

## Price

US$0.02 per call, whether it carries 1 quote or 20, paid in USDC with x402 v2 on Base mainnet (real USDC). No account, key or sign-up. A request with a malformed URL, an empty quote or a page that robots.txt disallows gets an error before payment and is never charged. If every quote is too costly to match, the call is a 422 and is not charged. From 3 quotes on the same page, this costs less than one [verify/quote](/docs/verify-quote) call per quote. All prices: [pricing.json](https://vehcdj664efetfrsolne5umanq.srv.us/pricing.json).

## 1. See an answer for free

The free sample checks three quotes on the demo page: one word for word, one close match and one that is not on the page.

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/quotes | jq '.results[] | {match, score}'
```

```json
{
  "match": "exact",
  "score": 1
}
{
  "match": "fuzzy",
  "score": 0.963
}
{
  "match": "none",
  "score": null
}
```

## 2. Read the price from the 402 offer

Step 3 explains the body. A request with a valid 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/quotes -H 'content-type: application/json' \
  -d '{ "url": "https://www.iana.org/help/example-domains", "quotes": [ "These domains may be used as illustrative examples in documents", "without prior coordination with us" ] }' \
  | 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 `20000` is US$0.02.

## 3. Check the quotes

Send `url` and `quotes`, a list of 1 to 20 strings (at most 1,000 characters each after normalisation). Optional fields: `match` (`fuzzy` by default, `exact` or `passages`), `case_sensitive` and `threshold`. This body checks two quotes on iana.org:

```sh
cat > body.json <<'EOF'
{ "url": "https://www.iana.org/help/example-domains",
  "quotes": [
    "These domains may be used as illustrative examples in documents",
    "without prior coordination with us"
  ] }
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`.

## 4. Read the answer

```sh
jq '{summary, kind: .page.content_kind, results: [.results[] | {match, score}]}' answer.json
```

`results` has one entry per quote, in request order. `summary` counts `exact`, `fuzzy` and `none` verdicts, and `failed` for quotes that carry an `error` instead of a verdict. `page.content_kind` and `advice` are for the page as a whole.

| `match` | `page.content_kind` | What it means |
|---|---|---|
| `exact` or `fuzzy` | `real` | The quote, or a close version of it, is on the page. `start`, `end` and `context` show where. |
| `none` | `real` | The page loaded and the quote is not on it. Check the quote before you cite it. |
| `none` | anything else | We could not read the page (404, bot wall, JavaScript shell), so we cannot say whether the quote is on it. Read `advice`. |

A quote that is too costly to match fuzzily gets an `error` with code `match_too_costly` on that quote only; the rest are still checked. Use `"match": "exact"`, a shorter quote or fewer quotes per call.

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

## With MCP

The same call is the MCP tool `attestpage_verify_quotes`, 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" }
  }
}
```

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_quotes",
    "arguments": { "url": "https://www.iana.org/help/example-domains",
      "quotes": [
        "These domains may be used as illustrative examples in documents",
        "without prior coordination with us"
      ] },
    "_meta": { "attestpage/trial": false } } }
EOF
```

```json
{
  "scheme": "exact",
  "network": "eip155:8453",
  "amount": "20000"
}
```

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

## Questions

### How much does it cost to check many quotes on one page?

US$0.02 per call for 1 to 20 quotes. Twenty quotes in one call cost the same as one.

### Is the call charged if the page is dead?

A page that is gone or behind a bot wall is a result, and the call is charged. A host name that does not resolve gets 422 host_not_found before payment and is not charged. A request that fails validation, or one where every quote is too costly to match, is not charged. If the page turns out to be disallowed by robots.txt after payment, the call is answered with an error and not charged.

### Does `none` mean the quote is not on the page?

Only when `page.content_kind` is `real`. On a 404, bot wall or JavaScript shell we could not read the page, so `none` says nothing about the quote.
