# 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_kind` does not.
- **Invented.** LLMs sometimes produce URLs that look right but never existed. These usually fail as dead links or dead domains.

## Check a batch

```json
{ "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](/docs/check-links). Step by step with `curl`: [How to check links before citing them, step by step with curl](/guides/check-links-before-citing).

## 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:

```js
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](/docs/examples/fetch-x402).

## 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](/docs/verify-quote), or [Verify citations](/docs/verify-citations) for up to 10 quote and URL pairs in one call. See [How to verify a quote an LLM cites](/docs/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](/docs/payments) and [Errors](/docs/errors).

### Does it follow robots.txt?

Yes. Our fetcher identifies itself and honours robots.txt and opt-outs; see [Our fetcher](/bot).

### 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](/docs/verify-offline).
