# Citation & Link Verifier - Check AI Sources, Dead Links, Quotes (`bruco3/citation-verifier`) Actor

Verify the links and citations in any text (AI answers, articles, reports): does the URL exist, where does it really go, what is the page's real title, date and author, and is the quoted passage actually there? Finds dead links, soft-404s, mismatched quotes; suggests Wayback copies. Pay per URL.

- **URL**: https://apify.com/bruco3/citation-verifier.md
- **Developed by:** [Luca Pietrini](https://apify.com/bruco3) (community)
- **Categories:** AI, Agents, Developer 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 citation checkeds

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

## Citation & Link Verifier — check that the sources in a text really exist and say what is claimed

Paste an AI answer, an article draft, a research note or a report. Every link in it is **fetched live** and the Actor tells you, per URL:

- does it exist, and where does it really lead (404, soft-404, redirect chain, redirect to the homepage, blocked, unreachable);
- what is **actually** on the page — real title, canonical URL, publication date, author, site name, word count;
- if you provide the **quoted passage**, whether that text is really on the page;
- if you provide a title, author or date, whether they match;
- for dead links, the last **Internet Archive** snapshot so the reference can be repaired.

Language models are confident about sources they have never opened. This Actor is the independent check that turns "the AI cited something" into "the citation holds".

### Verdicts

| `verdict` | Meaning |
|---|---|
| `verified` | Reachable, and every claim you provided (quote / title / author / date) matches the page |
| `reachable` | Reachable HTML page; you gave no claim to check beyond the URL |
| `mismatch` | Reachable, but a claim fails: the quote is not on the page, the title is unrelated, the date is off |
| `unverified` | Reachable but not inspectable (PDF, binary, non-HTML) |
| `paywalled` | Reachable, the page shows paywall markers and the quote is not visible |
| `not-found` | HTTP 404/410, or a "page not found" page served with HTTP 200 (soft-404) |
| `redirected-home` | The URL now redirects to the site's homepage: the original page is gone |
| `blocked` | HTTP 401/403/429 — the site refuses automated access; check by hand or with a proxy |
| `error` | 5xx or an unexpected failure |
| `unreachable` | DNS, connection, SSL or timeout failure |

Each row carries `confidence` (0–1), `reasons` in plain English, the redirect chain, and the evidence used (`claims.quote.coverage`, `claims.title.similarity`, `claims.date.deltaDays`).

### Input

Three ways, combinable:

| Option | Use |
|---|---|
| **Text to verify** | Free text or Markdown; all `http(s)` links are extracted |
| **URLs to verify** | One per line |
| **Structured citations** | `[{ "label": "[1]", "url": "…", "quote": "…", "title": "…", "author": "…", "date": "2026-09-01" }]` — the fields you provide become claims to check |

Options: **Internet Archive lookup** for dead links (on), **date tolerance** (2 days), maximum URLs, concurrency (≤ 4 requests per host), optional proxy for publishers that block datacenter traffic.

### Output

```json
{
  "label": "[3]",
  "url": "https://peps.python.org/pep-0008/",
  "verdict": "verified",
  "ok": true,
  "confidence": 0.9,
  "reasons": ["reachable; all provided claims match the page"],
  "status": 200,
  "finalUrl": null,
  "redirects": 0,
  "page": {
    "title": "PEP 8 – Style Guide for Python Code | peps.python.org",
    "canonical": "https://peps.python.org/pep-0008/",
    "published": null,
    "author": null,
    "siteName": null,
    "wordCount": 7094,
    "isHtml": true
  },
  "claims": {
    "quote": { "checked": true, "found": true, "coverage": 1.0, "method": "exact" },
    "title": { "expected": "PEP 8 – Style Guide for Python Code", "actual": "PEP 8 – Style Guide for Python Code | peps.python.org", "similarity": 0.83 }
  },
  "archive": null,
  "checkedAt": "2026-09-21T07:10:00+00:00"
}
```

A `SUMMARY` record in the run's key-value store counts verdicts.

### How claims are checked

- **Quote** — exact match first; otherwise word 4-gram coverage of the quote in the page text (≥ 60% counts as found, tolerant to small edits and whitespace). Scripts, styles and navigation are excluded from the page text.
- **Title** — token Jaccard similarity between your title and the page's `<title>` / `h1`, stop words removed; below 0.3 is a mismatch.
- **Date** — compared with `article:published_time`, JSON-LD `datePublished` or common meta tags, normalised to UTC.
- **Author** — against `author` meta tags and JSON-LD `author.name`.
- **Soft-404** — HTTP 200 pages with fewer than 400 words whose title or text says "not found" (in several languages).

### Pricing

Pay per event: **one event per URL checked**, plus a smaller one per **Internet Archive lookup** made for a dead link. No subscription. Set a maximum charge on the run to cap the cost.

### Try it — three inputs

1. **An AI answer, pasted as text** — every link is extracted and checked; you learn which exist, which moved, which are dead.
2. **A bibliography with quotes** — structured citations `{url, quote, title, date}`; you learn whether each quoted passage is really on the page and whether title/date match.
3. **The links of an old article** — URLs one per line with *Internet Archive lookup* on; dead ones come back with an archived copy to relink.

Inside an agent conversation, the same check is the `verify_citations` tool of the [Web Hygiene MCP](https://apify.com/bruco3/web-hygiene-mcp) server (Claude Code plugin: `/plugin marketplace add Luca-003/web-hygiene-claude-plugin`).

### Use cases

- **AI output QA** — verify every source an assistant cited before publishing or acting on it.
- **Editorial** — check the links in an article or newsletter before it goes out; get archive links for the dead ones.
- **Research and legal** — confirm that quoted passages appear in the cited documents.
- **Link rot audits** — pass the bibliography of an old page and see what still holds.
- **Agents** — call it as a tool (also available inside the [Web Hygiene MCP](https://apify.com/bruco3/web-hygiene-mcp) server as `verify_citations`).

### Limits

- JavaScript-rendered pages are read as served (no browser). PDFs are reported as reachable but not inspected.
- Bodies are read up to 1.5 MB.
- Sites that block automated access return `blocked`; a residential proxy usually helps.

### Legal

Public pages are fetched with a clear `User-Agent`, once, read-only. No personal data is collected or stored.

### Related

- **[Web Hygiene MCP](https://apify.com/bruco3/web-hygiene-mcp)** — this check and five other live web tools for AI agents.
- **[Broken Link Checker](https://apify.com/bruco3/broken-link-checker)** — every URL of a whole site, with new/fixed tracking.

### Feedback

A page misread, or a check you need? Open an issue on the **Issues** tab. If it saved you time, a review helps others find it.

### Changelog

- **0.1** — initial release.

# Actor input Schema

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

Paste the text (an AI answer, an article draft, a report). Every http(s) link in it is extracted and checked live.

## `urls` (type: `array`):

One URL per line, or upload a text file.

## `citations` (type: `array`):

JSON objects with <code>url</code> and optionally <code>quote</code> (the passage claimed to be on the page), <code>title</code>, <code>author</code>, <code>date</code> and a <code>label</code> echoed back. Each claim is checked against the real page.

## `checkArchive` (type: `boolean`):

For unreachable, missing or redirected-away pages, find the last Wayback Machine snapshot so the reference can be repaired.

## `dateToleranceDays` (type: `integer`):

A claimed publication date within this many days of the page's real date counts as matching.

## `maxUrls` (type: `integer`):

<code>0</code> = no limit.

## `concurrency` (type: `integer`):

Total concurrent requests (at most 4 per host).

## `userAgent` (type: `string`):

Sent with every request.

## `proxyConfiguration` (type: `object`):

Optional. Some publishers block datacenter traffic; a residential proxy can help.

## Actor input object example

```json
{
  "text": "According to the Apify docs (https://docs.apify.com/platform/actors), Actors are serverless programs. See also https://docs.apify.com/this-page-does-not-exist and https://blog.apify.com/.",
  "citations": [
    {
      "label": "[1]",
      "url": "https://docs.apify.com/platform/actors",
      "quote": "Actors are serverless programs",
      "title": "Actors"
    }
  ],
  "checkArchive": true,
  "dateToleranceDays": 2,
  "maxUrls": 0,
  "concurrency": 10,
  "userAgent": "CitationVerifier/1.0 (+https://apify.com/bruco3/citation-verifier)",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `verdicts` (type: `string`):

No description

## `summary` (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 = {
    "text": "According to the Apify docs (https://docs.apify.com/platform/actors), Actors are serverless programs. See also https://docs.apify.com/this-page-does-not-exist and https://blog.apify.com/.",
    "citations": [
        {
            "label": "[1]",
            "url": "https://docs.apify.com/platform/actors",
            "quote": "Actors are serverless programs",
            "title": "Actors"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bruco3/citation-verifier").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 = {
    "text": "According to the Apify docs (https://docs.apify.com/platform/actors), Actors are serverless programs. See also https://docs.apify.com/this-page-does-not-exist and https://blog.apify.com/.",
    "citations": [{
            "label": "[1]",
            "url": "https://docs.apify.com/platform/actors",
            "quote": "Actors are serverless programs",
            "title": "Actors",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("bruco3/citation-verifier").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 '{
  "text": "According to the Apify docs (https://docs.apify.com/platform/actors), Actors are serverless programs. See also https://docs.apify.com/this-page-does-not-exist and https://blog.apify.com/.",
  "citations": [
    {
      "label": "[1]",
      "url": "https://docs.apify.com/platform/actors",
      "quote": "Actors are serverless programs",
      "title": "Actors"
    }
  ]
}' |
apify call bruco3/citation-verifier --silent --output-dataset

```

## MCP server setup

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

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/IeL3shVA2b5dPtbrv/builds/rgcBirPQO2GVSYB1o/openapi.json
