Install any skill in seconds. Free to start, no credit card required.
Get Started Free →NCBI Gene via E-utilities: curated records across 1M+ taxa. Official symbols, aliases, RefSeq IDs, summaries, coordinates, GO, interactions. Use for gene ID resolution and cross-species function queries. For sequences use Ensembl; for expression use geo-database.
.claude/skills/jaechang-hits-gene-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 132% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 178% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 130% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 211% | 0% |
NCBI Gene is the authoritative curated database for gene-centric information, covering 1M+ genes across hundreds of thousands of taxa. Each gene record includes the official symbol, aliases, full name, functional summary, genomic coordinates (GRCh38/GRCh37), RefSeq accessions, GO annotations, interaction partners, and links to related databases. Access is free via E-utilities REST API (no API key required, though recommended).
gene_gene_homolog retired with HomoloGene in 2019)geo-database; for variant annotations use clinvar-database or ensembl-databaserequests, xml.etree.ElementTree (stdlib), pandas (optional)email parameter)bashpip install requests pandas
pythonimport requests EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" def gene_search(query, retmax=5): r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "term": query, "retmax": retmax, "retmode": "json", "email": EMAIL}) r.raise_for_status() return r.json()["esearchresult"]["idlist"] # Find human BRCA1 gene ID ids = gene_search("BRCA1[sym] AND Homo sapiens[orgn]") print(f"Gene IDs for BRCA1: {ids}") # → ['672']
Use ESearch with field tags for precise queries.
pythonimport requests EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" # Exact symbol match for human gene r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "term": "TP53[sym] AND Homo sapiens[orgn] AND alive[prop]"}) ids = r.json()["esearchresult"]["idlist"] print(f"TP53 Gene ID: {ids}") # → ['7157']
python# Search by function keyword r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "term": "CRISPR[title] AND Homo sapiens[orgn]", "retmax": 5}) ids = r.json()["esearchresult"]["idlist"] print(f"CRISPR-related gene IDs: {ids}")
Retrieve key metadata fields for a list of Gene IDs.
pythonimport requests EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" def esummary_gene(gene_ids): r = requests.post(f"{BASE}/esummary.fcgi", data={"db": "gene", "id": ",".join(gene_ids), "retmode": "json", "email": EMAIL}) r.raise_for_status() return r.json()["result"] result = esummary_gene(["672", "675", "7157"]) # BRCA1, BRCA2, TP53 for uid in result.get("uids", []): g = result[uid] print(f"\n{g.get('name')} (ID {uid})") print(f" Official symbol : {g.get('nomenclaturesymbol', g.get('name'))}") print(f" Chr location : {g.get('maplocation')}") print(f" Summary (first 100): {g.get('summary', '')[:100]}...") print(f" Aliases: {g.get('otheraliases', 'none')}")
Retrieve the complete gene record in XML for RefSeq accessions, GO terms, and interaction data.
pythonimport requests import xml.etree.ElementTree as ET EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" def efetch_gene_xml(gene_id): r = requests.get(f"{BASE}/efetch.fcgi", params={"db": "gene", "id": gene_id, "rettype": "gene_table", "retmode": "text", "email": EMAIL}) r.raise_for_status() return r.text # Get gene table (tab-delimited overview) table = efetch_gene_xml("672") print(table[:500])
python# XML for RefSeq accession extraction r = requests.get(f"{BASE}/efetch.fcgi", params={"db": "gene", "id": "672", "rettype": "xml", "retmode": "xml", "email": EMAIL}) root = ET.fromstring(r.text) # Extract RefSeq mRNA accessions for ref in root.iter("Gene-commentary"): acc = ref.find("Gene-commentary_accession") ver = ref.find("Gene-commentary_version") typ = ref.find("Gene-commentary_type") if acc is not None and acc.text and acc.text.startswith("NM_"): print(f"RefSeq mRNA: {acc.text}.{ver.text if ver is not None else ''}")
Map a list of gene symbols to NCBI Gene IDs efficiently.
pythonimport requests, time EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" def symbols_to_ids(symbols, organism="Homo sapiens"): """Map gene symbols to NCBI Gene IDs. Returns dict {symbol: gene_id}.""" mapping = {} for sym in symbols: r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "term": f"{sym}[sym] AND {organism}[orgn] AND alive[prop]"}) ids = r.json()["esearchresult"]["idlist"] mapping[sym] = ids[0] if ids else None time.sleep(0.1) return mapping genes = ["EGFR", "KRAS", "BRAF", "PIK3CA", "PTEN"] id_map = symbols_to_ids(genes) for sym, gid in id_map.items(): print(f"{sym:10s} → Gene ID {gid}")
Parse GO terms from the gene XML record.
pythonimport requests import xml.etree.ElementTree as ET EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" r = requests.get(f"{BASE}/efetch.fcgi", params={"db": "gene", "id": "7157", "rettype": "xml", "retmode": "xml", "email": EMAIL}) root = ET.fromstring(r.text) # Extract GO annotations go_terms = [] for ref in root.iter("Gene-commentary"): heading = ref.find("Gene-commentary_heading") label = ref.find("Gene-commentary_label") if heading is not None and "Gene Ontology" in heading.text: if label is not None: go_terms.append(label.text) print(f"TP53 GO terms ({len(go_terms)} found):") for term in go_terms[:10]: print(f" {term}")
Find orthologs across species. Note: the legacy E-utilities link gene_gene_homolog was retired with HomoloGene in 2019 — the modern path is the NCBI Datasets v2 REST API, which exposes a dedicated orthologs endpoint.
pythonimport requests, time DATASETS_BASE = "https://api.ncbi.nlm.nih.gov/datasets/v2" def get_orthologs(gene_id, taxon_filter=None): """Return ortholog Gene reports for a given NCBI Gene ID. taxon_filter: optional tax_id (int) or list of tax_ids to narrow species.""" params = {} if taxon_filter is not None: # tax_ids: human=9606, mouse=10090, rat=10116, zebrafish=7955, fly=7227 ids = taxon_filter if isinstance(taxon_filter, (list, tuple)) else [taxon_filter] params["taxon_filter"] = [str(t) for t in ids] r = requests.get(f"{DATASETS_BASE}/gene/id/{gene_id}/orthologs", params=params, timeout=30) r.raise_for_status() return r.json().get("reports", []) # Mouse ortholog of human TP53 (Gene ID 7157) reports = get_orthologs("7157", taxon_filter=10090) for rep in reports[:5]: g = rep.get("gene", {}) print(f" {g.get('symbol'):8s} (tax {g.get('tax_id')}, gene_id {g.get('gene_id')}): " f"{g.get('description', '')[:60]}") # Expect: Trp53 (tax 10090, gene_id 22059): transformation related protein 53 time.sleep(0.34) # All orthologs (every species in the orthology group) all_orthologs = get_orthologs("7157") print(f"\nTotal TP53 orthologs across species: {len(all_orthologs)}")
NCBI Gene IDs are integers assigned per gene per organism (e.g., human TP53 = 7157). These are distinct from HGNC IDs (e.g., HGNC:11998) and Ensembl IDs (ENSG00000141510). Many downstream NCBI databases (ClinVar, dbSNP, GEO) use NCBI Gene IDs internally.
alive[prop] FilterNCBI Gene records for discontinued genes have status=discontinued. Always add AND alive[prop] to symbol queries to exclude retired entries and avoid retrieving stale data.
Goal: For a list of gene symbols, retrieve Gene ID, official name, chromosomal location, and description.
pythonimport requests, time, pandas as pd EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" def search_gene(sym, organism="Homo sapiens"): r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "term": f"{sym}[sym] AND {organism}[orgn] AND alive[prop]"}) ids = r.json()["esearchresult"]["idlist"] return ids[0] if ids else None def batch_summary(gene_ids): r = requests.post(f"{BASE}/esummary.fcgi", data={"db": "gene", "id": ",".join(gene_ids), "retmode": "json", "email": EMAIL}) return r.json()["result"] symbols = ["BRCA1", "BRCA2", "TP53", "EGFR", "MYC", "KRAS", "PTEN"] # Step 1: Symbol → Gene ID id_map = {} for sym in symbols: gid = search_gene(sym) id_map[sym] = gid time.sleep(0.12) # Step 2: Batch summary valid_ids = [v for v in id_map.values() if v] result = batch_summary(valid_ids) rows = [] sym_to_id = {v: k for k, v in id_map.items() if v} for uid in result.get("uids", []): g = result[uid] rows.append({ "symbol": sym_to_id.get(uid, g.get("name")), "gene_id": uid, "full_name": g.get("description"), "chr_location": g.get("maplocation"), "summary": g.get("summary", "")[:200], }) df = pd.DataFrame(rows) df.to_csv("gene_annotations.csv", index=False) print(df[["symbol", "gene_id", "full_name", "chr_location"]].to_string(index=False))
Goal: Retrieve all human genes associated with a biological keyword from the NCBI Gene summary field.
pythonimport requests, time, pandas as pd EMAIL = "your@email.com" BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" keyword = "DNA mismatch repair" r = requests.get(f"{BASE}/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "retmax": 50, "term": f"{keyword}[title/abstract] AND Homo sapiens[orgn] AND alive[prop]"}) ids = r.json()["esearchresult"]["idlist"] print(f"Found {len(ids)} genes related to '{keyword}'") # Fetch summaries r2 = requests.post(f"{BASE}/esummary.fcgi", data={"db": "gene", "id": ",".join(ids), "retmode": "json", "email": EMAIL}) result = r2.json()["result"] rows = [] for uid in result.get("uids", []): g = result[uid] rows.append({"gene_id": uid, "symbol": g.get("name"), "description": g.get("description"), "location": g.get("maplocation")}) df = pd.DataFrame(rows) print(df.to_string(index=False)) df.to_csv(f"{keyword.replace(' ', '_')}_genes.csv", index=False)
| Parameter | Module | Default | Range / Options | Effect | |-----------|--------|---------|-----------------|--------| | retmax | ESearch | 20 | 1–10000 | Max records returned | | retmode | ESearch/ESummary | "xml" | "json", "xml" | Response format | | rettype | EFetch | depends | "xml", "gene_table", "text" | Record format for full fetch | | [sym] field tag | ESearch | — | gene symbol | Match exact official symbol only | | [orgn] field tag | ESearch | — | organism name or tax ID | Filter by taxonomy | | alive[prop] | ESearch | — | boolean flag | Exclude discontinued gene records |
alive[prop]: Discontinued gene records remain in the database. Without this filter, symbol searches may return outdated records.api_key parameter.When to use: Get the canonical mRNA accession for a protein-coding gene.
pythonimport requests, re EMAIL = "your@email.com" GENE_ID = "672" # BRCA1 r = requests.get( "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi", params={"db": "gene", "id": GENE_ID, "rettype": "gene_table", "retmode": "text", "email": EMAIL} ) nm_accessions = re.findall(r"NM_\d+\.\d+", r.text) print(f"RefSeq mRNA accessions: {list(set(nm_accessions))}")
When to use: Resolve legacy/alias symbols to the current official NCBI symbol.
pythonimport requests EMAIL = "your@email.com" # P53 is an alias for TP53 r = requests.get( "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "term": "p53[sym] AND Homo sapiens[orgn] AND alive[prop]"} ) ids = r.json()["esearchresult"]["idlist"] r2 = requests.post("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi", data={"db": "gene", "id": ",".join(ids[:1]), "retmode": "json", "email": EMAIL}) g = r2.json()["result"][ids[0]] print(f"Official symbol : {g.get('nomenclaturesymbol', g.get('name'))}") print(f"Other aliases : {g.get('otheraliases')}") print(f"Designations : {g.get('otherdesignations', '')[:100]}")
When to use: Get all protein-coding genes on a specific human chromosome.
pythonimport requests EMAIL = "your@email.com" r = requests.get( "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi", params={"db": "gene", "email": EMAIL, "retmode": "json", "retmax": 5, "term": "17[chr] AND Homo sapiens[orgn] AND protein coding[filter] AND alive[prop]"} ) result = r.json()["esearchresult"] print(f"Protein-coding genes on chr17: {result['count']} total") print(f"Sample IDs: {result['idlist']}")
| Problem | Cause | Solution | |---------|-------|----------| | Empty idlist for known symbol | Symbol is an alias, not the official term | Use [gene name] or [title] field tag; check aliases via ESummary | | Wrong species returned | Missing organism filter | Add AND Homo sapiens[orgn] or target tax ID (9606[taxid]) | | Discontinued gene returned | Missing alive[prop] filter | Append AND alive[prop] to all symbol queries | | HTTP 429 rate limit | Too many requests | Add time.sleep(0.35) between calls; use NCBI API key | | ESummary missing uids key | All IDs invalid/absent | Check id values are valid integers, not empty strings | | XML parse error | Malformed XML for rare genes | Wrap ET.fromstring in try/except; retry with rettype=text | | Empty ortholog list from ELink | Legacy linkname=gene_gene_homolog retired with HomoloGene in 2019 | Use NCBI Datasets v2 /gene/id/{gene_id}/orthologs instead (Query 6) |
geo-database — Gene Expression Omnibus for retrieving expression data linked to genes found hereclinvar-database — Clinical variant data indexed by NCBI Gene IDsensembl-database — Complementary gene annotations with VEP and comparative genomicsbiopython-molecular-biology — Biopython Entrez module wraps E-utilities with typed return values| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-18 | pass→pass | 7,296 | 5,200 | -29% | 1 | 1 | 0% | 1,413 | 6,338 | +349% | 0 | 0 | — |
case-05 | pass→pass | 17,030 | 16,615 | -2% | 1 | 1 | 0% | 3,230 | 8,712 | +170% | 0 | 0 | — |
case-01 | fail→pass | 14,740 | 14,416 | -2% | 1 | 1 | 0% | 2,840 | 8,082 | +185% | 0 | 0 | — |
case-02 | fail→pass | 19,595 | 10,945 | -44% | 1 | 1 | 0% | 3,267 | 7,581 | +132% | 0 | 0 | — |
case-03 | pass→pass | 15,445 | 10,421 | -33% | 1 | 1 | 0% | 3,096 | 7,585 | +145% | 0 | 0 | — |
case-04 | fail→pass | 14,976 | 9,838 | -34% | 1 | 1 | 0% | 2,745 | 7,638 | +178% | 0 | 0 | — |
case-06 | fail→pass | 15,067 | 7,371 | -51% | 1 | 1 | 0% | 2,995 | 6,881 | +130% | 0 | 0 | — |
case-07 | fail→pass | 12,093 | 8,679 | -28% | 1 | 1 | 0% | 2,320 | 7,226 | +211% | 0 | 0 | — |
case-08 | pass→pass | 9,836 | 6,531 | -34% | 1 | 1 | 0% | 1,832 | 6,712 | +266% | 0 | 0 | — |
case-09 | pass→pass | 15,062 | 9,764 | -35% | 1 | 1 | 0% | 2,928 | 7,379 | +152% | 0 | 0 | — |
case-10 | pass→pass | 11,199 | 11,533 | +3% | 1 | 1 | 0% | 2,084 | 7,665 | +268% | 0 | 0 | — |
case-11 | pass→pass | 25,500 | 22,216 | -13% | 1 | 1 | 0% | 5,081 | 9,822 | +93% | 0 | 0 | — |
case-12 | pass→pass | 15,164 | 15,251 | +1% | 1 | 1 | 0% | 2,783 | 8,303 | +198% | 0 | 0 | — |
case-13 | pass→pass | 14,445 | 12,171 | -16% | 1 | 1 | 0% | 2,701 | 7,965 | +195% | 0 | 0 | — |
case-14 | pass→pass | 10,276 | 6,494 | -37% | 1 | 1 | 0% | 2,135 | 6,634 | +211% | 0 | 0 | — |
case-15 | fail→pass | 21,328 | 8,176 | -62% | 1 | 1 | 0% | 3,807 | 6,924 | +82% | 0 | 0 | — |
case-16 | pass→pass | 9,673 | 8,739 | -10% | 1 | 1 | 0% | 1,913 | 7,132 | +273% | 0 | 0 | — |
case-17 | fail→pass | 12,744 | 8,172 | -36% | 1 | 1 | 0% | 2,662 | 7,139 | +168% | 0 | 0 | — |
case-19 | pass→pass | 13,600 | 7,245 | -47% | 1 | 1 | 0% | 2,402 | 6,698 | +179% | 0 | 0 | — |
case-20 | fail→pass | 11,843 | 7,032 | -41% | 1 | 1 | 0% | 2,237 | 6,800 | +204% | 0 | 0 | — |
case-21 | pass→pass | 9,642 | 5,355 | -44% | 1 | 1 | 0% | 1,822 | 6,476 | +255% | 0 | 0 | — |
case-22 | fail→pass | 8,178 | 4,888 | -40% | 1 | 1 | 0% | 1,417 | 6,266 | +342% | 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. 22 cases were attempted. The headline lift of +41 percentage points is the difference between those two pass rates over the 22 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.