Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Batch download open-access PDFs by DOI using legitimate OA APIs (Unpaywall, PMC, OpenAlex, Crossref). Optional PDF→Markdown conversion for token-efficient LLM analysis.
.claude/skills/aperivue-fulltext-retrieval/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 52% | 0% |
Batch download open-access full-text PDFs from a DOI list using legitimate OA APIs only.
DOI → arXiv (10.48550/arXiv.* DOIs) → Unpaywall → PMC (Europe PMC / OA FTP / web) → OpenAlex → Crossref → landing pageEach DOI goes through these sources in order until a valid PDF (≥10 KB, %PDF- header) is found. arXiv DOIs (10.48550/arXiv.2401.01234, version suffixes, old-style hep-th/9901001, or a bare arXiv: id) resolve directly to the arXiv PDF first.
bash# Prepare a DOI list (one per line) cat > dois.txt << 'EOF' 10.1007/s00330-010-1783-x 10.1002/mp.12524 10.1148/radiol.13131265 EOF # Run python fetch_oa.py dois.txt --output pdfs/ --email your@email.com # Verbose mode for debugging python fetch_oa.py dois.txt -o pdfs/ -e your@email.com --verbose
Plain text — one DOI per line:
10.1007/s00330-010-1783-x
10.1002/mp.12524TSV / CSV with header — must contain a DOI column; optional PMID and Title columns:
tsvID Title DOI PMID Year 1 Some paper 10.1007/s00330-010-1783-x 20628747 2010
Markdown table — a pipe table with a DOI column also works:
markdown| DOI | PMID | Title | |-----|------|-------| | 10.1007/s00330-010-1783-x | 20628747 | Some paper |
When a PMID is available, the PMC lookup is more reliable (PMID → PMCID conversion). When a Title column is present, downloaded PDFs get a best-effort title cross-check (see Retrieval report below).
PMC web pages may block automated downloads with JavaScript proof-of-work challenges. This tool uses three fallback methods:
bashPMCID="PMC9733600" curl -sLo output.pdf \ "https://europepmc.org/backend/ptpmcrender.fcgi?accid=${PMCID}&blobtype=pdf"
bashcurl -s "https://www.ncbi.nlm.nih.gov/pmc/utils/oa/oa.fcgi?id=${PMCID}" | \ grep -oE 'href="[^"]*\.pdf"' | head -1 | \ sed 's/href="//;s/"//' | xargs curl -sLo output.pdf
bash# Works with both DOI and PMID curl -s "https://www.ncbi.nlm.nih.gov/pmc/utils/idconv/v1.0/?ids=${DOI}&format=json" | \ python3 -c "import sys,json; print(json.load(sys.stdin)['records'][0].get('pmcid',''))"
{DOI_safe}.pdf (slashes replaced with underscores)pdfs/retrieval_report.json — structured per-DOI report (see below)manual_needed.txt — DOIs that could not be retrieved via OA--report)Every run writes a structured report (default <output>/retrieval_report.json, override with --report PATH):
json{ "schema_version": 1, "generated_by": "fetch_oa.py", "counts": {"total": 10, "retrieved": 6, "not_retrieved": 4, "title_mismatch": 1}, "items": [ {"doi": "10.1007/...", "pmid": "20628747", "title": "...", "status": "oa", "source": "unpaywall", "file": "10.1007_....pdf", "size_bytes": 482113, "title_match": "match"} ] }
status ∈ arxiv | oa | pmc | skip | fail; source names the resolver that succeeded.title_match ∈ match | mismatch | unavailable (tri-state). It is best-effort:it needs a Title column and pdftotext (poppler). When either is missing it is unavailable; a mismatch is flagged for review and never auto-rejects a PDF (guards against a publisher serving a wrong/redirect PDF that still passes the %PDF- check).
OA-only resolvers miss paywalled-but-licensed papers. To attach full text inside Zotero at a much higher yield, use references/find_available_pdf.js — a user-run snippet for Zotero's Tools → Developer → Run JavaScript. It triggers Zotero's own addAvailablePDF / addAvailablePDFs and therefore reuses your OpenURL resolver / institutional proxy config; no credentials, proxy hosts, or institutional identifiers are hard-coded or leave your Zotero client. The no-code equivalent is right-click → "Find Available PDF".
This path is user-initiated and depends on your live Zotero session, so its results are recorded manually (not reproducible CI evidence). /lit-sync Phase 2.7 orchestrates both routes (disk OA via this script + in-library via the snippet) and reconciles them in a report.
| Source | Rate Limit | Notes | |--------|-----------|-------| | Unpaywall | 100 req/sec | Email required | | NCBI PMC | 3 req/sec without API key | Add &api_key= for higher limits | | OpenAlex | 100k req/day | Polite pool with email in User-Agent | | Crossref | 50 req/sec with email | Plus service with mailto: in UA | | Europe PMC | No documented limit | Be polite, ≤1 req/sec recommended |
The script uses 0.3–0.5 second delays between requests.
After downloading PDFs, convert them to LLM-friendly Markdown for token-efficient repeated analysis. Uses pymupdf4llm — optimized for academic papers with two-column layout handling and table preservation.
bash# Install (one-time) pip install pymupdf4llm # Convert all PDFs in a directory python pdf_to_md.py pdfs/ # Convert with verbose output python pdf_to_md.py pdfs/ -v # Custom output directory python pdf_to_md.py pdfs/ -o markdown/ # First 10 pages only (useful for long supplements) python pdf_to_md.py pdfs/ --pages 0-9 # Overwrite existing conversions python pdf_to_md.py pdfs/ --force
bash# Step 1: Download PDFs python fetch_oa.py dois.txt -o pdfs/ -e your@email.com # Step 2: Convert to Markdown (only successful downloads) python pdf_to_md.py pdfs/ -v
After conversion, .md files sit alongside .pdf files. Claude Code can then use Read for full content or Grep for targeted extraction — significantly more token-efficient than re-reading PDFs.
| Scenario | Recommendation | |----------|---------------| | Screening/triage (read once) | Skip — read PDF directly | | Data extraction from k≥5 studies | Convert — repeated reads save tokens | | Meta-analysis full pipeline | Convert — papers referenced across multiple phases | | Single paper deep review | Optional — marginal benefit |
lines_strict strategy (preserves grid-line tables accurately)pdf_to_md.py requires pymupdf4llm (AGPL-3.0). This is an optional dependency — fetch_oa.py remains stdlib-only with zero external dependencies. The AGPL license applies to pymupdf4llm itself, not to this skill.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 15,652 | 7,873 | -50% | 1 | 1 | 0% | 3,619 | 2,726 | -25% | 0 | 0 | — |
case-02 | fail→fail | 4,688 | 2,701 | -42% | 1 | 1 | 0% | 321 | 2,622 | +717% | 0 | 0 | — |
case-03 | fail→fail | 18,938 | 4,941 | -74% | 1 | 1 | 0% | 3,908 | 2,804 | -28% | 0 | 0 | — |
case-04 | fail→pass | 18,275 | 7,932 | -57% | 1 | 1 | 0% | 3,062 | 3,923 | +28% | 0 | 0 | — |
case-05 | pass→pass | 16,722 | 12,706 | -24% | 1 | 1 | 0% | 3,308 | 4,986 | +51% | 0 | 0 | — |
case-06 | pass→pass | 13,866 | 4,656 | -66% | 1 | 1 | 0% | 2,705 | 3,242 | +20% | 0 | 0 | — |
case-07 | pass→pass | 11,245 | 4,536 | -60% | 1 | 1 | 0% | 1,873 | 3,448 | +84% | 0 | 0 | — |
case-08 | pass→pass | 15,632 | 3,550 | -77% | 1 | 1 | 0% | 3,174 | 3,067 | -3% | 0 | 0 | — |
case-09 | fail→pass | 10,934 | 2,389 | -78% | 1 | 1 | 0% | 1,826 | 2,854 | +56% | 0 | 0 | — |
case-10 | fail→pass | 15,770 | 7,234 | -54% | 1 | 1 | 0% | 2,668 | 3,600 | +35% | 0 | 0 | — |
case-11 | fail→pass | 16,489 | 5,557 | -66% | 1 | 1 | 0% | 2,546 | 3,223 | +27% | 0 | 0 | — |
case-12 | fail→pass | 10,152 | 1,306 | -87% | 1 | 1 | 0% | 1,744 | 2,649 | +52% | 0 | 0 | — |
case-13 | fail→pass | 6,602 | 1,877 | -72% | 1 | 1 | 0% | 1,163 | 2,775 | +139% | 0 | 0 | — |
case-14 | fail→pass | 4,653 | 12,452 | +168% | 1 | 1 | 0% | 704 | 3,583 | +409% | 0 | 0 | — |
case-15 | fail→fail | 2,257 | 8,939 | +296% | 1 | 1 | 0% | 346 | 4,140 | +1097% | 0 | 0 | — |
case-16 | fail→fail | 15,845 | 12,908 | -19% | 1 | 1 | 0% | 2,979 | 4,867 | +63% | 0 | 0 | — |
case-17 | pass→pass | 7,856 | 3,325 | -58% | 1 | 1 | 0% | 1,327 | 2,984 | +125% | 0 | 0 | — |
case-18 | pass→pass | 2,838 | 1,889 | -33% | 1 | 1 | 0% | 451 | 2,681 | +494% | 0 | 0 | — |
case-19 | fail→pass | 15,994 | 5,091 | -68% | 1 | 1 | 0% | 2,606 | 3,241 | +24% | 0 | 0 | — |
case-20 | pass→pass | 4,915 | 3,305 | -33% | 1 | 1 | 0% | 791 | 3,037 | +284% | 0 | 0 | — |
case-21 | fail→pass | 19,213 | 6,290 | -67% | 1 | 1 | 0% | 3,012 | 3,418 | +13% | 0 | 0 | — |
case-22 | fail→pass | 9,217 | 3,389 | -63% | 1 | 1 | 0% | 1,521 | 2,966 | +95% | 0 | 0 | — |
case-23 | pass→pass | 5,223 | 1,914 | -63% | 1 | 1 | 0% | 876 | 2,680 | +206% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 20 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +43 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.