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
- Fetches each link. It follows up to 5 redirects and reads the page as our fetcher receives it at that moment.
- Judges each page.
content_kindisrealwhen the page loaded as content, orhttp_error,off_site,bot_wall,js_shell,paywall,timeoutand others when it did not. - Reports what it found. Each result has
http_status, theredirectsit followed,final_url, the pagetitleandcontent_sha256, a hash of the page text. - Signs a receipt. One Ed25519 receipt covers every URL in the batch. Anyone can check it later against our published keys.
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.
3. Check the links
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_kind | What it means | What to do |
|---|---|---|
real | The page loaded as content. | Keep it if final_url and title are the page you meant. |
http_error | A 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_site | The 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, paywall | The site sent a challenge, an empty JavaScript shell or a login wall. | The link may work for a person, but its content is unchecked. |
pdf | A live PDF on the same site. It is not read, so title is null. | Check its text with verify/quote. |
timeout | The 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
How much does it cost to check a list of links?
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.
Is a batch charged if some links are dead?
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.
Does a real result mean the link supports my claim?
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.