# How to check every quote in a draft before publishing, step by step with curl

Last updated: 2026-10-13

To check every quote in a finished answer or report before it goes out, send the whole text to `POST /v1/verify/document` (US$0.10 per part of up to 10 pairs). AttestPage finds each quote and the link it points to, fetches each live page, and answers `exact`, `fuzzy` or `none` for every pair, with the verdict on each page and one signed receipt for the whole part.

This guide runs the check with `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`. For checking a single quote, see [How to check a quote an LLM cites](/docs/verify-a-quote-an-llm-cites).

## What the check does

- **Finds the quotes.** Text between quotation marks that has a link after it, inline or as a reference, is a pair. Code blocks are ignored. The rules are fixed and named by `extractor` (`doc-v1`) in every answer.
- **Fetches each page once.** Each distinct URL is fetched once per part, 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.
- **Judges each 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 document's hash, the options and each pair's verdict. It does not contain the quote text.

## Price

US$0.10 per part of up to 10 pairs, paid in USDC with x402 v2 on Base mainnet (real USDC). A document with 10 quotes or fewer is one part. 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. Preview the pairs for free

The preview is free and changes nothing. It lists every pair it found, what was skipped and why, the number of parts and the exact price. Put the draft in a file:

```sh
cat > document.json <<'EOF'
{ "document": "WHO says \"Air pollution kills an estimated seven million people every year\" ([WHO](https://www.who.int/health-topics/air-pollution)).", "format": "markdown" }
EOF
```

```sh
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/verify/document/preview -H 'content-type: application/json' -d @document.json \
  | jq '{pairs_total, parts, price: .price.usd_total, skipped_counts, pairs: [.pairs[] | {quote, url}]}'
```

```json
{
  "pairs_total": 1,
  "parts": 1,
  "price": "0.10",
  "skipped_counts": {},
  "pairs": [
    {
      "quote": "Air pollution kills an estimated seven million people every year",
      "url": "https://www.who.int/health-topics/air-pollution"
    }
  ]
}
```

Check that each quote is the one you mean and that each link is the source you cited. A quote in `skipped` with `no_url` has no link next to it, so it is not checked.

## 2. Read the price from the 402 offer

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/document -H 'content-type: application/json' -d @document.json \
  | 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 `100000` is US$0.10.

## 3. Check the part

Put the draft and the part number in a file. Part 0 is the first 10 pairs:

```sh
cat > body.json <<'EOF'
{ "document": "WHO says \"Air pollution kills an estimated seven million people every year\" ([WHO](https://www.who.int/health-topics/air-pollution)).", "format": "markdown", "part": 0 }
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. To check a longer draft, set `"part"` to 1 (then 2 and so on) in body.json; the preview gives the number of parts. Save the paid answer as `answer.json` for the steps below.

## 4. Read the answer

Start with `summary`, then go through `results`. Each result carries the `quote`, its `url`, `content_kind`, `match`, `score` and `context`:

```sh
jq '.summary' answer.json
jq '.results[] | {quote, url, content_kind, match, score}' answer.json
```

| `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 changed. |
| `real` | `none` | The quote is not on the page as fetched. Fix the quote or the link before you publish. |
| `bot_wall`, `js_shell`, `paywall`, `http_error` | any | The page could not be read. The quote is unchecked, not false. |

Each result also gives `doc_start` and `doc_end`, the position of the quote in your draft, so you can find it. A quote that appears more than once is checked once and listed with `doc_spans`.

## 5. Keep the receipt

The `receipt` field is a compact JWS. Keep it with the draft. 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_document`, 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_document",
    "arguments": { "document": "WHO says \"Air pollution kills an estimated seven million people every year\" ([WHO](https://www.who.int/health-topics/air-pollution)).", "format": "markdown", "part": 0 },
    "_meta": { "attestpage/trial": false } } }
EOF
```

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

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 a check cost?

US$0.10 per part of up to 10 pairs. A draft with 23 quotes is 3 parts, so 3 calls. The preview in step 1 shows the exact count and total before you pay.

### Is a part charged if a page will not load?

Yes, when the part is checked. A page that returns a 404, a bot wall or a timeout is charged, because that result is what the check finds; `content_kind` says so. A request that fails validation, a document with no quotes that have links, and a part where no URL resolves are never charged.

### Does `exact` mean the claim in the draft is right?

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

### What if the draft is longer than 60,000 bytes?

Split it and check each piece as its own document. Up to 100 pairs are checked per document, in 10 parts.
