agentsclimarketplace

Uniprot access

Skill BioTender-max/awesome-bio-agent-skills/skills/bioskills/uniprot-access

A curated collection of AI agent skills for biomedical research, covering genomics, proteomics, single-cell analysis, clinical AI, and protein design.

Install
npx -y skills add BioTender-max/awesome-bio-agent-skills --skill uniprot-access

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Query UniProt's REST API (post-2022 endpoint at rest.uniprot.org) for protein sequences, annotations, GO terms, cross-references, ID mappings, and proteomes. Use when fetching UniProtKB entries, navigating the JSON schema, choosing between UniProtKB/UniRef/UniParc/Proteomes resources, deciding stream vs search endpoint for batch retrieval, running ID-mapping jobs with the async pattern, handling isoform suffixes, or filtering reviewed Swiss-Prot vs auto-annotated TrEMBL. Encodes the legacy URL migration (2022), the new JSON schema layout, and bulk-pull patterns.

SKILL.md

18.4 KB, as published. Nobody here has run it

Version Compatibility

Reference examples tested with: requests 2.31+, pandas 2.2+; UniProt REST API as of 2024_06 release

Before using code patterns, verify installed versions match. If versions differ:

The REST API JSON schema is stable within a release; major schema changes are documented at https://www.uniprot.org/release-notes. The 2022 migration broke the legacy https://www.uniprot.org/uniprot/... endpoints.

UniProt Access

"Get protein information from UniProt" -> Two facts dominate every UniProt workflow in 2026: (1) the API endpoint migrated in 2022 from https://www.uniprot.org/uniprot/... to https://rest.uniprot.org/uniprotkb/... with a substantially different JSON schema; pre-2022 code does not work as-is. (2) ?fields= is essential — default JSON returns the full entry (~20-30 KB each); for bulk pulls, request only the fields actually needed.

The major databases under the UniProt umbrella have different scopes:

  • UniProtKB: the curated knowledgebase — Swiss-Prot (manually reviewed, ~570K entries as of 2024) + TrEMBL (auto-annotated, ~250M). Always specify reviewed:true for high-quality reference work.

  • UniRef: clustered sequences at 100%, 90%, 50% identity. UniRef50 is the standard for redundancy reduction.

  • UniParc: archival "every unique sequence ever seen" — for provenance and historical lookup.

  • Proteomes: organism-level groupings; reference proteomes (one per species) are the canonical subset.

  • Python: requests.get('https://rest.uniprot.org/uniprotkb/...') (REST API)

  • Python: Bio.ExPASy.get_sprot_raw() (BioPython; legacy SwissProt format)

  • CLI: curl https://rest.uniprot.org/uniprotkb/P04637.json

Required Setup

import requests
import pandas as pd
import time

No API key required. Rate limit is generous (~200 req/sec tolerated empirically); ID-mapping has its own job queue.

Endpoint reference

Base: https://rest.uniprot.org/

ResourceEndpointUse
Single entry/uniprotkb/{accession}One protein record
Search/uniprotkb/searchQuery with up to 500 results per page
Stream/uniprotkb/streamNo 500-result limit; for bulk
Batch by accession/uniprotkb/accessionsMultiple specific accessions
ID Mapping (run)/idmapping/runSubmit conversion job
ID Mapping (status)/idmapping/status/{jobId}Poll
ID Mapping (results)/idmapping/results/{jobId}Retrieve
UniRef entry/uniref/{cluster_id}One cluster
UniRef search/uniref/searchUniRef cluster queries
Proteome/proteomes/{upid}Organism proteome
Proteome FASTA/proteomes/{upid}.fasta.gzDownload whole proteome
Taxonomy/taxonomy/{taxid}Taxonomy info

Append .json, .fasta, .tsv, .xml, .txt, or .gff to single-entry URLs to control format.

Search query syntax

UniProt search queries use a Lucene-like syntax distinct from Entrez:

QueryMeans
gene:TP53Gene name TP53
gene_exact:TP53Exact gene name (no wildcard match)
organism_id:9606Human (NCBI taxonomy ID)
organism_name:"Homo sapiens"By name (slower than taxid)
reviewed:trueSwiss-Prot only
reviewed:falseTrEMBL only
length:[100 TO 500]Sequence length range
go:0006915GO term (apoptosis)
keyword:KW-0067UniProt keyword
ec:2.7.1.1Enzyme classification
database:pdbHas PDB cross-ref
xref:pdbSame as above
existence:1Evidence at protein level (1 = strongest)

Combine: organism_id:9606 AND reviewed:true AND keyword:KW-0067 AND xref:pdb.

?fields= for bulk pulls

Default JSON entry is ~20-30 KB. For batch work, restrict fields:

fields = 'accession,id,gene_names,protein_name,length,sequence,xref_pdb,xref_alphafolddb'
url = 'https://rest.uniprot.org/uniprotkb/search'
params = {'query': 'organism_id:9606 AND reviewed:true', 'fields': fields, 'format': 'tsv', 'size': 500}

Common field selectors:

FieldReturns
accession, idPrimary accession (P04637), entry name (P53_HUMAN)
gene_namesAll gene names
gene_primaryPrimary gene name only
protein_nameRecommended name
organism_name, organism_idSpecies
length, massSequence stats
sequenceThe actual sequence
cc_function, cc_subcellular_locationFunction and localization comments
ft_domain, ft_binding, ft_active_siteDomain/site features
go_p, go_c, go_fGO biological process / cellular component / molecular function
xref_pdb, xref_alphafolddb, xref_ensembl, xref_refseqCross-references
keywordUniProt keywords
ecEnzyme classification
reviewedSwiss-Prot vs TrEMBL flag
cc_alternative_productsIsoforms

Stream vs search vs accessions

EndpointWhenLimit
/uniprotkb/{acc}One accession1 entry
/uniprotkb/accessions?accessions=...Several known accessionsUp to ~100 per call
/uniprotkb/search?query=...Query-driven; need pagination500 results per page; cursor= for paging
/uniprotkb/stream?query=...Bulk query (>500)No hard limit; one HTTP stream

For 1000+ results, /stream is the right endpoint. Stream returns one HTTP response; iterate over the stream to avoid memory blowup.

JSON schema navigation (the post-2022 layout)

The new schema is deeply nested. Common access patterns:

entry = requests.get('https://rest.uniprot.org/uniprotkb/P04637.json').json()

acc = entry['primaryAccession']                                                # 'P04637'
entry_name = entry['uniProtkbId']                                              # 'P53_HUMAN'
sequence = entry['sequence']['value']                                          # actual AA sequence
length = entry['sequence']['length']

# Names (nested; defensive .get() because some fields are optional)
recommended = entry.get('proteinDescription', {}).get('recommendedName', {}).get('fullName', {}).get('value')
primary_gene = entry.get('genes', [{}])[0].get('geneName', {}).get('value')

# Cross-references
xrefs_by_db = {}
for xref in entry.get('uniProtKBCrossReferences', []):
    xrefs_by_db.setdefault(xref['database'], []).append(xref['id'])

# Features (domains, binding sites)
domains = [f for f in entry.get('features', []) if f['type'] == 'Domain']
binding = [f for f in entry.get('features', []) if f['type'] == 'Binding site']

# Isoforms
isoforms = []
for comment in entry.get('comments', []):
    if comment.get('commentType') == 'ALTERNATIVE PRODUCTS':
        isoforms = [iso['name']['value'] for iso in comment.get('isoforms', [])]

Isoform handling

Canonical sequence is returned for the bare accession (e.g. P04637). Isoforms have -2, -3, etc. suffixes (P04637-2). To fetch a specific isoform:

iso = requests.get('https://rest.uniprot.org/uniprotkb/P04637-2.fasta').text

The canonical entry's comments[type=ALTERNATIVE PRODUCTS] lists all isoforms with their differences. For workflows needing all isoforms, iterate the list and fetch separately.

ID Mapping API (async)

Convert between identifier systems (Ensembl Gene -> UniProt; PDB -> UniProt; UniProt -> RefSeq; etc.). The job pattern:

  1. Submit: POST /idmapping/run with ids, from, to.
  2. Poll: GET /idmapping/status/{jobId} — returns {'jobStatus': 'RUNNING'} or {'results': [...]}.
  3. Fetch: GET /idmapping/results/{jobId} once status is complete.

Job typically completes in 30s; larger batches take 5-10 min. Always set a poll timeout — the API doesn't fail-soft on stuck jobs.

FromToNotes
UniProtKB_AC-IDUniProtKBResolve obsolete to current accessions
Gene_NameUniProtKBSymbol -> accession (lossy; check matches)
EnsemblUniProtKBEnsembl Gene/Transcript/Protein
EMBL-GenBank-DDBJUniProtKBINSDC nucleotide accessions
RefSeq_ProteinUniProtKBNP_/XP_ accessions
PDBUniProtKBPDB chain to protein
UniProtKBEMBL-GenBank-DDBJReverse direction

Full from/to list at https://rest.uniprot.org/configure/idmapping/fields.

Code patterns

Single entry with defensive JSON parsing

Goal: Fetch one UniProt entry as JSON and extract canonical name, gene, sequence, PDB cross-refs without KeyErrors.

Approach: GET /uniprotkb/{acc}.json; navigate with .get() chains; handle missing fields gracefully.

Reference (UniProt REST as of 2024_06):

import requests


def fetch_uniprot_entry(accession):
    r = requests.get(f'https://rest.uniprot.org/uniprotkb/{accession}.json')
    r.raise_for_status()
    e = r.json()
    return {
        'accession': e['primaryAccession'],
        'entry_name': e.get('uniProtkbId'),
        'reviewed': e.get('entryType') == 'UniProtKB reviewed (Swiss-Prot)',
        'protein_name': e.get('proteinDescription', {}).get('recommendedName', {}).get('fullName', {}).get('value'),
        'gene_primary': (e.get('genes') or [{}])[0].get('geneName', {}).get('value'),
        'sequence': e['sequence']['value'],
        'length': e['sequence']['length'],
        'pdb_ids': [x['id'] for x in e.get('uniProtKBCrossReferences', []) if x['database'] == 'PDB'],
        'alphafold_id': next((x['id'] for x in e.get('uniProtKBCrossReferences', []) if x['database'] == 'AlphaFoldDB'), None),
    }


print(fetch_uniprot_entry('P04637'))

Search via TSV with fields= (bulk-friendly)

Goal: Get a DataFrame of human reviewed kinases with their PDB and AlphaFold IDs.

Approach: /search with format=tsv and explicit fields; paginate via cursor if results exceed 500.

Reference (requests 2.31+):

import pandas as pd
from io import StringIO


def search_uniprot_tsv(query, fields, size=500):
    url = 'https://rest.uniprot.org/uniprotkb/search'
    params = {'query': query, 'fields': ','.join(fields), 'format': 'tsv', 'size': size}
    r = requests.get(url, params=params)
    r.raise_for_status()
    return pd.read_csv(StringIO(r.text), sep='\t')


df = search_uniprot_tsv(
    'organism_id:9606 AND reviewed:true AND keyword:"Kinase"',
    fields=['accession', 'gene_primary', 'protein_name', 'length', 'xref_pdb', 'xref_alphafolddb'],
)
print(f'{len(df)} reviewed human kinases')
print(df.head())

Stream endpoint for >500 results

import requests
import pandas as pd
from io import StringIO


def stream_uniprot(query, fields):
    url = 'https://rest.uniprot.org/uniprotkb/stream'
    params = {'query': query, 'fields': ','.join(fields), 'format': 'tsv'}
    r = requests.get(url, params=params, stream=True)
    r.raise_for_status()
    return pd.read_csv(StringIO(r.text), sep='\t')


# All human reviewed proteins (~20K)
df = stream_uniprot(
    'organism_id:9606 AND reviewed:true',
    fields=['accession', 'gene_primary', 'protein_name', 'length'],
)
print(f'All human Swiss-Prot: {len(df)}')

ID mapping with proper async polling

Goal: Convert Ensembl Gene IDs to UniProt accessions.

Approach: Submit job; poll with timeout; retrieve results.

Reference (UniProt REST 2024_06):

import time


def map_ids(ids, from_db='Ensembl', to_db='UniProtKB', timeout=600, poll_interval=3):
    submit = requests.post('https://rest.uniprot.org/idmapping/run',
                           data={'ids': ','.join(ids), 'from': from_db, 'to': to_db})
    submit.raise_for_status()
    job_id = submit.json()['jobId']
    print(f'Submitted job {job_id}')

    elapsed = 0
    while elapsed < timeout:
        status = requests.get(f'https://rest.uniprot.org/idmapping/status/{job_id}')
        status.raise_for_status()
        js = status.json()
        if 'jobStatus' in js and js['jobStatus'] == 'RUNNING':
            time.sleep(poll_interval)
            elapsed += poll_interval
            continue
        # Completed (results in status response) or has results endpoint
        break
    else:
        raise TimeoutError(f'ID mapping job {job_id} did not complete in {timeout}s')

    results = requests.get(f'https://rest.uniprot.org/idmapping/results/{job_id}')
    results.raise_for_status()
    return results.json()


mapping = map_ids(['ENSG00000141510', 'ENSG00000171862', 'ENSG00000139618'])
for r in mapping.get('results', []):
    print(f"  {r['from']:<20} -> {r['to']}")
for failed in mapping.get('failedIds', []):
    print(f"  {failed:<20} -> NOT MAPPED")

Resolve obsolete accessions

def resolve_obsolete(accessions):
    '''Use ID mapping to update obsolete accessions to current primary IDs.'''
    return map_ids(accessions, from_db='UniProtKB_AC-ID', to_db='UniProtKB')

Download a reference proteome

import gzip


def download_proteome(upid, out_path):
    '''upid: UniProt Proteome ID, e.g. UP000005640 (human reference).'''
    url = f'https://rest.uniprot.org/proteomes/{upid}.fasta.gz'
    r = requests.get(url, stream=True)
    r.raise_for_status()
    with open(out_path, 'wb') as f:
        for chunk in r.iter_content(8192):
            f.write(chunk)
    return out_path


download_proteome('UP000005640', 'human.fasta.gz')  # human reference proteome

UniRef cluster lookup

def uniref_cluster(uniref_id):
    '''e.g. UniRef50_P04637 -- the UniRef50 cluster centered on P04637.'''
    r = requests.get(f'https://rest.uniprot.org/uniref/{uniref_id}.json')
    r.raise_for_status()
    j = r.json()
    return {
        'id': j['id'],
        'representative': j['representativeMember']['memberId'],
        'member_count': j['memberCount'],
        'identity': j.get('entryType'),
    }

Failure modes

Legacy URL still in code (post-2022)

  • Trigger: Old code using https://www.uniprot.org/uniprot/{acc}.json.
  • Mechanism: 2022 migration; old URLs redirect but JSON schema is the new one — old parsers break.
  • Symptom: Either 404 or KeyError from old field paths.
  • Fix: Use https://rest.uniprot.org/uniprotkb/{acc}.json; update field navigation to the new nested layout.

?fields= not specified

  • Trigger: Bulk pull (1000 accessions) returning full JSON entries.
  • Mechanism: Default returns ~20-30 KB per entry; 1000 entries = 20-30 MB.
  • Symptom: Slow; memory blowup; rate-limit triggers.
  • Fix: Always specify fields= for bulk; request only the fields actually needed.

Search hit 500-record cap

  • Trigger: Query matches 800 records; iterate first page only.
  • Mechanism: /search returns 500 per page; need cursor for next.
  • Symptom: Silently dropped tail.
  • Fix: Use /stream for >500 results; or paginate /search with cursor.

ID mapping job poll infinite loop

  • Trigger: Network glitch during job; status forever "RUNNING".
  • Mechanism: API doesn't time-out stuck jobs.
  • Symptom: Pipeline hangs.
  • Fix: Always set timeout= on polling; surface TimeoutError.

Isoform suffix mishandled

  • Trigger: Storing P04637 and assuming that's the only sequence.
  • Mechanism: TP53 has multiple isoforms; default fetch returns canonical only.
  • Symptom: Missing alternative-product sequences.
  • Fix: Read comments[type=ALTERNATIVE PRODUCTS]; fetch each isoform with -N suffix.

Swiss-Prot vs TrEMBL confusion

  • Trigger: Search without reviewed:true returning millions of TrEMBL hits.
  • Mechanism: TrEMBL is automatically annotated, often low-quality.
  • Symptom: "Why does my analysis include 200M proteins?"
  • Fix: For reference-quality work, always filter reviewed:true.

Obsolete accessions silently fail

  • Trigger: Old paper-derived accession that has been merged or demerged.
  • Mechanism: Direct fetch returns 404 or 301.
  • Symptom: Missing entries in a batch.
  • Fix: Use ID mapping (UniProtKB_AC-ID -> UniProtKB) to resolve to current accessions first.

Gene-symbol disambiguation

  • Trigger: Search gene:TP53 returns multiple species or duplicates.
  • Mechanism: Symbol is shared across species; UniProt indexes all.
  • Symptom: Mixed-species hits.
  • Fix: Combine with organism_id:9606 (or specific taxon); use gene_exact: to avoid wildcard matches.

Common errors

Error / symptomCauseSolution
404 on legacy URLPre-2022 endpointUse rest.uniprot.org/uniprotkb/
KeyError on old field pathSchema migration 2022Update to new nested layout; use .get()
Bulk fetch very slowDefault JSON entry sizeSpecify fields= for TSV bulk
Mid-pagination data missing500-record capUse /stream or paginate with cursor
ID mapping job hangsAPI doesn't fail stuck jobsSet timeout= on poll loop
Mixed-species search resultsSymbol shared across speciesAdd organism_id: filter
Million-row search returning TrEMBLNo reviewed filterAdd reviewed:true
Missing isoformDefault returns canonical onlyFetch with -N suffix per isoform

References

  • The UniProt Consortium. (2024) UniProt: the Universal Protein Knowledgebase in 2025. Nucleic Acids Res 53:D609-D617.
  • Bursteinas B, Britto R, Bely B, et al. (2016) Minimizing proteome redundancy in the UniProt Knowledgebase. Database 2016:baw139.
  • UniProt help: https://www.uniprot.org/help/api
  • UniProt REST: https://rest.uniprot.org

Related Skills

  • entrez-fetch - NCBI protein records (RefSeq, GenPept) alternative
  • biomart-queries - Alternative ID-mapping path via BioMart (preferred for Ensembl-rooted batches >5K; UniProt /idmapping/run is preferred for obsolete-accession resolution and any UniProt-rooted mapping)
  • ortholog-inference - Resolve UniProt accessions used by OMA orthology queries
  • structural-biology/structure-io - Download PDB structures referenced from UniProt
  • structural-biology/alphafold-predictions - AlphaFoldDB entries cross-referenced in UniProt
  • pathway-analysis/go-enrichment - Use GO annotations pulled from UniProt

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.