Find a legally free copy of a paper. Then find everything around it.
Two things researchers do constantly, and no single tool did both:
- A paper is behind a paywall. Somewhere there is usually a legal free copy — in PubMed Central, an institutional repository, a preprint server, or the publisher's own open archive. Finding it by hand means checking five sites.
- One good paper turns up. The papers it cites, the papers citing it, and the papers like it are exactly the reading list you want — and assembling that by hand is an afternoon.
openpaper does both from one command, and downloads what is openly available.
export OPENPAPER_EMAIL=you@example.com
# where can I legally read this?
python3 scripts/openpaper.py get 10.1136/bmj.n71
# build the reading list around a paper I like
python3 scripts/openpaper.py snowball 10.1161/CIR.0000000000001184 \
--year-from 2020 --min-links 2 --out ./lit
# download everything that is open
python3 scripts/openpaper.py fetch --from ./lit/cluster-*.json --out ./lit/pdfOut come .csv (opens in Excel), .bib (imports into Zotero, EndNote, Mendeley),
.json (full records), and a folder of PDFs named year-author-title.pdf.
The pieces all existed separately. Citation-mapping tools (Connected Papers, Research Rabbit, Litmaps, Inciteful) draw you a graph but never fetch a PDF. PDF finders (the Unpaywall extension, Zotero's Find Full Text, LibKey) work one paper at a time in a browser and know nothing about citation structure. Nothing joined "find the neighbours" to "and get me the text of all of them."
openpaper will not use Sci-Hub, LibGen, Sci-Net or any mirror, and contains no
paywall bypass. This is a design decision, not a limitation to work around — and
for current literature it costs you very little:
- Sci-Hub stopped taking in new articles around December 2020 – February 2021; its reported archive size was identical (88,343,822 items) in December 2021 and July 2022. For anything published from 2022 onward there is no coverage measurement at all.
- Meanwhile the legal share kept climbing. Of the randomised trials cited in Cochrane reviews, only 20.7% of those published 2020–2023 remained behind a paywall, against 60.0% for 1980–1999 (Learned Publishing 2026).
So for recent work — new guidelines, this year's trials — the legal route is not the compromise. It is the one that has the paper.
There is also an institutional risk that rarely gets mentioned: publishers police their licences at the organisation level. IEEE suspended off-campus access for the whole University of Central Arkansas after a compromised account was used to harvest content; the library had to disable the account before access was restored (UCA Library).
When a paper genuinely is closed, openpaper prints the legal ways to get it —
institutional access, Research4Life/HINARI, interlibrary loan, or a reprint request
it will draft for you.
All free, all public, none requiring an API key as of 2026-08-15:
| Source | Used for |
|---|---|
| OpenAlex | metadata, references, citations, related work, OA locations |
| Unpaywall | open-access locations per DOI |
| Europe PMC | full text (PDF and JATS XML) for the PMC open subset |
| Crossref | canonical bibliographic records |
Nothing is hidden. A candidate paper is ordered by:
seed_links— how many of your seed papers it connects to. Linked to three of your seeds beats linked to one, however famous it is. This is the Wohlin snowballing idea: relevance is a property of the local network.cites_per_year— citations divided by age, so a strong 2024 paper is not buried under a 2009 classic.
Neither number judges quality. They order the reading queue; you still read.
Preprints, conference abstracts and dissertations are excluded unless you ask for
them. Citing a preprint as though it were a peer-reviewed paper is a real error in
clinical writing, not a formatting slip — so the safe behaviour is the default one.
The engine says how many it set aside every run; --include-preprints brings them
back.
Detection catches both the obvious case (type: preprint) and the sneaky one — a
record labelled article whose host is medRxiv, bioRxiv, SSRN or Research Square.
- Retracted articles — cross-checked, never taken on one source's word. OpenAlex alone flags the 2020 Lancet Commission on dementia as retracted while Europe PMC and Crossref record no such notice, so a single flag is reported as unverified and only agreement between sources is reported as fact.
- Preprints — if you opt them in, routes stay tagged
preprint: submitted version, not peer reviewed, possibly different from what was published. - Author manuscripts — accepted versions whose pagination will not match the published record. Cite the DOI.
- Licence — recorded per download in
_fetch-log.json.
Needs Python 3.8+. Nothing else — no pip install.
As a Claude Code plugin:
/plugin marketplace add ChienNguyen23/openpaper
/plugin install openpaper
Or just run the script directly; it is self-contained.
| Flag | Does |
|---|---|
--min-links 2 |
keep only work connected to ≥2 seeds — the single best noise filter |
--oa-only |
only papers with a free copy |
--year-from 2020 |
recent literature only |
--no-forward / --no-backward |
references only / citing papers only |
--include-preprints |
bring back preprints/abstracts/theses (excluded by default) |
--per-seed 300 |
dig deeper per seed |
Responses are cached for a week under ~/.cache/openpaper, so re-running a query
costs nothing.
scripts/litsearch.py builds a reference set you can defend in front of a
committee — protocol declared before searching, five search lanes across two
databases, age limits by citation role (an original 1978 instrument paper is
kept; a 2015 prevalence figure is flagged), four-tier PICO relevance grading,
seven post-checks, and a PRISMA-S paragraph for the methods chapter.
L="python3 scripts/litsearch.py"
$L plan --topic "..." --purpose review # write a protocol, then fill it in
$L search protocol.json # five lanes -> run.json + screening sheet
$L apply run.json --decisions decisions.json
$L gate run-screened.json # seven checks -> READY / NOT-READY
$L prisma run-screened.json # methods-chapter declaration
$L audit thesis.docx # grade an EXISTING reference list
$L verify thesis.docx --quantitative-only # pair cited sentences with abstractstests/test_core.py holds 52 regression cases — every one a bug actually found
or a rule a committee actually enforces. benchmarks/ holds a 295-reference
gold set drawn from four committee-approved Vietnamese medical theses
(anonymized) and a ledger:
| Matcher version | Resolve rate* |
|---|---|
| baseline (title-only) | 80.0% |
| current (full-string + dual-index + parser fixes) | 94.9% |
* Resolve rate = the share of entries matched to an indexed record. The gold set carries no per-item answer key yet, so this measures retrieval, not verified correctness — spot checks are clean, but treat the number accordingly. Vietnamese-language references are deliberately refused (VN journals are not internationally indexed; guessing produced wrong DOIs) and are excluded from this set.
python3 tests/test_core.py --net
python3 benchmarks/run_bench.py --sample 60MIT.