# Bio Core Biological Databases

> Fetch sequences via NCBI Entrez (esearch/efetch/elink), UniProt REST, and RCSB PDB APIs with BioPython/urllib. Use when picking a database, decoding accession prefixes (NM_/XM_/GSE/SRR), or cross-linking a gene NCBI-UniProt-PDB.

- Skill: `pavel-kravchenko/bio-core-biological-databases` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/bio-core-biological-databases`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/bio-core-biological-databases/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: pavel-kravchenko (https://skillmd.com/u/pavel-kravchenko)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pavel-kravchenko/bio-core-biological-databases

---


# Biological Databases

## When to Use
- Deciding which database (NCBI, UniProt, PDB, Ensembl, GEO, SRA) holds the data you actually need
- Writing `Bio.Entrez` esearch/efetch/elink code to pull sequences, GenBank annotations, or PubMed abstracts
- Resolving accession types (`NM_`/`NP_`/`XM_`/`GSE`/`SRR`) and curated-vs-predicted status
- Fetching protein records from UniProt or 3D structures from PDB by accession/ID
- Chaining a lookup across databases: gene name -> RefSeq mRNA -> protein -> structure -> orthologs

## Version Compatibility
- biopython >= 1.81, Python >= 3.10
- UniProt REST API (`rest.uniprot.org`), current JSON schema (2024+)
- RCSB PDB Search API v2 and Data API v1 (`search.rcsb.org`, `data.rcsb.org`)
- Ensembl REST API (`rest.ensembl.org`), Ensembl release 110+

## Prerequisites
- `pip install biopython`
- stdlib only otherwise: `urllib.request`, `json`, `urllib.parse`
- Set `Entrez.email` (required by NCBI); get a free NCBI API key to raise rate limits
- Helpful follow-on skills: `bio-sequence-manipulation-seq-objects`, `bio-structural-biology-structure-io`

## Database Selector

| Need | Database | Preferred subset |
|------|----------|-------------------|
| Gene info + orthologs | NCBI Gene / Ensembl | Ensembl Compara for orthologs |
| mRNA/protein sequence | NCBI Nucleotide / Protein | RefSeq (`NM_`, `NP_`, `NC_`) |
| Protein function/structure refs | UniProt | SwissProt (`reviewed:true`) |
| 3D structure | PDB (RCSB) | check resolution + method |
| Raw sequencing reads | SRA | SRR accession |
| Processed expression data | GEO | GSE (series), GSM (sample) |
| Literature | PubMed | — |

## NCBI Accession Prefixes

| Prefix | Type | Curated? |
|--------|------|----------|
| `NM_` | curated mRNA | Yes (RefSeq) |
| `NP_` | curated protein | Yes (RefSeq) |
| `NC_` | chromosome/complete genome | Yes (RefSeq) |
| `XM_`/`XP_` | predicted/model mRNA/protein | No |
| `NR_` | non-coding RNA | Yes (RefSeq) |

GEO accessions: `GSE` (series/experiment), `GSM` (sample), `GPL` (platform), `GDS` (curated dataset).
SRA hierarchy: Study (`SRP`) > Sample (`SRS`) > Experiment (`SRX`) > Run (`SRR`).

**Goal:** search, fetch, and cross-link NCBI records without exhausting rate limits.
**Approach:** use `Entrez.esearch`/`efetch`/`elink`; always batch IDs and close handles.

```python
from Bio import Entrez, SeqIO

Entrez.email = "your.email@example.com"  # required by NCBI
# Entrez.api_key = "..."                 # optional, raises limit 3 -> 10 req/s

def fetch_refseq_mrna(gene, organism="Homo sapiens"):
    """Find and fetch the top RefSeq mRNA GenBank record for a gene symbol."""
    handle = Entrez.esearch(
        db="nucleotide",
        term=f"{gene}[Gene] AND {organism}[Organism] AND RefSeq[Filter] AND mRNA[Filter]",
        retmax=1,
    )
    ids = Entrez.read(handle)["IdList"]
    handle.close()
    if not ids:
        return None
    handle = Entrez.efetch(db="nucleotide", id=ids[0], rettype="gb", retmode="text")
    record = SeqIO.read(handle, "genbank")
    handle.close()
    return record

record = fetch_refseq_mrna("insulin")  # NM_000207.3

# Extract CDS and translate directly from GenBank features
for feat in record.features:
    if feat.type == "CDS":
        cds_seq = feat.location.extract(record.seq)
        protein = cds_seq.translate(to_stop=True)
        annotated = feat.qualifiers.get("translation", [""])[0]

# Cross-database link: nucleotide -> protein
handle = Entrez.elink(dbfrom="nucleotide", db="protein", id="NM_000207.3")
link_results = Entrez.read(handle)
handle.close()

# Batch fetch (comma-separated IDs — much faster than looping efetch calls)
handle = Entrez.efetch(db="nucleotide", id="NM_000207.3,NM_001301717.2",
                        rettype="fasta", retmode="text")
```

Entrez search syntax: `insulin[Gene]`, `Homo sapiens[Organism]`, `mRNA[Filter] AND RefSeq[Filter]`,
`2020:2024[PDAT]` (date range), `CRISPR AND (cancer OR tumor)`.

**Goal:** pull a protein record and its structural cross-references from UniProt.
**Approach:** hit the REST API directly with `urllib` — no extra dependency needed.

```python
import json
import urllib.parse
import urllib.request


def fetch_uniprot(accession, fmt="json"):
    """Fetch one UniProt entry by accession (e.g. 'P01308' = human insulin)."""
    url = f"https://rest.uniprot.org/uniprotkb/{accession}.{fmt}"
    with urllib.request.urlopen(url) as r:
        return json.loads(r.read()) if fmt == "json" else r.read().decode()


def search_uniprot(query, limit=5):
    """Search UniProt; e.g. query='hemoglobin AND organism_id:9606 AND reviewed:true'."""
    encoded = urllib.parse.quote(query)
    url = f"https://rest.uniprot.org/uniprotkb/search?query={encoded}&size={limit}&format=json"
    with urllib.request.urlopen(url) as r:
        return json.loads(r.read())


insulin = fetch_uniprot("P01308")
name = insulin["proteinDescription"]["recommendedName"]["fullName"]["value"]
seq = insulin["sequence"]["value"]

# Cross-references to PDB structures embedded in the UniProt record
pdb_refs = [r for r in insulin.get("uniProtKBCrossReferences", []) if r["database"] == "PDB"]
```

**Goal:** fetch and parse a 3D structure once you have a PDB ID (e.g. from the UniProt cross-refs above).
**Approach:** RCSB search API for discovery, `files.rcsb.org` for the actual coordinate file.

```python
def download_pdb(pdb_id, fmt="pdb"):
    """Download a structure file from RCSB PDB ('pdb' or 'cif' format)."""
    ext = "pdb" if fmt == "pdb" else "cif"
    url = f"https://files.rcsb.org/download/{pdb_id}.{ext}"
    filename = f"{pdb_id}.{ext}"
    urllib.request.urlretrieve(url, filename)
    return filename

path = download_pdb("4INS")  # classic insulin hexamer crystal structure
```

## Pitfalls

- **GenBank vs RefSeq**: GenBank has all submitted sequences (redundant, unreviewed); RefSeq is curated. Prefer RefSeq (`NM_`/`NP_`) for analysis
- **SwissProt vs TrEMBL**: SwissProt (~570K entries, manually curated) vs TrEMBL (~250M, auto-annotated); filter with `reviewed:true` for reliable annotations
- **Rate limits**: NCBI allows 3 req/s without an API key, 10/s with one (`Entrez.api_key = "..."`); batch with comma-separated IDs, never loop individual `efetch` calls
- **GI numbers deprecated**: use accession.version (`NM_000207.3`), not numeric GI IDs
- **`efetch` handle must be closed**: always `handle.close()` (or use `with`) — unclosed handles exhaust NCBI connections
- **`id` as a list breaks `efetch`**: pass a comma-separated string, not a Python list — a common LLM-generated bug
- **PDB resolution matters**: structures worse than ~3.5 Å only give reliable overall fold, not side-chain/atomic detail

## See Also
- `bio-database-access-entrez-search`, `bio-database-access-entrez-fetch`, `bio-database-access-entrez-link`
- `bio-database-access-uniprot-access`
- `bio-structural-biology-structure-io`
- `bio-database-access-geo-data`, `bio-database-access-sra-data`

