Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query UniProt REST API: search by gene/protein name, fetch FASTA, map IDs (Ensembl, PDB, RefSeq), access Swiss-Prot annotations. Use bioservices for multi-DB access; alphafold-database-access for structures.
.claude/skills/jaechang-hits-uniprot-protein-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 194% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 151% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 138% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 183% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 221% | 0% |
UniProt is the most comprehensive protein sequence and functional annotation database, containing 250M+ entries. This skill covers programmatic access via the UniProt REST API for protein search, sequence retrieval, ID mapping, and annotation queries. Swiss-Prot entries are manually curated; TrEMBL entries are computationally predicted.
bashpip install requests pandas
API Rate Limits: UniProt REST API has no strict rate limit but recommends adding time.sleep(0.5) between batch requests. For large queries (>10k results), use the streaming endpoint instead of paginated search. Maximum 100,000 IDs per ID mapping job.
pythonimport requests # Search for human insulin proteins (reviewed/Swiss-Prot only) url = "https://rest.uniprot.org/uniprotkb/search" params = {"query": "insulin AND organism_id:9606 AND reviewed:true", "format": "tsv", "fields": "accession,gene_names,protein_name,length"} response = requests.get(url, params=params) print(response.text[:500]) # accession gene_names protein_name length # P01308 INS Insulin 110
Search UniProt with structured queries combining Boolean operators and field-specific filters.
pythonimport requests import time BASE = "https://rest.uniprot.org/uniprotkb/search" def search_uniprot(query, fields=None, format="json", size=25): """Search UniProt with query syntax.""" params = {"query": query, "format": format, "size": size} if fields: params["fields"] = ",".join(fields) resp = requests.get(BASE, params=params) resp.raise_for_status() return resp.json() if format == "json" else resp.text # Search by gene name results = search_uniprot("gene:BRCA1 AND reviewed:true", fields=["accession", "gene_names", "organism_name", "length"]) for entry in results["results"][:3]: print(f"{entry['primaryAccession']} | {entry.get('genes', [{}])[0].get('geneName', {}).get('value', 'N/A')} | {entry.get('organism', {}).get('scientificName', 'N/A')}")
Query syntax reference:
# Boolean operators
kinase AND organism_id:9606 # Human kinases
(diabetes OR insulin) AND reviewed:true
cancer NOT lung
# Field-specific
gene:BRCA1
accession:P12345
taxonomy_name:"Homo sapiens"
go:0005515 # GO term: protein binding
# Range queries
length:[100 TO 500]
mass:[50000 TO 100000]
# Wildcards
gene:BRCA*Retrieve individual protein entries by accession number.
pythonimport requests def get_protein(accession, format="json"): """Retrieve a single protein entry.""" url = f"https://rest.uniprot.org/uniprotkb/{accession}" resp = requests.get(url, headers={"Accept": f"application/{format}"}) resp.raise_for_status() return resp.json() if format == "json" else resp.text # Get human insulin entry = get_protein("P01308") print(f"Protein: {entry['proteinDescription']['recommendedName']['fullName']['value']}") print(f"Gene: {entry['genes'][0]['geneName']['value']}") print(f"Length: {entry['sequence']['length']} aa") print(f"Sequence: {entry['sequence']['value'][:50]}...") # Get FASTA directly fasta = requests.get("https://rest.uniprot.org/uniprotkb/P01308.fasta").text print(fasta[:200])
Map identifiers between UniProt and other databases.
pythonimport requests import time def map_ids(ids, from_db, to_db): """Map identifiers between databases (async job).""" # Submit job resp = requests.post("https://rest.uniprot.org/idmapping/run", data={"from": from_db, "to": to_db, "ids": ",".join(ids)}) resp.raise_for_status() job_id = resp.json()["jobId"] # Poll for completion while True: status = requests.get(f"https://rest.uniprot.org/idmapping/status/{job_id}").json() if "results" in status or "failedIds" in status: break time.sleep(1) # Get results results = requests.get(f"https://rest.uniprot.org/idmapping/results/{job_id}").json() return results # UniProt → PDB mapping results = map_ids(["P01308", "P12345"], from_db="UniProtKB_AC-ID", to_db="PDB") for r in results.get("results", []): print(f"{r['from']} → PDB: {r['to']}") # UniProt → Ensembl mapping results = map_ids(["P01308"], from_db="UniProtKB_AC-ID", to_db="Ensembl") for r in results.get("results", []): print(f"{r['from']} → Ensembl: {r['to']}")
Common database codes: UniProtKB_AC-ID, Ensembl, RefSeq_Protein, PDB, Gene_Name, GeneID, KEGG
Retrieve large datasets efficiently.
pythonimport requests import time def batch_retrieve(accessions, fields=None, format="tsv"): """Retrieve multiple proteins by accession.""" query = " OR ".join(f"accession:{acc}" for acc in accessions) params = {"query": query, "format": format} if fields: params["fields"] = ",".join(fields) resp = requests.get("https://rest.uniprot.org/uniprotkb/search", params=params) resp.raise_for_status() return resp.text # Batch retrieve accessions = ["P01308", "P12345", "Q9Y6K9"] tsv = batch_retrieve(accessions, fields=["accession", "gene_names", "protein_name", "length"]) print(tsv) # Streaming for large queries (no pagination needed) def stream_query(query, format="fasta"): """Stream large result sets.""" url = f"https://rest.uniprot.org/uniprotkb/stream?query={query}&format={format}" resp = requests.get(url, stream=True) resp.raise_for_status() for chunk in resp.iter_content(chunk_size=8192, decode_unicode=True): yield chunk # Stream all human kinases as FASTA # for chunk in stream_query("kinase AND organism_id:9606 AND reviewed:true"): # print(chunk[:200])
Handle large result sets with pagination using the Link header cursor.
pythonimport requests def paginate_search(query, fields=None, page_size=500): """Iterate all pages of a UniProt search using cursor pagination.""" params = {"query": query, "format": "tsv", "size": page_size} if fields: params["fields"] = ",".join(fields) url = "https://rest.uniprot.org/uniprotkb/search" rows = [] header = None while url: resp = requests.get(url, params=params) resp.raise_for_status() params = {} # cursor is embedded in the next URL lines = resp.text.strip().split("\n") if header is None: header = lines[0] rows.extend(lines[1:]) # Follow Link header for next page link = resp.headers.get("Link", "") url = link.split("<")[1].split(">")[0] if "<" in link else None return header, rows header, rows = paginate_search( "kinase AND organism_id:9606 AND reviewed:true", fields=["accession", "gene_names", "length"] ) print(f"Retrieved {len(rows)} proteins") print(header) print("\n".join(rows[:3]))
Customize which data fields to retrieve.
pythonimport requests import pandas as pd from io import StringIO # Retrieve specific annotation fields params = { "query": "gene:TP53 AND organism_id:9606 AND reviewed:true", "format": "tsv", "fields": "accession,gene_names,protein_name,go_p,go_f,go_c,cc_function,ft_domain", } resp = requests.get("https://rest.uniprot.org/uniprotkb/search", params=params) df = pd.read_csv(StringIO(resp.text), sep="\t") print(df.columns.tolist()) print(df.iloc[0])
Common field groups:
accession, sequence, length, massgene_names, protein_name, organism_namego_p (process), go_f (function), go_c (component)ft_domain, ft_binding, ft_act_site, ft_mod_rescc_function, cc_interaction, cc_subcellular_location| Parameter | Function/Endpoint | Default | Range / Options | Effect | |-----------|-------------------|---------|-----------------|--------| | query | /search, /stream | — | UniProt query syntax | Filter proteins by criteria | | format | All endpoints | json | json, tsv, fasta, xml, gff | Output format | | fields | /search | all | Comma-separated field names | Reduces response size | | size | /search | 25 | 1–500 | Results per page | | from / to | /idmapping/run | — | Database codes | ID mapping direction | | reviewed:true | Query filter | — | true/false | Swiss-Prot (curated) only | | organism_id | Query filter | — | NCBI taxonomy ID | Filter by species |
reviewed:true for curated data: Swiss-Prot entries are manually reviewed; TrEMBL entries are computationally predicted. Use Swiss-Prot for high-confidence annotations.fields for tabular analysis: Requesting only needed fields as TSV is faster and easier to parse than full JSON entries./stream endpoint returns all results without pagination, avoiding the need for multi-page iteration.time.sleep(0.5) between batch requests: Respect API resources, especially when making many sequential requests.organism_id: Broad queries like gene:INS return thousands of entries across all species. Always filter by organism for targeted results.pythonimport requests import pandas as pd from io import StringIO url = "https://rest.uniprot.org/uniprotkb/stream" params = { "query": "ec:2.7.* AND organism_id:9606 AND reviewed:true", "format": "tsv", "fields": "accession,gene_names,protein_name,length,go_f", } resp = requests.get(url, params=params) df = pd.read_csv(StringIO(resp.text), sep="\t") print(f"Human kinases (Swiss-Prot): {len(df)}") print(df.head())
pythonimport requests import pandas as pd from io import StringIO gene_list = ["BRCA1", "BRCA2", "TP53", "ATM", "CHEK2"] query = " OR ".join(f"gene:{g}" for g in gene_list) query += " AND organism_id:9606 AND reviewed:true" params = { "query": query, "format": "tsv", "fields": "accession,gene_names,go_p,go_f,go_c", } resp = requests.get("https://rest.uniprot.org/uniprotkb/search", params=params) df = pd.read_csv(StringIO(resp.text), sep="\t") print(df[["Accession", "Gene Names", "Gene Ontology (biological process)"]].head())
pythonimport requests import time accessions = ["P53_HUMAN", "P01308", "P00533"] # TP53, Insulin, EGFR resp = requests.post("https://rest.uniprot.org/idmapping/run", data={"from": "UniProtKB_AC-ID", "to": "PDB", "ids": ",".join(accessions)}) job_id = resp.json()["jobId"] time.sleep(2) results = requests.get(f"https://rest.uniprot.org/idmapping/results/{job_id}").json() for r in results.get("results", []): print(f"{r['from']} → PDB: {r['to']}")
| Problem | Cause | Solution | |---------|-------|----------| | 400 Bad Request | Invalid query syntax | Check Boolean operators, field names, bracket matching; use UniProt query syntax docs | | Too many results (slow) | No organism or review filter | Add AND organism_id:9606 AND reviewed:true to narrow results | | ID mapping returns empty | Wrong database code | Verify from/to codes: use UniProtKB_AC-ID (not UniProtKB alone) | | Pagination missing entries | Large result set | Use /stream endpoint instead of paginated /search | | 429 Too Many Requests | Excessive API calls | Add time.sleep(0.5) between requests; batch accessions in single queries | | FASTA has no gene name | TrEMBL entry with minimal annotation | Filter reviewed:true for Swiss-Prot entries with full annotations |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 8,545 | 4,868 | -43% | 1 | 1 | 0% | 1,739 | 4,918 | +183% | 0 | 0 | — |
case-01 | pass→pass | 8,285 | 18,995 | +129% | 1 | 1 | 0% | 1,684 | 5,399 | +221% | 0 | 0 | — |
case-02 | pass→pass | 10,301 | 6,692 | -35% | 1 | 1 | 0% | 1,890 | 5,359 | +184% | 0 | 0 | — |
case-03 | pass→pass | 16,078 | 13,089 | -19% | 1 | 1 | 0% | 3,112 | 6,557 | +111% | 0 | 0 | — |
case-05 | pass→pass | 9,024 | 6,301 | -30% | 1 | 1 | 0% | 1,758 | 5,256 | +199% | 0 | 0 | — |
case-06 | pass→pass | 24,367 | 2,260 | -91% | 1 | 1 | 0% | 475 | 4,440 | +835% | 0 | 0 | — |
case-07 | pass→pass | 8,358 | 4,663 | -44% | 1 | 1 | 0% | 1,826 | 5,060 | +177% | 0 | 0 | — |
case-08 | pass→pass | 27,995 | 4,328 | -85% | 1 | 1 | 0% | 1,270 | 4,915 | +287% | 0 | 0 | — |
case-09 | pass→pass | 6,609 | 3,320 | -50% | 1 | 1 | 0% | 1,210 | 4,644 | +284% | 0 | 0 | — |
case-10 | pass→pass | 4,995 | 4,101 | -18% | 1 | 1 | 0% | 879 | 4,746 | +440% | 0 | 0 | — |
case-11 | fail→pass | 11,682 | 8,350 | -29% | 1 | 1 | 0% | 1,941 | 5,701 | +194% | 0 | 0 | — |
case-12 | pass→pass | 8,647 | 5,827 | -33% | 1 | 1 | 0% | 1,708 | 5,169 | +203% | 0 | 0 | — |
case-13 | pass→pass | 16,028 | 6,038 | -62% | 1 | 1 | 0% | 2,001 | 5,211 | +160% | 0 | 0 | — |
case-14 | pass→pass | 6,107 | 3,217 | -47% | 1 | 1 | 0% | 1,173 | 4,619 | +294% | 0 | 0 | — |
case-15 | pass→pass | 3,633 | 3,563 | -2% | 1 | 1 | 0% | 672 | 4,624 | +588% | 0 | 0 | — |
case-16 | pass→pass | 9,565 | 6,361 | -33% | 1 | 1 | 0% | 1,785 | 5,320 | +198% | 0 | 0 | — |
case-17 | fail→pass | 10,761 | 7,657 | -29% | 1 | 1 | 0% | 2,213 | 5,565 | +151% | 0 | 0 | — |
case-18 | pass→pass | 4,130 | 2,934 | -29% | 1 | 1 | 0% | 742 | 4,507 | +507% | 0 | 0 | — |
case-19 | pass→pass | 6,177 | 4,051 | -34% | 1 | 1 | 0% | 1,151 | 4,767 | +314% | 0 | 0 | — |
case-20 | pass→pass | 7,972 | 5,759 | -28% | 1 | 1 | 0% | 1,410 | 5,078 | +260% | 0 | 0 | — |
case-21 | fail→pass | 12,239 | 7,413 | -39% | 1 | 1 | 0% | 2,312 | 5,503 | +138% | 0 | 0 | — |
case-22 | pass→pass | 14,343 | 12,797 | -11% | 1 | 1 | 0% | 2,772 | 6,535 | +136% | 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 +14 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.