AttestPage · Docs · Pricing · OpenAPI · llms.txt

How to check the citations at the end of an answer before you send it, step by step with curl

Last updated: 2026-10-13

To check up to 10 citations in one call, send each quote and the URL it is attributed to to POST /v1/verify/citations (US$0.10 per batch). AttestPage fetches each distinct page once, looks for each quote on its page, says whether the page loaded as real content, and returns one signed receipt for the whole batch.

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

What the check does

Price

US$0.10 per batch of up to 10 citations, 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. A batch where no host name resolves is a 422 and is not charged either. A dead domain in a batch that has other live pages is a result, and the batch is charged. For one to nine citations, one verify/quote call per pair costs less. All prices: pricing.json.

1. See an answer for free

The free sample checks three citations on the demo site: one close match, one quote that is not on the page, and one page that does not exist.

curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/citations | jq '.results[] | {url, match, score, kind: .content_kind}'
{
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page",
  "match": "fuzzy",
  "score": 0.963,
  "kind": "real"
}
{
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page",
  "match": "none",
  "score": null,
  "kind": "real"
}
{
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/missing",
  "match": "none",
  "score": null,
  "kind": "http_error"
}

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:

curl -si -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/verify/citations -H 'content-type: application/json' \
  -d '{ "citations": [ { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" }, { "url": "https://www.iana.org/domains/reserved", "quote": "IANA-managed Reserved Domains" } ] }' \
  | 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 citations

Send citations, a list of 1 to 10 objects with a url and a quote (at most 1,000 characters after normalisation). Optional fields: match (fuzzy by default, exact or passages), case_sensitive and threshold. This body checks two pages on iana.org:

cat > body.json <<'EOF'
{ "citations": [
  { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" },
  { "url": "https://www.iana.org/domains/reserved", "quote": "IANA-managed Reserved Domains" }
] }
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 shows this with plain fetch, and the MCP server does it for you. Save the answer as answer.json.

4. Read the answer

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

results has one entry per citation, in request order. summary counts exact, fuzzy and none verdicts, and failed for citations that carry an error instead of a verdict.

matchcontent_kindWhat it means
exact or fuzzyrealThe quote, or a close version of it, is on the page. start, end and context show where.
nonerealThe page loaded and the quote is not on it. Check the quote before you cite it.
noneanything elseWe could not read the page (404, bot wall, JavaScript shell), so we cannot say whether the quote is on it. Read advice.

A citation whose quote is too costly to match fuzzily gets an error with code match_too_costly on that citation only; the rest are still checked. Use "match": "exact" or a shorter quote.

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

With MCP

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

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_citations",
    "arguments": { "citations": [
      { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" },
      { "url": "https://www.iana.org/domains/reserved", "quote": "IANA-managed Reserved Domains" }
    ] },
    "_meta": { "attestpage/trial": false } } }
EOF
{
  "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.

Questions

How much does it cost to check citations?

US$0.10 per batch of up to 10 citations. Ten citations in one call cost the same as one.

Is a batch charged if a page is dead?

Yes, when other pages in the batch resolve: a page that is gone or behind a bot wall is a result, and the batch is charged. If no host name in the batch resolves, the call is a 422 and is not charged. If a page turns out to be disallowed by robots.txt after payment, the whole batch is answered with an error and not charged.

Does none mean the quote is not on the page?

Only when 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.