Claude Skill

alterlab-biorxiv

Search the bioRxiv preprint server and retrieve paper metadata or download PDFs via its API. Use when finding life sciences preprints by keywords, authors, DOI, date ranges, or categories, or when conducting a biology literature review of not-yet-peer-reviewed work. Part of the A

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_databases_alterlab-biorxiv-e4836c0.zip · 17 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/databases/alterlab-biorxiv
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

bioRxiv Database

Overview

Python tooling over the keyless bioRxiv API for searching and retrieving life-sciences preprints. Searches by keyword, author, date range, and category, returning structured JSON (titles, abstracts, DOIs, authors, versions), and downloads full-text PDFs.

For published, peer-reviewed literature use alterlab-pubmed; for computer-science / physics / math preprints use alterlab-arxiv. bioRxiv covers biology subjects only. The same API also serves medRxiv (swap biorxiv for medrxiv in the endpoint path, e.g. /details/medrxiv/2025-03-21/2025-03-28?category=cardiovascular%20medicine); the bundled script targets bioRxiv, so query medRxiv with curl/requests directly.

How it works (and its limits)

The bioRxiv /details endpoint returns preprints by date range, 30 records per page. It accepts a server-side subject-category filter (?category=cell_biology), but has no keyword or author filter. So this tool:

  1. Paginates the date range (following the cursor until all records are retrieved), passing --category to the server so only that subject is fetched, then
  2. Filters client-side by keyword (substring over title/abstract) and author (substring over the author list).

Implication: a wide date range means many API calls and a large download. Keep ranges as tight as the question allows, and use --category (cuts API calls) and --limit to bound the work.

When to Use This Skill

Use this skill when:

  • Searching for recent life-sciences preprints in specific research areas
  • Tracking preprints by particular authors
  • Conducting systematic preprint literature reviews
  • Analyzing preprint trends over time periods
  • Retrieving metadata for citation management
  • Downloading preprint PDFs for analysis
  • Filtering papers by bioRxiv subject categories

Does NOT Trigger

Scenario Use Instead
Peer-reviewed, MeSH-indexed biomedical journal articles alterlab-pubmed
CS / physics / math / quantitative-biology preprints on arXiv alterlab-arxiv
Citation counts or cross-publisher bibliometrics for preprints alterlab-openalex
Depositing your own preprint (server choice, license, versioning) alterlab-preprint-deposition
Multi-database systematic review with PRISMA screening alterlab-literature-review

Running the script

The script's only dependency is requests. Run it with uv so the dependency is provisioned on the fly:

uv run --with requests scripts/biorxiv_search.py --help

The python scripts/biorxiv_search.py ... invocations below are shorthand; substitute uv run --with requests scripts/biorxiv_search.py ... (or activate an environment that has requests).

Core Search Capabilities

1. Keyword Search

Search for preprints containing specific keywords in titles, abstracts, or author lists.

Basic Usage:

python scripts/biorxiv_search.py \
  --keywords "CRISPR" "gene editing" \
  --start-date 2024-01-01 \
  --end-date 2024-12-31 \
  --output results.json

With Category Filter:

python scripts/biorxiv_search.py \
  --keywords "neural networks" "deep learning" \
  --days-back 180 \
  --category neuroscience \
  --output recent_neuroscience.json

Search Fields: Keyword matching is a case-insensitive substring match, and a paper matches if any keyword is found (OR semantics, not AND). By default keywords are searched in both title and abstract. Customize with --search-fields:

python scripts/biorxiv_search.py \
  --keywords "AlphaFold" \
  --search-fields title \
  --days-back 365

2. Author Search

Find all papers by a specific author within a date range.

Basic Usage:

python scripts/biorxiv_search.py \
  --author "Smith" \
  --start-date 2023-01-01 \
  --end-date 2024-12-31 \
  --output smith_papers.json

Recent Publications:

# Last year by default if no dates specified
python scripts/biorxiv_search.py \
  --author "Johnson" \
  --output johnson_recent.json

3. Date Range Search

Retrieve all preprints posted within a specific date range.

Basic Usage:

python scripts/biorxiv_search.py \
  --start-date 2024-01-01 \
  --end-date 2024-01-31 \
  --output january_2024.json

With Category Filter:

python scripts/biorxiv_search.py \
  --start-date 2024-06-01 \
  --end-date 2024-06-30 \
  --category genomics \
  --output genomics_june.json

Days Back Shortcut:

# Last 30 days
python scripts/biorxiv_search.py \
  --days-back 30 \
  --output last_month.json

4. Paper Details by DOI

Retrieve detailed metadata for a specific preprint. bioRxiv DOIs come in two prefixes: 10.1101/… for older preprints and 10.64898/… for preprints posted since the move to openRxiv (December 2025), e.g. 10.64898/2026.08.28.747819. Both work with /details/ and the www.biorxiv.org/content/ URLs; the script normalizes DOIs, doi.org links, and content URLs regardless of prefix. Don't hard-code 10.1101 in regexes or validators.

Basic Usage:

python scripts/biorxiv_search.py \
  --doi "10.1101/2024.01.15.123456" \
  --output paper_details.json

Full DOI URLs Accepted (either prefix):

python scripts/biorxiv_search.py \
  --doi "https://doi.org/10.64898/2026.08.28.747819"

5. PDF Downloads

Download the full-text PDF of any preprint.

Basic Usage:

python scripts/biorxiv_search.py \
  --doi "10.1101/2024.01.15.123456" \
  --download-pdf paper.pdf

Batch Processing: For multiple PDFs, extract DOIs from a search result JSON and download each paper:

import json
from biorxiv_search import BioRxivSearcher

# Load search results
with open('results.json') as f:
    data = json.load(f)

searcher = BioRxivSearcher(verbose=True)

# Download each paper
for i, paper in enumerate(data['results'][:10]):  # First 10 papers
    doi = paper['doi']
    searcher.download_pdf(doi, f"papers/paper_{i+1}.pdf")

Valid Categories

Filter searches by bioRxiv subject categories:

  • animal-behavior-and-cognition
  • biochemistry
  • bioengineering
  • bioinformatics
  • biophysics
  • cancer-biology
  • cell-biology
  • clinical-trials
  • developmental-biology
  • ecology
  • epidemiology
  • evolutionary-biology
  • genetics
  • genomics
  • immunology
  • microbiology
  • molecular-biology
  • neuroscience
  • paleontology
  • pathology
  • pharmacology-and-toxicology
  • physiology
  • plant-biology
  • scientific-communication-and-education
  • synthetic-biology
  • systems-biology
  • zoology

Output Format

All searches return structured JSON with the following format:

{
  "query": {
    "keywords": ["CRISPR"],
    "start_date": "2024-01-01",
    "end_date": "2024-12-31",
    "category": "genomics"
  },
  "result_count": 42,
  "results": [
    {
      "doi": "10.1101/2024.01.15.123456",
      "title": "Paper Title Here",
      "authors": "Smith, J.; Doe, J.; Johnson, A.",
      "author_corresponding": "Smith J",
      "author_corresponding_institution": "University Example",
      "date": "2024-01-15",
      "version": "1",
      "type": "new results",
      "license": "cc_by",
      "category": "genomics",
      "abstract": "Full abstract text...",
      "pdf_url": "https://www.biorxiv.org/content/10.1101/2024.01.15.123456v1.full.pdf",
      "html_url": "https://www.biorxiv.org/content/10.1101/2024.01.15.123456v1",
      "jatsxml": "https://www.biorxiv.org/content/...",
      "published": ""
    }
  ]
}

Common Usage Patterns

Literature Review Workflow

  1. Broad keyword search:
python scripts/biorxiv_search.py \
  --keywords "organoids" "tissue engineering" \
  --start-date 2023-01-01 \
  --end-date 2024-12-31 \
  --category bioengineering \
  --output organoid_papers.json
  1. Extract and review results:
import json

with open('organoid_papers.json') as f:
    data = json.load(f)

print(f"Found {data['result_count']} papers")

for paper in data['results'][:5]:
    print(f"\nTitle: {paper['title']}")
    print(f"Authors: {paper['authors']}")
    print(f"Date: {paper['date']}")
    print(f"DOI: {paper['doi']}")
  1. Download selected papers:
from biorxiv_search import BioRxivSearcher

searcher = BioRxivSearcher()
selected_dois = ["10.1101/2024.01.15.123456", "10.1101/2024.02.20.789012"]

for doi in selected_dois:
    filename = doi.replace("/", "_").replace(".", "_") + ".pdf"
    searcher.download_pdf(doi, f"papers/{filename}")

Trend Analysis

Track research trends by analyzing publication frequencies over time:

python scripts/biorxiv_search.py \
  --keywords "machine learning" \
  --start-date 2020-01-01 \
  --end-date 2024-12-31 \
  --category bioinformatics \
  --output ml_trends.json

Then analyze the temporal distribution in the results.

Author Tracking

Monitor specific researchers' preprints:

# Track multiple authors (each run scans the whole window, so keep it short)
for author in Smith Johnson Williams; do
  python scripts/biorxiv_search.py \
    --author "$author" \
    --days-back 365 \
    --output "${author}_papers.json"
done

Python API Usage

For more complex workflows, import and use the BioRxivSearcher class directly:

from scripts.biorxiv_search import BioRxivSearcher

# Initialize
searcher = BioRxivSearcher(verbose=True)

# Multiple search operations
keywords_papers = searcher.search_by_keywords(
    keywords=["CRISPR", "gene editing"],
    start_date="2024-01-01",
    end_date="2024-12-31",
    category="genomics"
)

author_papers = searcher.search_by_author(
    author_name="Smith",
    start_date="2023-01-01",
    end_date="2024-12-31"
)

# Get specific paper details
paper = searcher.get_paper_details("10.1101/2024.01.15.123456")

# Download PDF
success = searcher.download_pdf(
    doi="10.1101/2024.01.15.123456",
    output_path="paper.pdf"
)

# Format results consistently
formatted = searcher.format_result(paper, include_abstract=True)

Best Practices

  1. Keep date ranges tight: Because filtering is client-side, the tool paginates the entire range (30 records/page) before filtering. A single busy week is ~800 preprints (~27 API calls); a full year is tens of thousands. Narrow the range, or use --days-back for recency.

  2. Filter by category: Use --category whenever the subject is known. It is sent to the server (?category=), so it cuts both the result set and the number of API calls (one busy week: ~1,270 preprints overall vs. ~80 in cell biology).

  3. Cap with --limit: For pure date-range searches, --limit also stops pagination early, so it genuinely reduces API calls. For keyword/author searches the whole range must be scanned first, so --limit only trims the final list.

  4. Respect rate limits: The script sleeps 0.5s between requests. There is no documented hard rate limit, but for large collections add more delay and cache results to JSON.

  5. Version tracking: Preprints can have multiple versions. DOI lookups return the latest version; download_pdf resolves the latest version automatically (pass version= to override). PDF/HTML URLs embed the version number. The published field of /details/ carries the journal DOI once the preprint is published (or NA) — use it rather than the per-DOI /pubs/ lookup, which returns nothing for 10.64898 DOIs.

  6. PDF downloads can be throttled: PDFs come from www.biorxiv.org, which sits behind Cloudflare and may answer scripted requests with HTTP 429. Space downloads out, fall back to the html_url, and for bulk full text use bioRxiv's requester-pays text-mining bucket s3://biorxiv-src-monthly (MECA zip packages; see https://www.biorxiv.org/tdm).

  7. Handle empty results: Check result_count. Empty results usually mean the date range had no matching papers, an over-narrow category, or transient API connectivity issues — not a silent truncation (pagination retrieves the full range).

  8. Verbose mode for debugging: Use --verbose to see each paginated API request and the reported total.

Advanced Features

Custom Date Range Logic

from datetime import datetime, timedelta
from scripts.biorxiv_search import BioRxivSearcher

# Last quarter
end_date = datetime.now()
start_date = end_date - timedelta(days=90)

papers = BioRxivSearcher().search_by_date_range(
    start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"), category="genomics"
)

Result Limiting

Limit the number of results returned:

python scripts/biorxiv_search.py \
  --keywords "COVID-19" \
  --days-back 30 \
  --limit 50 \
  --output covid_top50.json

Exclude Abstracts for Speed

When only metadata is needed:

# Note: Abstract inclusion is controlled in Python API
from scripts.biorxiv_search import BioRxivSearcher

searcher = BioRxivSearcher()
papers = searcher.search_by_keywords(keywords=["AI"], days_back=30)
formatted = [searcher.format_result(p, include_abstract=False) for p in papers]

Programmatic Integration

Integrate search results into downstream analysis pipelines:

import json
import pandas as pd

# Load results
with open('results.json') as f:
    data = json.load(f)

# Convert to DataFrame for analysis
df = pd.DataFrame(data['results'])

# Analyze
print(f"Total papers: {len(df)}")
print(f"Date range: {df['date'].min()} to {df['date'].max()}")
print(f"\nTop authors by paper count:")
print(df['authors'].str.split(';').explode().str.strip().value_counts().head(10))

# Filter and export
recent = df[df['date'] >= '2024-06-01']
recent.to_csv('recent_papers.csv', index=False)

Reference Documentation

For detailed API specifications, endpoint documentation, and response schemas, refer to:

  • references/api_reference.md - Complete bioRxiv API documentation

The reference file includes:

  • Full API endpoint specifications
  • Response format details
  • Error handling patterns
  • Rate limiting guidelines
  • Advanced search patterns
Files (alterlab-academic-skills)
  • evals
    • evals.json 4.2 KB
      {
        "skill": "alterlab-biorxiv",
        "evals": [
          {
            "id": "keyword-category-date-search",
            "prompt": "Find me bioRxiv preprints on CRISPR base editing posted in the cancer-biology category between January and December 2024. I want titles, abstracts, and DOIs.",
            "expected_output": "Invokes alterlab-biorxiv to run scripts/biorxiv_search.py with --keywords, --category cancer-biology, and --start-date/--end-date over 2024, returning structured JSON metadata (titles, abstracts, DOIs, authors). Should ground the search in bioRxiv's category list and the keyword search over title+abstract.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Uses the bioRxiv search script with a valid category filter and a date range, and returns preprint metadata including DOIs and abstracts." }
            ]
          },
          {
            "id": "author-tracking",
            "prompt": "Track all bioRxiv preprints posted by the author Doudna over the last year so I can monitor their recent unpublished work.",
            "expected_output": "Invokes alterlab-biorxiv in author-search mode using --author with a date range (defaulting to the last year), returning the author's recent preprints as JSON metadata. Should make clear this targets bioRxiv preprints, not peer-reviewed publications.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Performs an author search on bioRxiv and returns that author's recent preprints." }
            ]
          },
          {
            "id": "doi-details-pdf-download",
            "prompt": "Pull the full metadata for bioRxiv preprint 10.1101/2024.01.15.123456 and download its PDF for me.",
            "expected_output": "Invokes alterlab-biorxiv to fetch paper details by DOI and download the full-text PDF (e.g. --doi with --download-pdf, or BioRxivSearcher.get_paper_details/download_pdf), returning the structured record plus the saved PDF path. Handles DOIs with either bioRxiv prefix (10.1101/... or the post-Dec-2025 10.64898/...) and resolves the latest version before building the PDF URL.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "doi" }
            ]
          },
          {
            "id": "trend-analysis",
            "prompt": "I'm writing a review on the rise of machine-learning methods in the bioinformatics literature. Pull bioRxiv preprints mentioning machine learning in bioinformatics from 2020 through 2024 so I can analyze how the volume changed over time.",
            "expected_output": "Invokes alterlab-biorxiv to run a keyword search filtered to the bioinformatics category over a multi-year date range, returning JSON results suitable for temporal/trend analysis of preprint counts. Should suggest splitting long ranges into smaller queries per best practices.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Searches bioRxiv preprints over a multi-year window in a relevant category and returns data usable for trend analysis over time." }
            ]
          },
          {
            "id": "near-miss-arxiv",
            "prompt": "Find me recent preprints on transformer architectures for large language models. They'll be on arXiv in the cs.CL and cs.LG categories.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-arxiv. bioRxiv only covers life-sciences preprints and its categories are biology subjects, whereas the user wants computer-science/ML preprints from arXiv.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-arxiv" }
            ]
          },
          {
            "id": "near-miss-pubmed",
            "prompt": "Build me a MeSH-term Boolean query and search PubMed for peer-reviewed randomized controlled trials on metformin in type 2 diabetes published 2020 to 2024.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-pubmed. The user wants peer-reviewed published literature with MeSH terms and publication-type filters via NCBI E-utilities, not not-yet-peer-reviewed bioRxiv preprints.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-pubmed" }
            ]
          }
        ]
      }
      
  • references
    • api_reference.md 10.1 KB
      # bioRxiv API Reference
      
      ## Overview
      
      The bioRxiv API provides programmatic access to preprint metadata from the bioRxiv server. The API returns JSON-formatted data with comprehensive metadata about life sciences preprints.
      
      ## Base URL
      
      ```
      https://api.biorxiv.org
      ```
      
      ## Rate Limiting
      
      Be respectful of the API:
      - Add delays between requests (minimum 0.5 seconds recommended)
      - Use appropriate User-Agent headers
      - Cache results when possible
      
      ## API Endpoints
      
      ### 1. Details by Date Range
      
      Retrieve preprints posted within a specific date range, optionally restricted to one
      subject category with the `?category=` query parameter (see the note below). The same
      paths work for medRxiv by replacing `biorxiv` with `medrxiv`.
      
      **Endpoint:**
      ```
      GET /details/biorxiv/{start_date}/{end_date}/{cursor}/{format}
      ```
      
      **Parameters:**
      - `start_date`: Start date in YYYY-MM-DD format
      - `end_date`: End date in YYYY-MM-DD format
      - `cursor`: Absolute record offset for pagination (0, then 30, 60, ...). The
        endpoint returns **30 records per call**; iterate the cursor until it reaches
        `messages[0].total`.
      - `format`: `json` (default) or `xml`
      
      **Pagination:** the `/details` endpoint is capped at 30 records per request.
      `messages[0]` reports `count` (records in this page), `cursor` (current offset),
      and `total` (records in the whole range). To retrieve everything, loop the cursor
      by 30 until `cursor >= total`.
      
      **Category filtering:** pass the subject as a **query-string parameter**, with
      underscores (or `%20`) for spaces — `?category=cell_biology`. It is applied
      server-side, so `messages[0].total` and the number of pages shrink to that subject
      (one week in March 2025: 1,267 preprints overall vs. 77 in cell biology). A category
      path segment (`/details/biorxiv/{start}/{end}/neuroscience`) does **not** work. In
      records the category is **lowercase with spaces** (e.g. `cell biology`), whereas the
      script's CLI form is **hyphenated** (e.g. `cell-biology`); normalize between the two.
      
      Other interval forms: `/details/biorxiv/{N}` (the N most recent posts) and
      `/details/biorxiv/{N}d` (posts from the last N days).
      
      **Example:**
      ```
      GET https://api.biorxiv.org/details/biorxiv/2024-01-01/2024-01-31/0/json
      GET https://api.biorxiv.org/details/biorxiv/2024-01-01/2024-01-31/30/json
      GET https://api.biorxiv.org/details/biorxiv/2025-03-21/2025-03-28/0/json?category=cell_biology
      ```
      
      **Response:**
      ```json
      {
        "messages": [
          {
            "status": "ok",
            "category": "all",
            "interval": "2024-01-01:2024-01-31",
            "funder": "all",
            "cursor": 0,
            "count": 30,
            "count_new_papers": "590",
            "total": "801"
          }
        ],
        "collection": [
          {
            "doi": "10.1101/2024.01.15.123456",
            "title": "Example Paper Title",
            "authors": "Smith, J.; Doe, J.; Johnson, A.",
            "author_corresponding": "Smith J",
            "author_corresponding_institution": "University Example",
            "date": "2024-01-15",
            "version": "1",
            "type": "new results",
            "license": "cc_by",
            "category": "neuroscience",
            "jatsxml": "https://www.biorxiv.org/content/...",
            "abstract": "This is the abstract...",
            "funder": "NA",
            "published": "NA",
            "server": "bioRxiv"
          }
        ]
      }
      ```
      
      Note: `authors` is **semicolon-separated** (`"Smith, J.; Doe, J."`), not
      comma-separated. `total` and `count_new_papers` may be returned as strings.
      `published` holds the journal DOI once the preprint has been published, else `NA`.
      (Values above are illustrative; field names match the live API as of 2026-09.)
      
      ### 2. Details by DOI
      
      Retrieve details for a specific preprint by DOI. The response `collection`
      contains one entry per version (ordered ascending), so the last entry is the
      latest version.
      
      **Endpoint:**
      ```
      GET /details/biorxiv/{doi}/na/{format}
      ```
      
      **Parameters:**
      - `doi`: The DOI of the preprint — `10.1101/…` (e.g. `10.1101/2024.01.15.123456`) for
        older preprints, `10.64898/…` (e.g. `10.64898/2026.08.28.747819`) for those posted
        since the move to openRxiv in December 2025. Accept both prefixes in any DOI
        validation or regex.
      - `format`: `json` or `xml`
      
      **Example:**
      ```
      GET https://api.biorxiv.org/details/biorxiv/10.1101/2024.01.15.123456/na/json
      GET https://api.biorxiv.org/details/biorxiv/10.64898/2026.08.28.747819/na/json
      ```
      
      ### 3. Published-Article Metadata (Pubs)
      
      Retrieve metadata for the **published (journal) version** of bioRxiv preprints
      that have subsequently been published. Useful for linking a preprint to its
      peer-reviewed DOI; it is not a feed of arbitrary preprints. The per-DOI form
      (`/pubs/biorxiv/{doi}/na/json`) returns nothing for `10.64898` preprint DOIs (checked
      2026-09) — read the `published` field from `/details/` instead.
      
      **Endpoint:**
      ```
      GET /pubs/biorxiv/{interval}/{cursor}
      ```
      
      **Parameters:**
      - `interval`: A date range (`YYYY-MM-DD/YYYY-MM-DD`) or a numeric value for the N
        most recent published records.
      - `cursor`: Pagination cursor. This endpoint returns **100 records per call**;
        increment the cursor by 100 for subsequent pages.
      
      The response carries both preprint and publication fields (e.g. `biorxiv_doi`,
      `published_doi`, `published_journal`, publication dates).
      
      **Example:**
      ```
      GET https://api.biorxiv.org/pubs/biorxiv/2024-01-01/2024-01-31/0
      ```
      
      **Response includes pagination:**
      ```json
      {
        "messages": [
          {
            "status": "ok",
            "count": 100,
            "total": 250,
            "cursor": 100
          }
        ],
        "collection": [...]
      }
      ```
      
      ## Valid Categories
      
      bioRxiv organizes preprints into the following categories:
      
      - `animal-behavior-and-cognition`
      - `biochemistry`
      - `bioengineering`
      - `bioinformatics`
      - `biophysics`
      - `cancer-biology`
      - `cell-biology`
      - `clinical-trials`
      - `developmental-biology`
      - `ecology`
      - `epidemiology`
      - `evolutionary-biology`
      - `genetics`
      - `genomics`
      - `immunology`
      - `microbiology`
      - `molecular-biology`
      - `neuroscience`
      - `paleontology`
      - `pathology`
      - `pharmacology-and-toxicology`
      - `physiology`
      - `plant-biology`
      - `scientific-communication-and-education`
      - `synthetic-biology`
      - `systems-biology`
      - `zoology`
      
      ## Paper Metadata Fields
      
      Each paper in the `collection` array contains:
      
      | Field | Description | Type |
      |-------|-------------|------|
      | `doi` | Digital Object Identifier | string |
      | `title` | Paper title | string |
      | `authors` | Semicolon-separated author list (e.g. `"Smith, J.; Doe, J."`) | string |
      | `author_corresponding` | Corresponding author name | string |
      | `author_corresponding_institution` | Corresponding author's institution | string |
      | `date` | Publication date (YYYY-MM-DD) | string |
      | `version` | Version number | string |
      | `type` | Type of submission (e.g., "new results") | string |
      | `license` | License type (e.g., "cc_by") | string |
      | `category` | Subject category | string |
      | `jatsxml` | URL to JATS XML | string |
      | `abstract` | Paper abstract | string |
      | `published` | Journal publication info (if published) | string |
      
      ## Downloading Full Papers
      
      ### PDF Download
      
      PDFs can be downloaded directly (not through the API). `www.biorxiv.org` is behind
      Cloudflare and may return **HTTP 429** to scripted clients; space requests out and
      treat a 429 as "retry later", not "missing". For bulk full text use the requester-pays
      text-mining bucket `s3://biorxiv-src-monthly` (us-east-1; `.meca` zip packages with
      PDF + JATS XML; https://www.biorxiv.org/tdm).
      
      ```
      https://www.biorxiv.org/content/{doi}v{version}.full.pdf
      ```
      
      Example:
      ```
      https://www.biorxiv.org/content/10.1101/2024.01.15.123456v1.full.pdf
      ```
      
      ### HTML Version
      
      ```
      https://www.biorxiv.org/content/{doi}v{version}
      ```
      
      ### JATS XML
      
      Full structured XML is available via the `jatsxml` field in the API response.
      
      ## Common Search Patterns
      
      ### Author Search
      
      1. Get papers from date range
      2. Filter by author name (case-insensitive substring match in `authors` field)
      
      ### Keyword Search
      
      1. Get papers from date range (optionally filtered by category)
      2. Search in title, abstract, or both fields
      3. Filter papers containing keywords (case-insensitive)
      
      ### Recent Papers by Category
      
      1. Use the `/details` date-range endpoint over a recent window with `?category=<subject_with_underscores>`
      2. Paginate with the cursor until `cursor >= total`
      
      ### Papers by Funder (since 2025-04-10)
      
      `GET /funder/{server}/{start}/{end}/{ROR_ID_suffix}/{cursor}/{format}` returns preprints
      whose funding declaration names that funder (ROR ID suffix, e.g. `00k4n6c32` for the
      European Commission); 100 records per page, also accepts `?category=`. Funder metadata
      starts on 2025-04-10, so earlier dates return nothing. Records from `/details` carry a
      `funder` field as well.
      
      ## Error Handling
      
      Common HTTP status codes:
      - `200`: Success
      - `404`: Resource not found
      - `500`: Server error
      
      Always check the `messages` array in the response:
      ```json
      {
        "messages": [
          {
            "status": "ok",
            "count": 100
          }
        ]
      }
      ```
      
      ## Best Practices
      
      1. **Cache results**: Store retrieved papers to avoid repeated API calls
      2. **Use appropriate date ranges**: Smaller date ranges return faster
      3. **Filter by category**: Pass `?category=` so the server does the filtering (fewer pages)
      4. **Batch processing**: When downloading multiple PDFs, add delays between requests
      5. **Error handling**: Always check response status and handle errors gracefully
      6. **Version tracking**: Note that papers can have multiple versions
      
      ## Python Usage Example
      
      ```python
      from biorxiv_search import BioRxivSearcher
      
      searcher = BioRxivSearcher(verbose=True)
      
      # Search by keywords
      papers = searcher.search_by_keywords(
          keywords=["CRISPR", "gene editing"],
          start_date="2024-01-01",
          end_date="2024-12-31",
          category="genomics"
      )
      
      # Search by author
      papers = searcher.search_by_author(
          author_name="Smith",
          start_date="2023-01-01",
          end_date="2024-12-31"
      )
      
      # Get specific paper
      paper = searcher.get_paper_details("10.1101/2024.01.15.123456")
      
      # Download PDF
      searcher.download_pdf("10.1101/2024.01.15.123456", "paper.pdf")
      ```
      
      ## External Resources
      
      - bioRxiv homepage: https://www.biorxiv.org/
      - API documentation: https://api.biorxiv.org/ (also covers `/pubs`, `/publisher`, `/funder`, `/sum`, `/usage`)
      - Text and data mining (bulk full text): https://www.biorxiv.org/tdm
      - JATS XML specification: https://jats.nlm.nih.gov/
      
  • scripts
    • biorxiv_search.py 19.6 KB
      #!/usr/bin/env python3
      """
      bioRxiv Search Tool
      A comprehensive Python tool for searching and retrieving preprints from bioRxiv.
      Supports keyword search, author search, date filtering, category filtering, and more.
      
      Note: This tool is focused exclusively on bioRxiv (life sciences preprints).
      
      DOIs: preprints posted since the move to openRxiv (Dec 2025) carry the prefix
      10.64898 (e.g. 10.64898/2026.08.28.747819); older ones keep 10.1101. Both work
      with /details/ and the www.biorxiv.org content URLs, so DOIs are handled
      prefix-agnostically (see normalize_doi).
      """
      
      import requests
      import json
      import argparse
      import re
      from datetime import datetime, timedelta
      from typing import List, Dict, Optional
      import time
      import sys
      
      # bioRxiv/medRxiv DOI prefixes: 10.1101 (legacy) and 10.64898 (openRxiv, Dec 2025+).
      KNOWN_DOI_PREFIXES = ("10.1101", "10.64898")
      _DOI_RE = re.compile(r"(10\.\d{4,9}/[^\s?#]+)")
      
      
      def normalize_doi(doi: str) -> str:
          """Return a bare DOI from a DOI, doi.org URL, 'doi:' string, or content URL.
      
          Accepts both bioRxiv prefixes (10.1101/... and 10.64898/...) and strips a
          trailing version suffix such as 'v2' or '.full.pdf' from content URLs.
          """
          m = _DOI_RE.search(doi.strip())
          if not m:
              raise ValueError(f"Not a DOI: {doi!r}")
          bare = m.group(1)
          bare = re.sub(r"(\.full(\.pdf)?|\.full-text)$", "", bare)
          bare = re.sub(r"v\d+$", "", bare)
          if not bare.startswith(KNOWN_DOI_PREFIXES):
              print(f"[WARN] {bare} is not a bioRxiv/medRxiv DOI prefix "
                    f"({' or '.join(KNOWN_DOI_PREFIXES)})", file=sys.stderr)
          return bare
      
      
      class BioRxivSearcher:
          """Efficient search interface for bioRxiv preprints."""
      
          BASE_URL = "https://api.biorxiv.org"
      
          # Valid bioRxiv categories
          CATEGORIES = [
              "animal-behavior-and-cognition", "biochemistry", "bioengineering",
              "bioinformatics", "biophysics", "cancer-biology", "cell-biology",
              "clinical-trials", "developmental-biology", "ecology", "epidemiology",
              "evolutionary-biology", "genetics", "genomics", "immunology",
              "microbiology", "molecular-biology", "neuroscience", "paleontology",
              "pathology", "pharmacology-and-toxicology", "physiology",
              "plant-biology", "scientific-communication-and-education",
              "synthetic-biology", "systems-biology", "zoology"
          ]
      
          def __init__(self, verbose: bool = False):
              """Initialize the searcher."""
              self.verbose = verbose
              self.session = requests.Session()
              self.session.headers.update({
                  'User-Agent': 'BioRxiv-Search-Tool/1.0'
              })
      
          def _log(self, message: str):
              """Print verbose logging messages."""
              if self.verbose:
                  print(f"[INFO] {message}", file=sys.stderr)
      
          def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
              """Make an API request with error handling and rate limiting."""
              url = f"{self.BASE_URL}/{endpoint}"
              self._log(f"Requesting: {url}")
      
              try:
                  response = self.session.get(url, params=params, timeout=30)
                  response.raise_for_status()
      
                  # Rate limiting - be respectful to the API
                  time.sleep(0.5)
      
                  return response.json()
              except requests.exceptions.RequestException as e:
                  self._log(f"Error making request: {e}")
                  return {"messages": [{"status": "error", "message": str(e)}], "collection": []}
      
          # Per-page size of the /details endpoint (fixed by the API at 30 records).
          PAGE_SIZE = 30
      
          @staticmethod
          def _normalize_category(category: str) -> str:
              """Normalize a category to the API's per-paper form (lowercase, spaces)."""
              return category.strip().lower().replace("-", " ")
      
          def search_by_date_range(
              self,
              start_date: str,
              end_date: str,
              category: Optional[str] = None,
              max_results: Optional[int] = None
          ) -> List[Dict]:
              """
              Search for preprints within a date range, paginating through all results.
      
              The /details endpoint returns only 30 records per call and reports the
              total via messages[0]['total']; this method follows the cursor until the
              full result set (or max_results) is retrieved.
      
              Category filtering is done SERVER-SIDE via the documented
              `?category=cell_biology` query parameter (underscores for spaces), so
              `total` and the number of API calls shrink to that category. Each
              record's 'category' field (lowercase with spaces, e.g. 'cell biology')
              is still checked client-side as a safety net; the hyphenated CLI form
              ('cell-biology') is normalized to match.
      
              Args:
                  start_date: Start date in YYYY-MM-DD format
                  end_date: End date in YYYY-MM-DD format
                  category: Optional category filter (hyphenated or spaced form)
                  max_results: Optional cap on records fetched (caps API calls too)
      
              Returns:
                  List of preprint dictionaries
              """
              self._log(f"Searching bioRxiv from {start_date} to {end_date}")
      
              wanted_cat = self._normalize_category(category) if category else None
              params = {"category": wanted_cat.replace(" ", "_")} if wanted_cat else None
              results: List[Dict] = []
              cursor = 0
              total = None
      
              while True:
                  # Date-range form: details/biorxiv/{start}/{end}/{cursor}/json
                  endpoint = f"details/biorxiv/{start_date}/{end_date}/{cursor}/json"
                  data = self._make_request(endpoint, params=params)
      
                  messages = data.get("messages") or [{}]
                  status = messages[0].get("status")
                  if status and status != "ok":
                      self._log(f"API status: {status} - {messages[0]}")
                      break
      
                  collection = data.get("collection", [])
                  if not collection:
                      break
      
                  for paper in collection:
                      if wanted_cat and self._normalize_category(paper.get("category", "")) != wanted_cat:
                          continue
                      results.append(paper)
                      if max_results and len(results) >= max_results:
                          self._log(f"Reached max_results={max_results}; stopping pagination")
                          return results
      
                  # Determine total once and decide whether to continue paginating.
                  if total is None:
                      try:
                          total = int(messages[0].get("total", len(collection)))
                      except (TypeError, ValueError):
                          total = len(collection)
                      self._log(f"Total records in range: {total}")
      
                  cursor += self.PAGE_SIZE
                  if cursor >= total:
                      break
      
              self._log(f"Found {len(results)} preprints (after any category filter)")
              return results
      
          def search_by_interval(
              self,
              interval: str = "1",
              cursor: int = 0,
              format: str = "json"
          ) -> Dict:
              """
              Retrieve one page (30 records) of the most recent preprints.
      
              Args:
                  interval: "N" for the N most recent posts, or "Nd" for posts from
                      the last N days (e.g. "7d")
                  cursor: Pagination cursor (0, 30, 60, ...)
                  format: Response format ('json' or 'xml')
      
              Returns:
                  Dictionary with collection and pagination info
              """
              # /details serves preprints; /pubs would return journal-publication
              # links instead, which is not what this method promises.
              endpoint = f"details/biorxiv/{interval}/{cursor}/{format}"
              return self._make_request(endpoint)
      
          def get_paper_details(self, doi: str) -> Dict:
              """
              Get detailed information about a specific paper by DOI.
      
              Args:
                  doi: The DOI of the paper (e.g. '10.1101/2021.01.01.123456' or,
                      for preprints posted since Dec 2025, '10.64898/2026.08.28.747819');
                      doi.org / biorxiv.org URLs are accepted too
      
              Returns:
                  Dictionary with paper details
              """
              doi = normalize_doi(doi)
      
              self._log(f"Fetching details for DOI: {doi}")
              # Documented DOI form: details/biorxiv/{doi}/na/json
              endpoint = f"details/biorxiv/{doi}/na/json"
      
              data = self._make_request(endpoint)
      
              collection = data.get("collection", [])
              if collection:
                  # The API returns one entry per version, ordered ascending;
                  # return the latest version.
                  return collection[-1]
      
              return {}
      
          def search_by_author(
              self,
              author_name: str,
              start_date: Optional[str] = None,
              end_date: Optional[str] = None,
              category: Optional[str] = None
          ) -> List[Dict]:
              """
              Search for papers by author name.
      
              Args:
                  author_name: Author name to search for
                  start_date: Optional start date (YYYY-MM-DD); defaults to one year back
                  end_date: Optional end date (YYYY-MM-DD); defaults to today
                  category: Optional category filter (applied server-side)
      
              Returns:
                  List of matching preprints
              """
              # Every paper in the window is fetched before filtering, so default to
              # the last year rather than a multi-year scan.
              end_date = end_date or datetime.now().strftime("%Y-%m-%d")
              if not start_date:
                  start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
      
              self._log(f"Searching for author: {author_name}")
      
              # Get all papers in date range
              papers = self.search_by_date_range(start_date, end_date, category)
      
              # Filter by author name (case-insensitive)
              author_lower = author_name.lower()
              matching_papers = []
      
              for paper in papers:
                  authors = paper.get("authors", "")
                  if author_lower in authors.lower():
                      matching_papers.append(paper)
      
              self._log(f"Found {len(matching_papers)} papers by {author_name}")
              return matching_papers
      
          def search_by_keywords(
              self,
              keywords: List[str],
              start_date: Optional[str] = None,
              end_date: Optional[str] = None,
              category: Optional[str] = None,
              search_fields: List[str] = ["title", "abstract"]
          ) -> List[Dict]:
              """
              Search for papers containing specific keywords.
      
              Args:
                  keywords: List of keywords to search for
                  start_date: Optional start date (YYYY-MM-DD)
                  end_date: Optional end date (YYYY-MM-DD)
                  category: Optional category filter
                  search_fields: Fields to search in (title, abstract, authors)
      
              Returns:
                  List of matching preprints
              """
              # If no date range specified, search last year
              end_date = end_date or datetime.now().strftime("%Y-%m-%d")
              if not start_date:
                  start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
      
              self._log(f"Searching for keywords: {keywords}")
      
              # Get all papers in date range
              papers = self.search_by_date_range(start_date, end_date, category)
      
              # Filter by keywords
              matching_papers = []
              keywords_lower = [k.lower() for k in keywords]
      
              for paper in papers:
                  # Build search text from specified fields
                  search_text = ""
                  for field in search_fields:
                      if field in paper:
                          search_text += " " + str(paper[field]).lower()
      
                  # Check if any keyword matches
                  if any(keyword in search_text for keyword in keywords_lower):
                      matching_papers.append(paper)
      
              self._log(f"Found {len(matching_papers)} papers matching keywords")
              return matching_papers
      
          def download_pdf(self, doi: str, output_path: str,
                           version: Optional[str] = None) -> bool:
              """
              Download the PDF of a paper.
      
              Args:
                  doi: The DOI of the paper
                  output_path: Path where PDF should be saved
                  version: Optional version number (e.g. '2'). If omitted, the latest
                      version is looked up via get_paper_details(); falls back to v1.
      
              Returns:
                  True if download successful, False otherwise
              """
              doi = normalize_doi(doi)
      
              # Resolve the version so revised preprints get the right PDF.
              if version is None:
                  details = self.get_paper_details(doi)
                  version = details.get("version") or "1"
      
              # Construct PDF URL (bioRxiv PDFs are served from www, not the API host).
              pdf_url = f"https://www.biorxiv.org/content/{doi}v{version}.full.pdf"
      
              self._log(f"Downloading PDF from: {pdf_url}")
      
              try:
                  response = self.session.get(pdf_url, timeout=60)
                  response.raise_for_status()
      
                  with open(output_path, 'wb') as f:
                      f.write(response.content)
      
                  self._log(f"PDF saved to: {output_path}")
                  return True
              except Exception as e:
                  self._log(f"Error downloading PDF: {e}")
                  return False
      
          def format_result(self, paper: Dict, include_abstract: bool = True) -> Dict:
              """
              Format a paper result with standardized fields.
      
              Args:
                  paper: Raw paper dictionary from API
                  include_abstract: Whether to include the abstract
      
              Returns:
                  Formatted paper dictionary
              """
              result = {
                  "doi": paper.get("doi", ""),
                  "title": paper.get("title", ""),
                  "authors": paper.get("authors", ""),
                  "author_corresponding": paper.get("author_corresponding", ""),
                  "author_corresponding_institution": paper.get("author_corresponding_institution", ""),
                  "date": paper.get("date", ""),
                  "version": paper.get("version", ""),
                  "type": paper.get("type", ""),
                  "license": paper.get("license", ""),
                  "category": paper.get("category", ""),
                  "jatsxml": paper.get("jatsxml", ""),
                  "published": paper.get("published", "")
              }
      
              if include_abstract:
                  result["abstract"] = paper.get("abstract", "")
      
              # Add PDF and HTML URLs (fall back to v1 if version is missing).
              if result["doi"]:
                  version = result["version"] or "1"
                  result["pdf_url"] = f"https://www.biorxiv.org/content/{result['doi']}v{version}.full.pdf"
                  result["html_url"] = f"https://www.biorxiv.org/content/{result['doi']}v{version}"
      
              return result
      
      
      def main():
          """Command-line interface for bioRxiv search."""
          parser = argparse.ArgumentParser(
              description="Search bioRxiv preprints efficiently",
              formatter_class=argparse.RawDescriptionHelpFormatter
          )
      
          parser.add_argument("--verbose", "-v", action="store_true",
                             help="Enable verbose logging")
      
          # Search type arguments
          search_group = parser.add_argument_group("Search options")
          search_group.add_argument("--keywords", "-k", nargs="+",
                                  help="Keywords to search for")
          search_group.add_argument("--author", "-a",
                                  help="Author name to search for")
          search_group.add_argument("--doi",
                                  help="Get details for specific DOI")
      
          # Date range arguments
          date_group = parser.add_argument_group("Date range options")
          date_group.add_argument("--start-date",
                                help="Start date (YYYY-MM-DD)")
          date_group.add_argument("--end-date",
                                help="End date (YYYY-MM-DD)")
          date_group.add_argument("--days-back", type=int,
                                help="Search N days back from today")
      
          # Filter arguments
          filter_group = parser.add_argument_group("Filter options")
          filter_group.add_argument("--category", "-c",
                                  choices=BioRxivSearcher.CATEGORIES,
                                  help="Filter by category")
          filter_group.add_argument("--search-fields", nargs="+",
                                  default=["title", "abstract"],
                                  choices=["title", "abstract", "authors"],
                                  help="Fields to search in for keywords")
      
          # Output arguments
          output_group = parser.add_argument_group("Output options")
          output_group.add_argument("--output", "-o",
                                  help="Output file (default: stdout)")
          output_group.add_argument("--include-abstract", action="store_true",
                                  default=True, help="Include abstracts in output")
          output_group.add_argument("--download-pdf",
                                  help="Download PDF to specified path (requires --doi)")
          output_group.add_argument("--limit", type=int,
                                  help="Limit number of results")
      
          args = parser.parse_args()
      
          # Initialize searcher
          searcher = BioRxivSearcher(verbose=args.verbose)
      
          # Handle date range
          end_date = args.end_date or datetime.now().strftime("%Y-%m-%d")
          if args.days_back:
              start_date = (datetime.now() - timedelta(days=args.days_back)).strftime("%Y-%m-%d")
          else:
              start_date = args.start_date
      
          # Execute search based on arguments
          results = []
      
          if args.download_pdf:
              if not args.doi:
                  print("Error: --doi required with --download-pdf", file=sys.stderr)
                  return 1
      
              success = searcher.download_pdf(args.doi, args.download_pdf)
              if not success:
                  print("Error: PDF download failed. www.biorxiv.org sits behind Cloudflare and "
                        "may answer scripted requests with HTTP 429; retry later, open the "
                        "html_url in a browser, or use the text-mining bucket "
                        "s3://biorxiv-src-monthly for bulk full text.", file=sys.stderr)
              return 0 if success else 1
      
          elif args.doi:
              # Get specific paper by DOI
              paper = searcher.get_paper_details(args.doi)
              if paper:
                  results = [paper]
      
          elif args.author:
              # Search by author
              results = searcher.search_by_author(
                  args.author, start_date, end_date, args.category
              )
      
          elif args.keywords:
              # Search by keywords
              if not start_date:
                  print("Error: --start-date or --days-back required for keyword search",
                        file=sys.stderr)
                  return 1
      
              results = searcher.search_by_keywords(
                  args.keywords, start_date, end_date,
                  args.category, args.search_fields
              )
      
          else:
              # Date range search
              if not start_date:
                  print("Error: Must specify search criteria (--keywords, --author, or --doi)",
                        file=sys.stderr)
                  return 1
      
              results = searcher.search_by_date_range(
                  start_date, end_date, args.category, max_results=args.limit
              )
      
          # Apply limit
          if args.limit:
              results = results[:args.limit]
      
          # Format results
          formatted_results = [
              searcher.format_result(paper, args.include_abstract)
              for paper in results
          ]
      
          # Output results
          output_data = {
              "query": {
                  "keywords": args.keywords,
                  "author": args.author,
                  "doi": args.doi,
                  "start_date": start_date,
                  "end_date": end_date,
                  "category": args.category
              },
              "result_count": len(formatted_results),
              "results": formatted_results
          }
      
          output_json = json.dumps(output_data, indent=2)
      
          if args.output:
              with open(args.output, 'w') as f:
                  f.write(output_json)
              print(f"Results written to {args.output}", file=sys.stderr)
          else:
              print(output_json)
      
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
  • SKILL.md 14.4 KB
    ---
    name: alterlab-biorxiv
    description: Search the bioRxiv preprint server and retrieve paper metadata or download PDFs via its API. Use when finding life sciences preprints by keywords, authors, DOI, date ranges, or categories, or when conducting a biology literature review of not-yet-peer-reviewed work. Part of the AlterLab Academic Skills suite.
    license: MIT
    allowed-tools: Read WebFetch Bash(curl:*) Bash(python:*)
    compatibility: Keyless bioRxiv API (no authentication required)
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # bioRxiv Database
    
    ## Overview
    
    Python tooling over the keyless bioRxiv API for searching and retrieving **life-sciences preprints**. Searches by keyword, author, date range, and category, returning structured JSON (titles, abstracts, DOIs, authors, versions), and downloads full-text PDFs.
    
    For **published, peer-reviewed** literature use `alterlab-pubmed`; for **computer-science / physics / math** preprints use `alterlab-arxiv`. bioRxiv covers biology subjects only. The same API also serves **medRxiv** (swap `biorxiv` for `medrxiv` in the endpoint path, e.g. `/details/medrxiv/2025-03-21/2025-03-28?category=cardiovascular%20medicine`); the bundled script targets bioRxiv, so query medRxiv with `curl`/`requests` directly.
    
    ### How it works (and its limits)
    
    The bioRxiv `/details` endpoint returns preprints by date range, 30 records per page. It accepts a server-side **subject-category** filter (`?category=cell_biology`), but has **no keyword or author filter**. So this tool:
    
    1. Paginates the date range (following the cursor until all records are retrieved), passing `--category` to the server so only that subject is fetched, then
    2. Filters **client-side** by keyword (substring over title/abstract) and author (substring over the author list).
    
    Implication: a wide date range means many API calls and a large download. Keep ranges as tight as the question allows, and use `--category` (cuts API calls) and `--limit` to bound the work.
    
    ## When to Use This Skill
    
    Use this skill when:
    - Searching for recent life-sciences preprints in specific research areas
    - Tracking preprints by particular authors
    - Conducting systematic preprint literature reviews
    - Analyzing preprint trends over time periods
    - Retrieving metadata for citation management
    - Downloading preprint PDFs for analysis
    - Filtering papers by bioRxiv subject categories
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Peer-reviewed, MeSH-indexed biomedical journal articles | `alterlab-pubmed` |
    | CS / physics / math / quantitative-biology preprints on arXiv | `alterlab-arxiv` |
    | Citation counts or cross-publisher bibliometrics for preprints | `alterlab-openalex` |
    | Depositing your own preprint (server choice, license, versioning) | `alterlab-preprint-deposition` |
    | Multi-database systematic review with PRISMA screening | `alterlab-literature-review` |
    
    ## Running the script
    
    The script's only dependency is `requests`. Run it with uv so the dependency is provisioned on the fly:
    
    ```bash
    uv run --with requests scripts/biorxiv_search.py --help
    ```
    
    The `python scripts/biorxiv_search.py ...` invocations below are shorthand; substitute `uv run --with requests scripts/biorxiv_search.py ...` (or activate an environment that has `requests`).
    
    ## Core Search Capabilities
    
    ### 1. Keyword Search
    
    Search for preprints containing specific keywords in titles, abstracts, or author lists.
    
    **Basic Usage:**
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "CRISPR" "gene editing" \
      --start-date 2024-01-01 \
      --end-date 2024-12-31 \
      --output results.json
    ```
    
    **With Category Filter:**
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "neural networks" "deep learning" \
      --days-back 180 \
      --category neuroscience \
      --output recent_neuroscience.json
    ```
    
    **Search Fields:**
    Keyword matching is a case-insensitive substring match, and a paper matches if **any** keyword is found (OR semantics, not AND). By default keywords are searched in both title and abstract. Customize with `--search-fields`:
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "AlphaFold" \
      --search-fields title \
      --days-back 365
    ```
    
    ### 2. Author Search
    
    Find all papers by a specific author within a date range.
    
    **Basic Usage:**
    ```bash
    python scripts/biorxiv_search.py \
      --author "Smith" \
      --start-date 2023-01-01 \
      --end-date 2024-12-31 \
      --output smith_papers.json
    ```
    
    **Recent Publications:**
    ```bash
    # Last year by default if no dates specified
    python scripts/biorxiv_search.py \
      --author "Johnson" \
      --output johnson_recent.json
    ```
    
    ### 3. Date Range Search
    
    Retrieve all preprints posted within a specific date range.
    
    **Basic Usage:**
    ```bash
    python scripts/biorxiv_search.py \
      --start-date 2024-01-01 \
      --end-date 2024-01-31 \
      --output january_2024.json
    ```
    
    **With Category Filter:**
    ```bash
    python scripts/biorxiv_search.py \
      --start-date 2024-06-01 \
      --end-date 2024-06-30 \
      --category genomics \
      --output genomics_june.json
    ```
    
    **Days Back Shortcut:**
    ```bash
    # Last 30 days
    python scripts/biorxiv_search.py \
      --days-back 30 \
      --output last_month.json
    ```
    
    ### 4. Paper Details by DOI
    
    Retrieve detailed metadata for a specific preprint. bioRxiv DOIs come in two prefixes:
    `10.1101/…` for older preprints and `10.64898/…` for preprints posted since the move to
    openRxiv (December 2025), e.g. `10.64898/2026.08.28.747819`. Both work with `/details/`
    and the `www.biorxiv.org/content/` URLs; the script normalizes DOIs, doi.org links, and
    content URLs regardless of prefix. Don't hard-code `10.1101` in regexes or validators.
    
    **Basic Usage:**
    ```bash
    python scripts/biorxiv_search.py \
      --doi "10.1101/2024.01.15.123456" \
      --output paper_details.json
    ```
    
    **Full DOI URLs Accepted (either prefix):**
    ```bash
    python scripts/biorxiv_search.py \
      --doi "https://doi.org/10.64898/2026.08.28.747819"
    ```
    
    ### 5. PDF Downloads
    
    Download the full-text PDF of any preprint.
    
    **Basic Usage:**
    ```bash
    python scripts/biorxiv_search.py \
      --doi "10.1101/2024.01.15.123456" \
      --download-pdf paper.pdf
    ```
    
    **Batch Processing:**
    For multiple PDFs, extract DOIs from a search result JSON and download each paper:
    ```python
    import json
    from biorxiv_search import BioRxivSearcher
    
    # Load search results
    with open('results.json') as f:
        data = json.load(f)
    
    searcher = BioRxivSearcher(verbose=True)
    
    # Download each paper
    for i, paper in enumerate(data['results'][:10]):  # First 10 papers
        doi = paper['doi']
        searcher.download_pdf(doi, f"papers/paper_{i+1}.pdf")
    ```
    
    ## Valid Categories
    
    Filter searches by bioRxiv subject categories:
    
    - `animal-behavior-and-cognition`
    - `biochemistry`
    - `bioengineering`
    - `bioinformatics`
    - `biophysics`
    - `cancer-biology`
    - `cell-biology`
    - `clinical-trials`
    - `developmental-biology`
    - `ecology`
    - `epidemiology`
    - `evolutionary-biology`
    - `genetics`
    - `genomics`
    - `immunology`
    - `microbiology`
    - `molecular-biology`
    - `neuroscience`
    - `paleontology`
    - `pathology`
    - `pharmacology-and-toxicology`
    - `physiology`
    - `plant-biology`
    - `scientific-communication-and-education`
    - `synthetic-biology`
    - `systems-biology`
    - `zoology`
    
    ## Output Format
    
    All searches return structured JSON with the following format:
    
    ```json
    {
      "query": {
        "keywords": ["CRISPR"],
        "start_date": "2024-01-01",
        "end_date": "2024-12-31",
        "category": "genomics"
      },
      "result_count": 42,
      "results": [
        {
          "doi": "10.1101/2024.01.15.123456",
          "title": "Paper Title Here",
          "authors": "Smith, J.; Doe, J.; Johnson, A.",
          "author_corresponding": "Smith J",
          "author_corresponding_institution": "University Example",
          "date": "2024-01-15",
          "version": "1",
          "type": "new results",
          "license": "cc_by",
          "category": "genomics",
          "abstract": "Full abstract text...",
          "pdf_url": "https://www.biorxiv.org/content/10.1101/2024.01.15.123456v1.full.pdf",
          "html_url": "https://www.biorxiv.org/content/10.1101/2024.01.15.123456v1",
          "jatsxml": "https://www.biorxiv.org/content/...",
          "published": ""
        }
      ]
    }
    ```
    
    ## Common Usage Patterns
    
    ### Literature Review Workflow
    
    1. **Broad keyword search:**
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "organoids" "tissue engineering" \
      --start-date 2023-01-01 \
      --end-date 2024-12-31 \
      --category bioengineering \
      --output organoid_papers.json
    ```
    
    2. **Extract and review results:**
    ```python
    import json
    
    with open('organoid_papers.json') as f:
        data = json.load(f)
    
    print(f"Found {data['result_count']} papers")
    
    for paper in data['results'][:5]:
        print(f"\nTitle: {paper['title']}")
        print(f"Authors: {paper['authors']}")
        print(f"Date: {paper['date']}")
        print(f"DOI: {paper['doi']}")
    ```
    
    3. **Download selected papers:**
    ```python
    from biorxiv_search import BioRxivSearcher
    
    searcher = BioRxivSearcher()
    selected_dois = ["10.1101/2024.01.15.123456", "10.1101/2024.02.20.789012"]
    
    for doi in selected_dois:
        filename = doi.replace("/", "_").replace(".", "_") + ".pdf"
        searcher.download_pdf(doi, f"papers/{filename}")
    ```
    
    ### Trend Analysis
    
    Track research trends by analyzing publication frequencies over time:
    
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "machine learning" \
      --start-date 2020-01-01 \
      --end-date 2024-12-31 \
      --category bioinformatics \
      --output ml_trends.json
    ```
    
    Then analyze the temporal distribution in the results.
    
    ### Author Tracking
    
    Monitor specific researchers' preprints:
    
    ```bash
    # Track multiple authors (each run scans the whole window, so keep it short)
    for author in Smith Johnson Williams; do
      python scripts/biorxiv_search.py \
        --author "$author" \
        --days-back 365 \
        --output "${author}_papers.json"
    done
    ```
    
    ## Python API Usage
    
    For more complex workflows, import and use the `BioRxivSearcher` class directly:
    
    ```python
    from scripts.biorxiv_search import BioRxivSearcher
    
    # Initialize
    searcher = BioRxivSearcher(verbose=True)
    
    # Multiple search operations
    keywords_papers = searcher.search_by_keywords(
        keywords=["CRISPR", "gene editing"],
        start_date="2024-01-01",
        end_date="2024-12-31",
        category="genomics"
    )
    
    author_papers = searcher.search_by_author(
        author_name="Smith",
        start_date="2023-01-01",
        end_date="2024-12-31"
    )
    
    # Get specific paper details
    paper = searcher.get_paper_details("10.1101/2024.01.15.123456")
    
    # Download PDF
    success = searcher.download_pdf(
        doi="10.1101/2024.01.15.123456",
        output_path="paper.pdf"
    )
    
    # Format results consistently
    formatted = searcher.format_result(paper, include_abstract=True)
    ```
    
    ## Best Practices
    
    1. **Keep date ranges tight**: Because filtering is client-side, the tool paginates the *entire* range (30 records/page) before filtering. A single busy week is ~800 preprints (~27 API calls); a full year is tens of thousands. Narrow the range, or use `--days-back` for recency.
    
    2. **Filter by category**: Use `--category` whenever the subject is known. It is sent to the server (`?category=`), so it cuts both the result set and the number of API calls (one busy week: ~1,270 preprints overall vs. ~80 in cell biology).
    
    3. **Cap with `--limit`**: For pure date-range searches, `--limit` also stops pagination early, so it genuinely reduces API calls. For keyword/author searches the whole range must be scanned first, so `--limit` only trims the final list.
    
    4. **Respect rate limits**: The script sleeps 0.5s between requests. There is no documented hard rate limit, but for large collections add more delay and cache results to JSON.
    
    5. **Version tracking**: Preprints can have multiple versions. DOI lookups return the **latest** version; `download_pdf` resolves the latest version automatically (pass `version=` to override). PDF/HTML URLs embed the version number. The `published` field of `/details/` carries the journal DOI once the preprint is published (or `NA`) — use it rather than the per-DOI `/pubs/` lookup, which returns nothing for `10.64898` DOIs.
    
    6. **PDF downloads can be throttled**: PDFs come from `www.biorxiv.org`, which sits behind Cloudflare and may answer scripted requests with HTTP 429. Space downloads out, fall back to the `html_url`, and for bulk full text use bioRxiv's requester-pays text-mining bucket `s3://biorxiv-src-monthly` (MECA zip packages; see https://www.biorxiv.org/tdm).
    
    7. **Handle empty results**: Check `result_count`. Empty results usually mean the date range had no matching papers, an over-narrow category, or transient API connectivity issues — not a silent truncation (pagination retrieves the full range).
    
    8. **Verbose mode for debugging**: Use `--verbose` to see each paginated API request and the reported `total`.
    
    ## Advanced Features
    
    ### Custom Date Range Logic
    
    ```python
    from datetime import datetime, timedelta
    from scripts.biorxiv_search import BioRxivSearcher
    
    # Last quarter
    end_date = datetime.now()
    start_date = end_date - timedelta(days=90)
    
    papers = BioRxivSearcher().search_by_date_range(
        start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"), category="genomics"
    )
    ```
    
    ### Result Limiting
    
    Limit the number of results returned:
    
    ```bash
    python scripts/biorxiv_search.py \
      --keywords "COVID-19" \
      --days-back 30 \
      --limit 50 \
      --output covid_top50.json
    ```
    
    ### Exclude Abstracts for Speed
    
    When only metadata is needed:
    
    ```python
    # Note: Abstract inclusion is controlled in Python API
    from scripts.biorxiv_search import BioRxivSearcher
    
    searcher = BioRxivSearcher()
    papers = searcher.search_by_keywords(keywords=["AI"], days_back=30)
    formatted = [searcher.format_result(p, include_abstract=False) for p in papers]
    ```
    
    ## Programmatic Integration
    
    Integrate search results into downstream analysis pipelines:
    
    ```python
    import json
    import pandas as pd
    
    # Load results
    with open('results.json') as f:
        data = json.load(f)
    
    # Convert to DataFrame for analysis
    df = pd.DataFrame(data['results'])
    
    # Analyze
    print(f"Total papers: {len(df)}")
    print(f"Date range: {df['date'].min()} to {df['date'].max()}")
    print(f"\nTop authors by paper count:")
    print(df['authors'].str.split(';').explode().str.strip().value_counts().head(10))
    
    # Filter and export
    recent = df[df['date'] >= '2024-06-01']
    recent.to_csv('recent_papers.csv', index=False)
    ```
    
    ## Reference Documentation
    
    For detailed API specifications, endpoint documentation, and response schemas, refer to:
    - `references/api_reference.md` - Complete bioRxiv API documentation
    
    The reference file includes:
    - Full API endpoint specifications
    - Response format details
    - Error handling patterns
    - Rate limiting guidelines
    - Advanced search patterns
    
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related