citeverify
Check whether the references in a bibliography actually exist.
Fabricated citations are now a routine failure mode. LLM-drafted reference lists
invent plausible-looking papers; copy-paste drift corrupts real ones. citeverify
cross-checks every reference against OpenAlex, Crossref and arXiv, and
tells you what it could and could not confirm.
pip install citeverify
citeverify check references.txt
Checked 6 references
----------------------------------------------------
verified 4 ( 66.7%)
year mismatch 1 ( 16.7%)
NOT FOUND 1 ( 16.7%)
2 reference(s) need attention:
[NOT FOUND] Nonexistent, A. (2023). Quantum entanglement in transformer attention heads.
closest: Transformer quantum state: A multipurpose model for quantum many-body problems (2023)
[year mismatch] Smith, J. (1897). Attention is all you need. NeurIPS.
closest: Attention Is All You Need (2017)
Why the verdicts are split the way they are
Most of the design effort went into not crying wolf. A tool whose output reads as an integrity signal has to be careful about what it asserts.
| Verdict | Meaning |
|---|---|
verified |
A record matching title, authors and year was found. |
year_mismatch |
The work is real; the year disagrees. This is a typo. |
author_mismatch |
The title matches a real work but the authors don't. Often a merged or mis-copied reference. |
partial |
Weak similarity only. Needs a human. |
unverified |
Nothing plausible found in any index. |
error |
A lookup failed. Not a finding about the reference. |
Three rules follow from this, and they are enforced by tests:
- A transport failure is never a verdict. If every index errors you get
error, neverunverified. Reporting a real paper as missing because DNS failed is the worst thing this tool could do. - Missing author data never counts against a reference. Standards, datasets and technical reports routinely list no authors. Absence of evidence is not evidence of fabrication.
- Wrong metadata is distinguished from invention. A real paper cited with a bad year resolves to the right work and is reported as a year error. A typo and a hallucination need different fixes.
Usage
CLI
citeverify check refs.txt # human-readable report
citeverify check refs.txt --json out.json # machine-readable results
citeverify check refs.txt --cache .cv.json # reruns become free
citeverify check refs.txt --strict # exit 1 if anything suspicious (CI)
citeverify parse refs.txt # parse only, no network
citeverify check - < refs.txt # read stdin
Give --mailto you@example.org to enter the OpenAlex/Crossref polite pool, which
gets you faster and more reliable service. Thresholds are tunable:
--verified-threshold, --partial-threshold, --year-tolerance.
Exit codes: 0 ran cleanly · 1 suspicious references found under --strict
(error results never trigger this) · 2 usage or input error.
Python
from citeverify import parse_references, verify_all, summarize
refs = parse_references(open("references.txt").read())
results = verify_all(refs, cache=Cache(".cv.json"))
print(summarize(results)) # {'verified': 4, 'unverified': 1, ...}
for r in results:
if r.suspicious:
print(r.status, "--", r.citation["raw"])
Already have structured data? Skip the parser. verify() and verify_all()
accept plain dicts with title / authors / year / doi / arxiv_id.
Air-gapped and restricted networks
All network access goes through a single injectable callable. Nothing else in the package performs I/O:
from citeverify import verify, OpenAlexSource
def my_fetcher(url, headers):
return my_corporate_proxy.get(url, headers=headers).text
verify(citation, sources=[OpenAlexSource(fetcher=my_fetcher)])
Point it at an on-prem OpenAlex mirror, a proxy, or a cached snapshot. The entire test suite runs through this seam, so it is a supported path and not an afterthought.
Install
pip install citeverify
Zero hard dependencies. Standard library only. This is deliberate: the people who most need citation checking (institutional review offices, air-gapped labs) are the least able to approve a dependency tree.
Requires Python 3.9+.
Limits, and please read them
- The parser is heuristic. Reference lists are a hundred house styles wearing
a trenchcoat. Entries parsed by segmentation are marked
parse_confidence: "low"and flagged innotes. Feed structured data when you have it. - Matching is lexical. Title similarity is token overlap with a substring escape hatch. It does not understand meaning.
- Coverage gaps are real. Books, theses, standards, and much non-English and
pre-1970 work are thinly indexed.
unverifiedon those usually means the work is not indexed. It does not mean the work is fake. - It does not judge whether a citation is appropriate, or whether the cited work supports the claim attached to it. It answers exactly one question: does a record matching this reference exist?
Treat the output as a triage queue for a human, never as a verdict.
Provenance
The matching logic was developed and hardened while verifying a 1,473-reference corpus across ~101 papers. Every one was driven to a resolved status. This is extracted from tooling that had to work at that scale.
Development
pip install -e ".[dev]"
pytest # 76 tests, all offline. No network required.
Licence
MIT.
Metadata
Release files for citeverify 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| citeverify-0.1.0.tar.gz | 25.6 kB | Without provenance | Details for citeverify-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| citeverify-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for citeverify-0.1.0-py3-none-any.whl |
Total release size: 46.1 kB
Release files / citeverify-0.1.0.tar.gz
| Download URL | citeverify-0.1.0.tar.gz |
|---|---|
| Size | 25.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e227123a5180e42513ad10f12b4bebf6fffc992ee290aecc89e808e1ad3528ea
|
|
BLAKE2b-256 checksum How to use checksums |
87eeb673d1821cc87c3886f93d7c516bc356d66ea8abd50ff60455bb3fbc1226
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / citeverify-0.1.0-py3-none-any.whl
| Download URL | citeverify-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
567e1226f08e459141dd9294ee549bf10df58676625121f39890eb0af2cda22c
|
|
BLAKE2b-256 checksum How to use checksums |
e58f2e7be79ac5fee31b974a4d102d650cea28be1a58398efb964247f2c19289
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for citeverify 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.