AttestPage · Docs · Pricing · OpenAPI · llms.txt

How to check a quote against text you already hold, step by step with curl

Last updated: 2026-10-13

To check that a quote really appears in a text you already have, such as a file you read, a tool result or a document, send the text and the quote, word for word, to POST /v1/verify/text-quote (US$0.005 per call). Nothing is fetched. AttestPage searches the text you sent and answers exact, fuzzy or none, with a score, the offsets of the match 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. To check a quote against a live web page instead, use Verify a quote, which fetches the page.

What the check does

Price

US$0.005 per call, paid in USDC with x402 v2 on Base mainnet (real USDC). No account, key or sign-up. A request that fails validation gets 400 before payment and is never charged. All prices: pricing.json.

1. See an answer for free

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

curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/textquote | jq '{match: .result.match, score: .result.score, start: .result.start, end: .result.end, advice}'
{
  "match": "fuzzy",
  "score": 0.963,
  "start": 43,
  "end": 70,
  "advice": "The supplied text contains a close match to this quote, with small differences. This says nothing about where the text came from."
}

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:

curl -si -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/verify/text-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 5000 is US$0.005.

3. Check a quote

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

cat > body.json <<'EOF'
{ "text": "Refunds are paid within 14 days of purchase, to the card that was used.", "quote": "refunds are paid within 14 days", "match": "fuzzy" }
EOF

text may be up to 50,000 characters and 64 KB of UTF-8. quote may be up to 1,000 characters. Do not send a url field: this route never fetches, so an unknown field gets 400.

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 shows this with plain fetch, and the MCP server 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/document, verify/quotes, check/links or fetch with urls covers at most 3 citations, document pairs, quotes or URLs; a larger batch is not run as a trial but answered with the normal 402 offer, details.reason trial_too_large.

4. Read the answer

Read result.match first, then injection_flags:

matchWhat it means
exactThe quoted words are in the text after normalisation.
fuzzyA close match. Compare result.context with the quote: a word may have changed or two sentences may have been joined.
noneThe quote is not in the text. Do not quote it from this source.

result.occurrences says how many times the quote was found. result.start and result.end are offsets into the normalised text, not into what you sent, and result.normalised_sha256 is the hash of that normalised text.

To check one part of the text, add start and end to the body. They are offsets into your text as sent, counted in characters (code points), with end not included, and the slice may be up to 1,000 characters. The answer then has result.declared with slice_matches, which is true when the text at those offsets says the quote. Offsets are raw; the result offsets from a search are normalised, so do not mix the two.

If the LLM paraphrased rather than quoted, send "match": "passages" with the claim as quote. You get the passages of the text that share most of its words, as evidence to read, not a verdict. Offsets cannot be used with passages.

{
  "result": {
    "match": "exact", "score": 1, "start": 0, "end": 31,
    "context": "Refunds are paid within 14 days of purchase, to the card that was used.",
    "occurrences": 1, "normalised_sha256": "…",
    "source": { "kind": "supplied", "text_sha256": "…", "code_points": 71 }
  },
  "injection_flags": [],
  "advice": "The supplied text contains this quote. This says nothing about where the text came from.",
  "receipt": "eyJ…",
  "tier": "trial"
}

The advice line is a reminder, not a verdict on the source. The check says what the text you sent says; it does not say where the text came from.

5. Keep the receipt

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

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. The receipt attests the hash of the text and the match. It does not show that the text came from any particular site.

With MCP

The same check is the MCP tool attestpage_verify_text_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:

{
  "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.

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:

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_text_quote",
    "arguments": { "text": "Refunds are paid within 14 days of purchase, to the card that was used.", "quote": "refunds are paid within 14 days", "match": "fuzzy" },
    "_meta": { "attestpage/trial": false } } }
EOF
{
  "scheme": "exact",
  "network": "eip155:8453",
  "amount": "5000"
}

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.005 per call with POST /v1/verify/text-quote. A check against a live web page costs more, because it fetches the page: Verify a quote.

Does an exact match mean the quoted claim is right?

No. It means those words are in the text you sent. Whether that text is right, or where it came from, is a separate question.

Is a call charged when it fails?

Only answers below 400 are charged. A request that fails validation, such as an unknown field, a missing quote, or offsets that are out of range, is never charged. Every case is in Payments and Errors.