An agent that cites a retracted paper does not announce it. It writes a confident paragraph with a DOI at the end, and the DOI happens to be the 1998 Wakefield paper or the 2020 hydroxychloroquine preprint-era trial — both retracted, both still cited by thousands of works.

The information to catch that exists, but it is fragmented across three places that do not agree with each other:

  • Crossref records an update relation (update-to) when a publisher issues a retraction notice.
  • Retraction Watch maintains the notice-level record, with dates.
  • OpenAlex carries an index-derived retraction flag on the work itself.

Any one of them alone gives false negatives. So we merged all three into one graph, published it under CC0, and put a free lookup in front of it. This post is the receipt: what the data contains, what the endpoint actually returns, and the numbers from a benchmark you can rerun.

What is published

The dataset is ashesborn6/integrity-citation-retraction-graph on Hugging Face, snapshot ri-20261025-1526:

  • 98,868 papers — OpenAlex work IDs, DOI, title, year.
  • 144,814 citation edges — citing_id → cited_id, with the source that supplied the edge.
  • 572 retraction status rows — one row per (paper, source) assertion, each carrying a source tier.

That last design decision is the whole point. A retraction is not one boolean from one authority; it is a set of assertions that can disagree, and the schema keeps them separate instead of collapsing them into a single is_retracted column a downstream reader cannot audit. Tier A means a registry that records retractions directly (Crossref update-to, Retraction Watch); tier C means an index-derived flag (OpenAlex).

Reason and nature free text from Retraction Watch is deliberately excluded. A retraction is a bibliographic event; carrying the publisher's prose about it turns a lookup into an accusation.

What the call returns

text
GET https://pay.ashesborn.cloud/api/v1/judge/paper?doi=10.1016/S0140-6736(97)11096-0
json
{
  "paper": { "paper_id": "W2117847125", "doi": "10.1016/s0140-6736(97)11096-0", "year": 1998, "source_tier": "C" },
  "statuses": [
    { "source": "openalex",         "source_tier": "C", "is_retracted": true, "retraction_date": null },
    { "source": "retraction_watch", "source_tier": "A", "is_retracted": true, "retraction_date": "2010-02-06" }
  ]
}

No key, no account, access-control-allow-origin: *, so it works from a browser, a script, or a page like the interactive lookup. Nothing is generated by a model — the same DOI against the same snapshot returns the same bytes, which is the property that makes it usable inside an agent pipeline rather than beside one.

The benchmark, and what it says

Twenty DOIs, one GET each, sequential, no retries: 20/20 HTTP 200, median 1,595 ms, slowest 5,318 ms (a cold start after idle), 16 retractions each confirmed by three sources. Full table, method and the shell loop to reproduce it are on the benchmark page.

The interesting part is not the latency. It is the three answer shapes that a naive is_retracted ? "retracted" : "clean" check gets wrong:

  1. Abstained. A DOI outside the snapshot returns abstention_reason: "paper_not_in_snapshot" and an empty status list. It declines to judge. Reporting that as “clean” is how a checker produces false assurance.
  2. No status rows. The paper is in the graph, no source asserts anything about it. Also not “clean” — it is absence of evidence, and it is a different verdict in our response than an abstention.
  3. Source disagreement. Tier A and tier C rows can conflict. A judgment that silently picks the tier-A row hides the conflict; ours reports it.

The graph, rendered

The published subset is a neighbourhood sample: 12 retracted seeds, the works that cite them, and those works' reference lists. In that sample, 781 distinct papers cite at least one retracted seed, across 788 edges — and 6 papers cite two or more of them. Those six are why this is a graph problem and not a lookup problem: a claim resting on two independently retracted foundations is a different risk from a claim resting on one, and you only see it by walking edges.

You can hover the actual rendering on the dataset walkthrough — 1,039 nodes, drawn client-side from a 115 KB payload with no charting library.

Free versus paid

Being explicit about the line, because it is the difference between a tool and a teaser:

  • Free: paper-level retraction status per DOI, unlimited-ish, no key. This is the part that answers “is this paper retracted?”
  • Paid, $0.02: retraction propagation — given a paper, does its claim base include retracted work, how deep, with what per-source agreement and conflict flags.
  • Paid, $0.10: the full evidence audit — the path, the tiers, the deterministic explanation.
  • Data: CC0-1.0. Download it and build your own thing; a link back is appreciated, not required.

The paid calls go through x402 on Base, which means a program can pay per call without a subscription, an API key, or a sales conversation. The judgment engine is deterministic: 0 LLM tokens per call.

Honest limits

  • The public graph is a neighbourhood sample, not the scholarly record. 12 seeds, up to 60 citing works each.
  • Retraction dates come from whichever source recorded them; null means "this source did not date it", not "unknown retraction".
  • 572 status rows is small. It is a deliberate starting subset, and the snapshot ID travels with every response so you can tell when an answer ages.
  • The MCP server at mcp.ashesborn.cloud/mcp answers initialize and tools/list anonymously and exposes one free read-only tool, integrity_paper_status — a client can call it with no key. The propagation ($0.02) and evidence-audit ($0.10) tools answer payment_required to an anonymous session: they are the paid product, and the free tool is deliberately not a copy of them. Indexing and the corpus-query tools still need a credential.

Try one

Paste any DOI you already distrust into the lookup. If the response is useful to you, the same shape is available programmatically, and the data behind it is a single curl away:

bash
curl -sL https://huggingface.co/datasets/ashesborn6/integrity-citation-retraction-graph/resolve/main/data/retraction_status.jsonl.gz | gunzip | head