AttestPage · Docs · Pricing · OpenAPI · llms.txt

How to check links before citing them, step by step with curl

Last updated: 2026-10-10

To check that the links you are about to cite are live, send up to 10 of them to POST /v1/check/links (US$0.005 per batch). AttestPage fetches each one and returns its status, redirects, final URL, title and a content_kind verdict, under one signed receipt. Then check that each page says what you claim with POST /v1/verify/citations.

This guide runs both checks with curl and jq against https://vehcdj664efetfrsolne5umanq.srv.us, then shows the link check as an MCP tool call. For the kinds of broken link to look for, see How to check links before citing them.

What the check does

Price

US$0.005 per batch of 1 to 10 URLs, 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, and a batch where no host name resolves gets 422 and is not charged. All prices: pricing.json.

1. See an answer for free

The free sample runs the same route on two demo links, one live and one missing:

curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/links | jq '.results[] | {url, status: .http_status, kind: .content_kind, title}'
{
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page",
  "status": 200,
  "kind": "real",
  "title": "Regional climate report 2025 (demo page)"
}
{
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/missing",
  "status": 404,
  "kind": "http_error",
  "title": null
}

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/check/links -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.

Put the links you plan to cite in a file, exactly as they will appear in your answer:

cat > body.json <<'EOF'
{ "urls": ["https://www.iana.org/help/example-domains", "https://www.iana.org/domains/reserved"] }
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 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:

curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/check/links -H 'content-type: application/json' \
  -H 'quoteproof-trial: 1' -d @body.json > answer.json
jq '.results[] | {url, status: .http_status, kind: .content_kind, final_url, title}' answer.json

4. Read the answer

Read content_kind for each result, then compare final_url and title with what you meant to cite:

content_kindWhat it meansWhat to do
realThe page loaded as content.Keep it if final_url and title are the page you meant.
http_errorA 4xx or 5xx status, or a host name that does not resolve (http_status null).Drop the link or find where the page moved.
off_siteThe link redirected to another site. final_url says where; off_site_error: true means that site answered with an error.Cite the final page only if it is the same source.
bot_wall, js_shell, paywallThe site sent a challenge, an empty JavaScript shell or a login wall.The link may work for a person, but its content is unchecked.
pdfA live PDF on the same site. It is not read, so title is null.Check its text with verify/quote.
timeoutThe site did not answer in time.Try again later.

A link that redirects to a home page or a search page often still returns 200 and real. Check redirects and title, not the status alone.

5. Check that each page says what you claim

A live link can still be the wrong source. Send each URL with the words you attribute to it to POST /v1/verify/citations (US$0.10 for up to 10 pairs):

{ "citations": [ { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" } ] }

Each citation gets exact, fuzzy or none. If you paraphrased, add "match": "passages" beside citations to get the passages closest to your claim, as evidence to read. Details: Verify citations. For one quote at a time, see How to check a quote an LLM cites, step by step with curl.

6. Keep the receipt

The receipt field is a compact JWS covering every URL in the batch. 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.

With MCP

The same check is the MCP tool attestpage_check_links, 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_check_links",
    "arguments": { "urls": ["https://www.iana.org/help/example-domains", "https://www.iana.org/domains/reserved"] },
    "_meta": { "attestpage/trial": false } } }
EOF
{
  "scheme": "exact",
  "network": "eip155:84532",
  "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

US$0.005 per batch of up to 10 URLs with POST /v1/check/links. Checking the quoted words too costs US$0.10 per batch of up to 10 citations with POST /v1/verify/citations.

Yes, because reporting dead links is what the call does. If no host name in the batch resolves, the call gets 422 host_not_found and is not charged. Every case is in Payments and Errors.

No. It means the page loaded as content when our fetcher read it. Whether it says what you claim is the separate check in step 5.

Why not just send a HEAD request?

Many servers answer HEAD differently from GET, block unknown clients, or return 200 for a challenge page. We fetch each page as a reader would and look at what came back.