Skip to contents

retraction scans your manuscripts, bibliographies, and reference lists for citations to retracted papers, so you do not cite discredited work. It reads a wide range of document and bibliography formats, extracts and normalizes identifiers, checks them against retraction data, and returns a tidy, confidence-scored report.

The default data source is the Retraction Watch database, served through the XeraRetractionTracker API. Crossref and OpenAlex are available as additional, reconcilable sources.

Installation

# Development version from GitHub:
# install.packages("pak")
pak::pak("choxos/retraction")

Quick start

library(retraction)

# Check a manuscript, bibliography, or reference file.
result <- check_file("manuscript.Rmd")
result

# Check a vector of DOIs or PMIDs directly.
check_dois(c("10.1016/S0140-6736(97)11096-0", "10.1038/s41586-020-2649-2"))

# Check a data frame of references (columns are auto-detected).
check_refs(retraction_example)

A result is a tidy tibble, one row per reference, with the retraction status, an is_retracted flag, a match confidence, the retraction_date, days_since_retraction, the reason, and which sources confirmed it. Printing it gives a compact summary and lists the flagged citations.

What it reads

Two levels of parsing:

  • Structured (identifiers read from fields): BibTeX and BibLaTeX (.bib), CSL-JSON (.json), RIS (.ris), EndNote XML, and JATS XML.
  • DOI scraping (DOIs found in the text): Word (.docx), PDF (.pdf, needs the optional pdftools package), and text documents (.Rmd, .qmd, .tex, .md, .txt, .html). In these formats, references that do not carry a DOI in the text are not detected.

PubMed Central articles

Given a PMID, PMCID, DOI, title, or a whole reference, check_pmc() resolves it to a PubMed Central article, tells you whether the open-access full text is available, and if so checks that article’s reference list for retractions.

res <- check_pmc(c("PMC5334499", "10.1371/journal.pone.0000217", "29939664"))
pmc_articles(res)   # open-access status, reference count, and retracted count per input
retracted(res)      # the retracted references found

Multiple sources

Every check_*() function takes a sources argument. The default is "xera" (Retraction Watch). Also available: "crossref", "openalex", "europepmc", "ncbi" (PubMed), "datacite", and "preprint" (arXiv/bioRxiv withdrawals). You can query more than one, or "all", and have the results reconciled:

check_dois("10.1016/S0140-6736(97)11096-0",
           sources = c("xera", "europepmc", "ncbi"))

list_backends()          # available sources
check_dois("10.1126/science.aac4716", sources = "all")
options(retraction.sources = c("xera", "openalex"))   # set a session default

When several sources are queried, the highest-priority match sets the verdict, every confirming source is recorded (with dissenters in disagreeing), and a disagreement flag is raised when sources do not agree.

Two caveats worth knowing:

  • OpenAlex’s is_retracted flag is itself derived from Retraction Watch, so Xera and OpenAlex are not fully independent; a disagreement between them usually reflects OpenAlex lagging rather than a genuine conflict.
  • Crossref and OpenAlex detect retractions only. Corrections, expressions of concern, and reinstatements come from the Retraction Watch (xera) source.

Offline and private use

Offline mode is fully local: it does not contact the network. (An optional staleness notice is available with options(retraction.check_freshness = TRUE), which makes one small request during offline checks.)

For bulk checking, privacy (checking an unpublished manuscript sends only DOIs), or working without a connection, build a local snapshot once and match against it. Updates are incremental, adding new retractions rather than re-downloading everything.

retraction_sync()                       # download once
check_file("manuscript.Rmd", offline = TRUE)
retraction_sync()                       # later: incremental update

If your snapshot falls behind the live database, the package tells you when you run an offline check.

Reports

result <- check_file("manuscript.docx")
render_report(result, "report.html")    # self-contained HTML, no extra software
render_report(result, "report.md", format = "md")

How matching works

Within a source, matching runs a cascade: exact DOI, then PMID (matched directly against the Retraction Watch corpus, both online and offline, falling back to OpenAlex only to obtain a DOI for the other sources), then fuzzy title matching for references that carry no identifier. When several sources are selected they are all queried and their verdicts reconciled. Exact identifier matches are asserted with high confidence; fuzzy matches are reported as “possible” so you can verify them, and are never asserted as retracted. Fuzzy and PMID matching are most reliable in offline mode, where the full corpus is available locally.

The confidence column is a match-quality heuristic (exact identifier vs. fuzzy title, adjusted for year and author agreement), not a calibrated probability.

A citation of a retraction notice (rather than of the retracted work) is recognized and not flagged. A work that was retracted and later reinstated is reported as reinstated, not flagged.

Data sources and credits

Retraction data comes from the Retraction Watch database, made openly available through Crossref and served by XeraRetractionTracker. The optional Crossref and OpenAlex backends use their public APIs; OpenAlex’s is_retracted flag is itself derived from Retraction Watch. See the LICENSE.note file shipped with the package (and in the source repository) for the full data-provenance note covering every source.

Use of AI

Parts of retraction were developed with the assistance of AI coding tools (Anthropic’s Claude, via Claude Code), including code drafting, documentation, and test scaffolding. All AI-assisted output was reviewed, tested, and verified by the author, who is solely responsible for the package and its correctness.

License

GPL-3. See the LICENSE file.