You find the perfect paper for your research. You click the link. And you hit a $40 paywall.
But what if that same paper is legally available for free somewhere else?
Unpaywall checks 50,000+ open-access sources to find free legal copies of paywalled papers. It covers 30M+ articles. And it has a free API.
How It Works
Give Unpaywall a DOI, and it tells you:
- Is there a free legal version?
- Where is it hosted? (university repo, preprint server, author page)
- What type of open access? (gold, green, hybrid, bronze)
Quick Start
import requests
def find_free_version(doi, email):
resp = requests.get(f"https://api.unpaywall.org/v2/{doi}", params={"email": email})
data = resp.json()
if data.get("is_oa"):
best = data.get("best_oa_location", {})
print(f"FREE version found!")
print(f" URL: {best.get('url_for_pdf') or best.get('url')}")
print(f" Host: {best.get('host_type')} ({best.get('repository_institution', 'N/A')})")
print(f" OA type: {data.get('oa_status')}")
else:
print(f"No free version found for {doi}")
# Example: famous AlphaFold paper
find_free_version("10.1038/s41586-021-03819-2", "[email protected]")
Output:
FREE version found!
URL: https://europepmc.org/articles/pmc8371605?pdf=render
Host: repository (Europe PMC)
OA type: green
Batch Check Multiple Papers
dois = [
"10.1038/s41586-021-03819-2", # AlphaFold
"10.1126/science.abj8754", # Some Nature paper
"10.1016/j.cell.2021.12.006", # Cell paper
"10.1145/3442188.3445922", # ACM paper
]
for doi in dois:
resp = requests.get(f"https://api.unpaywall.org/v2/{doi}",
params={"email": "[email protected]"})
data = resp.json()
title = data.get("title", "Unknown")[:50]
status = "FREE" if data.get("is_oa") else "PAYWALLED"
oa_type = data.get("oa_status", "closed")
print(f"[{status:>9}] ({oa_type:>7}) {title}")
Build a Paywall Bypass Checker
def check_reading_list(dois, email):
results = {"free": [], "paywalled": []}
for doi in dois:
resp = requests.get(f"https://api.unpaywall.org/v2/{doi}",
params={"email": email})
data = resp.json()
title = data.get("title", doi)
if data.get("is_oa"):
best = data.get("best_oa_location", {})
url = best.get("url_for_pdf") or best.get("url", "")
results["free"].append({"title": title, "url": url})
else:
results["paywalled"].append({"title": title, "doi": doi})
print(f"\nResults: {len(results['free'])} free, {len(results['paywalled'])} paywalled")
print(f"\nFree papers:")
for p in results["free"]:
print(f" {p['title'][:60]}")
print(f" {p['url']}")
return results
Combine with Crossref for Full Pipeline
def search_and_find_free(query, email, top=5):
# Step 1: Search Crossref for papers
resp = requests.get("https://api.crossref.org/works", params={
"query": query, "rows": top, "sort": "is-referenced-by-count", "order": "desc"
})
papers = resp.json()["message"]["items"]
# Step 2: Check each for free version
for paper in papers:
doi = paper.get("DOI")
title = paper.get("title", ["N/A"])[0][:50]
cites = paper.get("is-referenced-by-count", 0)
oa = requests.get(f"https://api.unpaywall.org/v2/{doi}",
params={"email": email}).json()
free = "FREE" if oa.get("is_oa") else "PAID"
print(f"[{free}] {title} ({cites:,} cites)")
search_and_find_free("deep learning medical imaging", "[email protected]")
API Details
Endpoint:https://api.unpaywall.org/v2/{doi}[email protected]
Rate limit: 100K requests/day
Auth: Just your email address (no key)
Coverage: 30M+ articles checked against 50K+ sources
Data dump: Full dataset available for download
Pro Tips
- Always prefer
url_for_pdfoverurlwhen available - Check
oa_locationsarray for ALL free versions, not just the best one - Green OA = author/repository copy, Gold = publisher open access
- Combine with Crossref to search by topic, then check OA status
- The browser extension is great for manual checking
Unpaywall + Crossref + OpenAlex = the ultimate free research stack.
Do you hit paywalls often? What is your workflow for finding free papers? Share in the comments.
More research API tools on GitHub.