Small CLI, web and REST API for extracting DOI metadata from article URLs.
See it live at 🔗 https://url2doi.onrender.com/.
Supported and covered by the current test URLs:
- ✅ Cell Press
- ✅ Nature
- ✅ Science / Science Advances / Science Immunology
- ✅ bioRxiv
- ✅ AACR
Create a text file with one URL per line:
https://www.nature.com/articles/d41586-026-02103-5
Run:
uv run url2doi convert urls.txtDefault output is one DOI per line. If a URL cannot be resolved, the command prints a warning with the original URL:
10.1038/d41586-026-02103-5
⚠️ could not retrieve - https://example.com/missing
To export the tab-separated table instead, pass --tsv with an output path:
uv run url2doi convert urls.txt --tsv output/results.tsvThe exported TSV keeps the previous columns:
url doi doi_url status
https://www.nature.com/articles/d41586-026-02103-5 10.1038/d41586-026-02103-5 https://doi.org/10.1038/d41586-026-02103-5 ok
The extractor first checks common publisher meta tags such as citation_doi,
dc.identifier, and prism.doi, then falls back to DOI links or DOI-like text
in the HTML. For science.org URLs that already embed a DOI under
/doi/10.1126/..., and biorxiv.org URLs that embed a DOI under /content/,
it extracts that DOI directly without downloading the page. Biorxiv version
markers such as v2 are removed from the exported DOI.
For Elsevier and Cell URLs that expose a PII in the path, it can also query
Crossref and accept the DOI only when Crossref's alternative-id matches that
PII.
Start the Flask API for local development:
uv run url2doi serve-apiStart it with Gunicorn for production:
uv run url2doi serve-api --productionThe production server listens on 0.0.0.0:8000 by default. Pass --host or
--port to change the bind address, WEB_CONCURRENCY, WEB_THREADS, or
WEB_TIMEOUT to override Gunicorn settings, and --timeout to change the
default DOI fetch timeout.
Platforms that expect a WSGI target can also run:
uv run gunicorn url2doi.api.wsgi:app --config gunicorn.conf.pyExtract a DOI from one URL:
curl -X POST http://127.0.0.1:8000/doi \
-H 'Content-Type: application/json' \
-d '{"url": "https://www.nature.com/articles/d41586-026-02103-5"}'The same endpoint also supports GET /doi?url=....
uv sync
uv run python -m unittest discover -s tests