# Link and Citation Checker for AI Answers (`landlordtools/link-citation-checker`) Actor

Check every link in an AI answer, report or list: does it resolve, HTTP status, redirects, page title, soft 404s, and whether the quoted text really appears on the page (HTML and PDF), with the matching passage. Catches made-up links and misquotes.

- **URL**: https://apify.com/landlordtools/link-citation-checker.md
- **Developed by:** [LandlordTools](https://apify.com/landlordtools) (community)
- **Categories:** AI, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 links

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Link and Citation Checker for AI Answers

AI answers and reports cite links that don't exist, and quote pages that say something else. This Actor checks every link: does it resolve, where does it end up, what is the page called, and is the quoted text really on it? When a quote is wrong, you get the passage the page actually contains.

### What it checks

- **Status and redirects**:
  - Each URL is fetched with GET and redirects are followed one by one, so you see the whole chain (relative `Location` headers are resolved as RFC 9110 says [1]).
  - Up to 10 redirects are followed.
- **Verdicts**:
  - `ok`;
  - `broken`: 404 or 410 [1], a domain that doesn't exist, a page that says it wasn't found while returning 200 (a "soft 404" [4]), or a deep link redirected to the home page;
  - `blocked`: 401, 403, 429 [2], 451 [3] or a bot challenge. The site refused an automated check, which doesn't mean the link is wrong;
  - `error`: timeouts, 5xx and connection problems, which may be temporary;
  - `invalid`.
- **Quotes** (HTML, PDF and plain text). Both sides are normalised first: Unicode NFKC [5], curly quotes and dashes, soft hyphens, spacing and case.
  - `exact`, or `words` (same words in the same order, punctuation aside), means the quote is there.
  - `with-omissions`: the quote uses `...` or `…` and every part is on the page, in order.
  - `close` and `partial` mean a similar passage exists but the words differ. The verdict is `quote-differs`, with a `similarity` score and the real `passage`. This is how changed numbers and dates show up.
  - `none` means nothing like it is on the page (`quote-not-found`).
- **PDFs** are read with Mozilla's PDF.js [6], including the document title.
- **Text input**: paste an AI answer or Markdown. Links in Markdown, HTML and bare URLs are found, and trailing punctuation is trimmed.

**Checking**:

- Unit tests replay real GOV.UK responses recorded on 3 October 2026, plus generated PDFs and edge cases.
- `tests/check_live.py` checks live URLs against independent tools: curl for status and final URL, and Python's HTML parser or poppler's `pdftotext` for quotes. All 8 agreed on 3 October 2026.

### Input

```json
{
  "links": [
    {
      "url": "https://www.gov.uk/vat-rates",
      "quote": "The reduced rate of VAT is 8%, for example on children's car seats and home energy.",
      "label": "VAT rates"
    },
    {
      "url": "https://gov.uk/vat-rates",
      "quote": "The standard rate of VAT increased to 20% on 4 January 2011 (from 17.5%).",
      "label": "VAT rates"
    },
    {
      "url": "https://www.gov.uk/guidance/rates-of-vat-on-different-goods-and-services-2031",
      "label": "Rates of VAT on different goods and services"
    }
  ],
  "timeoutSecs": 20
}
```

### Example output

One dataset row per link. Here the quoted 8% rate is wrong, and the passage shows the page says 5%:

```json
{
  "url": "https://www.gov.uk/vat-rates",
  "label": "VAT rates",
  "verdict": "quote-differs",
  "status": 200,
  "finalUrl": "https://www.gov.uk/vat-rates",
  "redirects": 0,
  "redirectChain": [],
  "title": "VAT rates - GOV.UK",
  "contentType": "text/html",
  "quote": "The reduced rate of VAT is 8%, for example on children's car seats and home energy.",
  "quoteFound": false,
  "quoteMatch": "partial",
  "similarity": 0.59,
  "passage": "…of VAT What the rate applies to Standard rate 20% Most goods and services Reduced rate 5% Some goods and services, for example, children’s car seats and home energy Zero rate 0% Zero-rated goods and services,…",
  "note": null,
  "error": null
}
```

The `OUTPUT` record adds a count per verdict, and what each verdict means.

### Integrations

Ready-made files in [`integrations/`](integrations/README.md): a **Microsoft Power Platform custom connector**, **OpenAPI 3** (AI agents, n8n, Postman), an **Excel / Power BI Power Query** script, a **Google Sheets** function, and curl, Python and JavaScript examples.

### Pricing

Pay per event: $2 per 1,000 results ($0.002 each), plus Apify's minimal start fee. A result is one row in the dataset: one link checked. The run stops at your spending limit.

### Sources

1. [RFC 9110 HTTP Semantics: 15.4 Redirection 3xx, 10.2.2 Location (may be relative), 15.5.5 404 Not Found, 15.5.11 410 Gone](https://www.rfc-editor.org/rfc/rfc9110.html). Accessed 3 October 2026.
2. [RFC 6585: 4. 429 Too Many Requests](https://www.rfc-editor.org/rfc/rfc6585.html). Accessed 3 October 2026.
3. [RFC 7725: 3. 451 Unavailable For Legal Reasons](https://www.rfc-editor.org/rfc/rfc7725.html). Accessed 3 October 2026.
4. [Google Search Central: HTTP status codes, and soft 404 errors (an error message served with 200)](https://developers.google.com/search/docs/crawling-indexing/http-network-errors). Accessed 3 October 2026.
5. [Unicode Standard Annex #15: Normalization Forms (NFKC, used before comparing quotes)](https://unicode.org/reports/tr15/). Accessed 3 October 2026.
6. [pdfjs-dist (Mozilla PDF.js, Apache-2.0): text from PDFs](https://www.npmjs.com/package/pdfjs-dist). Accessed 3 October 2026.

[1]: https://www.rfc-editor.org/rfc/rfc9110.html

[2]: https://www.rfc-editor.org/rfc/rfc6585.html

[3]: https://www.rfc-editor.org/rfc/rfc7725.html

[4]: https://developers.google.com/search/docs/crawling-indexing/http-network-errors

[5]: https://unicode.org/reports/tr15/

[6]: https://www.npmjs.com/package/pdfjs-dist

# Actor input Schema

## `links` (type: `array`):

URLs, or objects {"url", "quote", "label"}. With a quote, the page is checked for that text.

## `text` (type: `string`):

An AI answer, report or Markdown: every link in it is checked.

## `timeoutSecs` (type: `integer`):

Default 20.

## `maxConcurrency` (type: `integer`):

Default 5.

## `maxPageMB` (type: `integer`):

Default 10. Larger HTML is read in part; larger PDFs aren't read.

## Actor input object example

```json
{
  "links": [
    {
      "url": "https://www.gov.uk/vat-rates",
      "quote": "The reduced rate of VAT is 8%, for example on children's car seats and home energy.",
      "label": "VAT rates"
    },
    {
      "url": "https://gov.uk/vat-rates",
      "quote": "The standard rate of VAT increased to 20% on 4 January 2011 (from 17.5%).",
      "label": "VAT rates"
    },
    {
      "url": "https://www.gov.uk/guidance/rates-of-vat-on-different-goods-and-services-2031",
      "label": "Rates of VAT on different goods and services"
    }
  ],
  "timeoutSecs": 20
}
```

# Actor output Schema

## `result` (type: `string`):

No description

## `output` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "links": [
        {
            "url": "https://www.gov.uk/vat-rates",
            "quote": "The reduced rate of VAT is 8%, for example on children's car seats and home energy.",
            "label": "VAT rates"
        },
        {
            "url": "https://gov.uk/vat-rates",
            "quote": "The standard rate of VAT increased to 20% on 4 January 2011 (from 17.5%).",
            "label": "VAT rates"
        },
        {
            "url": "https://www.gov.uk/guidance/rates-of-vat-on-different-goods-and-services-2031",
            "label": "Rates of VAT on different goods and services"
        }
    ],
    "timeoutSecs": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("landlordtools/link-citation-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "links": [
        {
            "url": "https://www.gov.uk/vat-rates",
            "quote": "The reduced rate of VAT is 8%, for example on children's car seats and home energy.",
            "label": "VAT rates",
        },
        {
            "url": "https://gov.uk/vat-rates",
            "quote": "The standard rate of VAT increased to 20% on 4 January 2011 (from 17.5%).",
            "label": "VAT rates",
        },
        {
            "url": "https://www.gov.uk/guidance/rates-of-vat-on-different-goods-and-services-2031",
            "label": "Rates of VAT on different goods and services",
        },
    ],
    "timeoutSecs": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("landlordtools/link-citation-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "links": [
    {
      "url": "https://www.gov.uk/vat-rates",
      "quote": "The reduced rate of VAT is 8%, for example on children'\''s car seats and home energy.",
      "label": "VAT rates"
    },
    {
      "url": "https://gov.uk/vat-rates",
      "quote": "The standard rate of VAT increased to 20% on 4 January 2011 (from 17.5%).",
      "label": "VAT rates"
    },
    {
      "url": "https://www.gov.uk/guidance/rates-of-vat-on-different-goods-and-services-2031",
      "label": "Rates of VAT on different goods and services"
    }
  ],
  "timeoutSecs": 20
}' |
apify call landlordtools/link-citation-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,landlordtools/link-citation-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/YqHiLywccfiU4UEVk/builds/o6VBc4lUEXyVjN0UY/openapi.json
