How to check links before citing them
Last updated: 2026-10-10
Before citing a link, fetch it and confirm it returns real content, not a 404, a redirect to another site, a bot challenge or an empty page. POST /v1/check/links (US$0.005 per batch) checks up to 10 URLs at once and gives each one a status, redirect chain, final URL, content_kind verdict and hash, under one signed receipt.
What can go wrong with a link
- Dead. 404, 410, a 5xx error, or a host name that no longer resolves.
- Moved. The link redirects, sometimes to a different site or to a home page that has nothing to do with the citation. Each result lists the redirects and the final URL; a link that ends on another site is
off_site. - Not readable. The server answers 200 but sends a bot challenge, an empty JavaScript shell or a paywall. A status code alone calls these fine;
content_kinddoes not. - Invented. LLMs sometimes produce URLs that look right but never existed. These usually fail as dead links or dead domains.
Check a batch
{ "urls": ["https://www.iana.org/help/example-domains", "https://www.iana.org/domains/reserved"] }
Keep a citation when content_kind is real and the final URL is still the page you meant. Try the route free on demo data: [GET /v1/sample/links](https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/links). Details: Check links. Step by step with curl: How to check links before citing them, step by step with curl.
Pay per call with x402
No account or key is needed. A valid request with no payment gets 402 Payment Required with the price in a PAYMENT-REQUIRED header; an x402 v2 client signs that offer and sends the request again. With @x402/fetch (npm install @x402/fetch @x402/evm viem) and a Base Sepolia wallet made only for testing, holding test USDC:
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { privateKeyToAccount } from 'viem/accounts';
const client = new x402Client();
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.ATTESTPAGE_X402_KEY) });
const payingFetch = wrapFetchWithPayment(fetch, client);
const res = await payingFetch('https://vehcdj664efetfrsolne5umanq.srv.us/v1/check/links', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ urls: ['https://www.iana.org/help/example-domains', 'https://www.iana.org/domains/reserved'] }),
});
const answer = await res.json();
for (const link of answer.results) console.log(link.content_kind, link.http_status, link.final_url);
The wrapper signs the offer it gets. To check the price and network before signing, see One x402 payment step by step.
Check the words too
A live link can still be the wrong source. To check that the quoted words are on the page, use Verify a quote, or Verify citations for up to 10 quote and URL pairs in one call. See How to verify a quote an LLM cites.
Questions
Why not just send a HEAD request?
Many servers answer HEAD differently from GET, block unknown clients, or return 200 for pages that only hold a challenge. We fetch the page as a reader would and look at what came back.
Is a failed batch charged?
If no URL in the batch resolves, the call gets 422 host_not_found and is not charged. Bad requests are refused before payment. See Payments and Errors.
Does it follow robots.txt?
Yes. Our fetcher identifies itself and honours robots.txt and opt-outs; see Our fetcher.
Can I keep a record of the check?
Yes. The receipt covers every URL in the batch and can be checked offline: Verify receipts offline.