Back to skills
SKILL.md
Pdb Database
ASecurityQuery RCSB PDB (200K+ structures) via the public REST + GraphQL APIs with plain `requests` (no SDK). Search by text, attribute, sequence, or 3D structure similarity (Search API); retrieve metadata via GraphQL (Data API); download PDB/mmCIF from files.rcsb.org. For AlphaFold predictions use alphafold-database-access; for protein sequences only use uniprot-protein-database.
- 368 stars
- 0 votes
- 0 copies
- 3 views
- Added May 28, 2026
Works with
Security analysis
96/100- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add jaechang-hits/SciAgent-Skills --skill pdb-database --agent claude-codeAre you the author of Pdb Database?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/jaechang-hits-pdb-database)---
name: "pdb-database"
description: "Query RCSB PDB (200K+ structures) via the public REST + GraphQL APIs with plain `requests` (no SDK). Search by text, attribute, sequence, or 3D structure similarity (Search API); retrieve metadata via GraphQL (Data API); download PDB/mmCIF from files.rcsb.org. For AlphaFold predictions use alphafold-database-access; for protein sequences only use uniprot-protein-database."
license: "BSD-3-Clause"
---
# PDB Database
> **Why no SDK?** The `rcsb-api` Python SDK is convenient sugar over three public, no-auth REST endpoints (`search.rcsb.org`, `data.rcsb.org`, `files.rcsb.org`). When the SDK is unavailable, every operation can be reproduced with plain `requests` and a small JSON payload. This SKILL.md uses the REST path throughout so the code runs in any environment with `requests` installed.
## Overview
RCSB PDB is the worldwide repository for 3D structural data of biological macromolecules with 200,000+ experimentally determined structures. Programmatic access is via three free, no-auth endpoints:
| API | Base URL | Method | Purpose |
|---|---|---|---|
| **Search** | `https://search.rcsb.org/rcsbsearch/v2/query` | `POST` JSON | Find PDB IDs by text, attribute filters, sequence, or 3D similarity |
| **Data** | `https://data.rcsb.org/graphql` | `POST` GraphQL | Retrieve structured metadata (entries, polymer entities, assemblies, ligands) |
| **Files** | `https://files.rcsb.org/download/{id}.{format}` | `GET` | Download coordinate files (mmCIF, PDB, FASTA) |
Use this skill for programmatic structural biology queries, drug target analysis, and protein family comparisons.
## When to Use
- Searching for protein or nucleic acid crystal/cryo-EM/NMR structures by keyword or property
- Finding structures similar to a query sequence (MMseqs2) or 3D geometry (BioZernike)
- Retrieving experimental metadata (resolution, method, organism, deposition date) for structure sets
- Downloading coordinate files (PDB, mmCIF) for molecular dynamics, docking, or visualization
- Building structure-based datasets for machine learning or drug discovery pipelines
- Comparing protein-ligand complexes across a target family
- For AlphaFold predicted structures, use `alphafold-database-access` instead
- For protein sequence/annotation queries without structures, use `uniprot-protein-database` instead
## Prerequisites
- **Python packages**: `requests` (only requirement). Optional: `biopython` for parsing downloaded coordinate files.
- **No API key required**: RCSB PDB is freely accessible.
- **Rate limits**: No published hard limit. Polite delays of `time.sleep(0.2-0.5)` between requests are sufficient; implement exponential backoff on HTTP 429.
```bash
pip install requests
# Optional, for coordinate parsing:
pip install biopython
```
## Quick Start
Typical search-then-fetch pattern: hit the Search API, get a list of PDB IDs, then resolve metadata via the GraphQL Data API.
```python
import requests
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
DATA = "https://data.rcsb.org/graphql"
# 1. Search: human X-ray structures of "kinase" at resolution < 2.0 Å
payload = {
"query": {
"type": "group", "logical_operator": "and",
"nodes": [
{"type": "terminal", "service": "full_text",
"parameters": {"value": "kinase"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
"operator": "exact_match", "value": "Homo sapiens"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entry_info.resolution_combined",
"operator": "less", "value": 2.0}},
],
},
"return_type": "entry",
"request_options": {"paginate": {"rows": 10}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
result = r.json()
pdb_ids = [hit["identifier"] for hit in result["result_set"]]
print(f"Total matches: {result['total_count']}, first batch: {pdb_ids}")
# 2. Fetch metadata for the first hit via GraphQL
gql = """{ entry(entry_id: "%s") {
struct { title }
exptl { method }
rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count }
} }""" % pdb_ids[0]
r2 = requests.post(DATA, json={"query": gql}, timeout=30)
entry = r2.json()["data"]["entry"]
print(entry["struct"]["title"])
print(f"Method: {entry['exptl'][0]['method']}, Resolution: {entry['rcsb_entry_info']['resolution_combined']} Å")
```
## Core API
### Module 1: Text and Attribute Search
**Free-text search** uses `service: "full_text"` and searches across all indexed fields.
```python
import requests
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
def text_search(keyword, rows=25):
payload = {
"query": {"type": "terminal", "service": "full_text",
"parameters": {"value": keyword}},
"return_type": "entry",
"request_options": {"paginate": {"rows": rows}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
data = r.json()
return [hit["identifier"] for hit in data["result_set"]], data["total_count"]
ids, total = text_search("hemoglobin")
print(f"Found {total} structures; first batch: {ids[:5]}")
```
**Attribute search** uses `service: "text"` with structured `attribute`/`operator`/`value` parameters.
```python
import requests
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
def attribute_search(attribute, operator, value, return_type="entry", rows=25):
payload = {
"query": {"type": "terminal", "service": "text",
"parameters": {"attribute": attribute,
"operator": operator,
"value": value}},
"return_type": return_type,
"request_options": {"paginate": {"rows": rows}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
return r.json()
# Human proteins
human = attribute_search("rcsb_entity_source_organism.scientific_name",
"exact_match", "Homo sapiens", rows=5)
print(f"Human structures: {human['total_count']}")
# X-ray only
xray = attribute_search("exptl.method", "exact_match", "X-RAY DIFFRACTION", rows=5)
print(f"X-ray structures: {xray['total_count']}")
# Resolution range: 1.5–2.5 Å
res = attribute_search(
"rcsb_entry_info.resolution_combined", "range",
{"from": 1.5, "to": 2.5, "include_lower": True, "include_upper": True},
rows=5
)
print(f"1.5–2.5 Å: {res['total_count']}")
```
### Module 2: Sequence Similarity Search
Find structures with similar sequences using MMseqs2. Service is `"sequence"`; `target` selects protein vs. nucleic acid.
```python
import requests
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
kras_seq = ("MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ"
"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS"
"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK")
payload = {
"query": {
"type": "terminal", "service": "sequence",
"parameters": {
"target": "pdb_protein_sequence", # or "pdb_dna_sequence", "pdb_rna_sequence"
"value": kras_seq,
"evalue_cutoff": 0.1,
"identity_cutoff": 0.9,
},
},
"return_type": "polymer_entity",
"request_options": {"paginate": {"rows": 10}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
data = r.json()
print(f"KRAS-like hits: {data['total_count']}")
for hit in data["result_set"][:5]:
print(f" {hit['identifier']} score={hit.get('score', 'n/a')}")
```
### Module 3: Structure Similarity Search
Find structures with similar 3D geometry using BioZernike descriptors. Service is `"structure"`; pass the reference entry + assembly ID.
```python
import requests
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
payload = {
"query": {
"type": "terminal", "service": "structure",
"parameters": {
"value": {"entry_id": "4HHB", "assembly_id": "1"},
"operator": "strict_shape_match", # or "relaxed_shape_match"
},
},
"return_type": "polymer_entity",
"request_options": {"paginate": {"rows": 10}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
data = r.json()
print(f"Structurally similar to 4HHB: {data['total_count']}")
for hit in data["result_set"][:5]:
print(f" {hit['identifier']} score={hit.get('score', 'n/a')}")
```
### Module 4: Data Retrieval (GraphQL)
The GraphQL endpoint at `data.rcsb.org/graphql` is the canonical way to retrieve structured metadata for known PDB IDs. One request can pull fields across the full data hierarchy (entry → polymer_entity → assembly → chem_comp).
```python
import requests
DATA = "https://data.rcsb.org/graphql"
# Entry-level metadata
gql = """{ entry(entry_id: "4HHB") {
struct { title }
exptl { method }
rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count nonpolymer_entity_count }
rcsb_accession_info { deposit_date initial_release_date }
} }"""
r = requests.post(DATA, json={"query": gql}, timeout=30)
entry = r.json()["data"]["entry"]
print(f"Title : {entry['struct']['title']}")
print(f"Method : {entry['exptl'][0]['method']}")
print(f"Resolution : {entry['rcsb_entry_info']['resolution_combined']} Å")
print(f"Atoms : {entry['rcsb_entry_info']['deposited_atom_count']}")
```
```python
# Polymer entity (sequence, organism, MW)
gql = """{ polymer_entity(entry_id: "4HHB", entity_id: "1") {
entity_poly { pdbx_seq_one_letter_code }
rcsb_polymer_entity { formula_weight }
rcsb_entity_source_organism { scientific_name ncbi_taxonomy_id }
} }"""
r = requests.post(DATA, json={"query": gql}, timeout=30)
pe = r.json()["data"]["polymer_entity"]
print(f"Sequence (first 50): {pe['entity_poly']['pdbx_seq_one_letter_code'][:50]}")
print(f"Organism : {pe['rcsb_entity_source_organism'][0]['scientific_name']}")
print(f"MW : {pe['rcsb_polymer_entity']['formula_weight']}")
```
```python
# Batch: pull metadata for many entries in one request
gql = """{ entries(entry_ids: ["4HHB", "1A3N", "1HHB"]) {
rcsb_id
struct { title }
exptl { method }
rcsb_entry_info { resolution_combined }
} }"""
r = requests.post(DATA, json={"query": gql}, timeout=30)
for e in r.json()["data"]["entries"]:
res = e["rcsb_entry_info"]["resolution_combined"]
print(f" {e['rcsb_id']}: {e['exptl'][0]['method']:<25} {res} Å — {e['struct']['title'][:40]}")
```
### Module 5: File Download
Coordinate files (mmCIF, PDB, FASTA, assembly variants) are served directly from `files.rcsb.org`.
```python
import requests
def download_structure(pdb_id, fmt="cif", output_dir="."):
"""Download mmCIF / PDB / FASTA. URLs: .pdb, .cif, /fasta/entry/{ID}, .pdb1 (assembly)."""
url = f"https://files.rcsb.org/download/{pdb_id}.{fmt}"
r = requests.get(url, timeout=60)
if r.status_code == 200:
path = f"{output_dir}/{pdb_id}.{fmt}"
# mmCIF / PDB are text; assemblies and biological units are also text
with open(path, "w") as f:
f.write(r.text)
print(f"Downloaded {path} ({len(r.text)/1024:.1f} KB)")
return path
print(f"HTTP {r.status_code} for {pdb_id}.{fmt}")
return None
download_structure("4HHB", fmt="cif")
download_structure("4HHB", fmt="pdb")
```
```python
# FASTA sequence for an entry
r = requests.get("https://www.rcsb.org/fasta/entry/4HHB", timeout=30)
r.raise_for_status()
print(r.text[:400])
```
### Module 6: Query Composition (group + logical_operator)
Combine terminal queries with `type: "group"` and a `logical_operator` of `"and"` / `"or"`. Nested groups give arbitrary boolean expressions; negation is via `"node_id"` references with `"operator": "negate"` on the group (rare — usually expressed as the inverse attribute filter).
```python
import requests, datetime
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
# AND: high-resolution human structures
q_and = {
"type": "group", "logical_operator": "and",
"nodes": [
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
"operator": "exact_match", "value": "Homo sapiens"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entry_info.resolution_combined",
"operator": "less", "value": 2.0}},
],
}
# OR: human or mouse
q_or = {
"type": "group", "logical_operator": "or",
"nodes": [
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
"operator": "exact_match", "value": "Homo sapiens"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
"operator": "exact_match", "value": "Mus musculus"}},
],
}
# Combined: recent (last 30 days) + high-quality
one_month_ago = (datetime.date.today() - datetime.timedelta(days=30)).isoformat()
today = datetime.date.today().isoformat()
q_recent_hq = {
"type": "group", "logical_operator": "and",
"nodes": [
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entry_info.resolution_combined",
"operator": "less", "value": 2.0}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "refine.ls_R_factor_R_free",
"operator": "less", "value": 0.25}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_accession_info.initial_release_date",
"operator": "range",
"value": {"from": one_month_ago, "to": today,
"include_lower": True, "include_upper": True}}},
],
}
payload = {"query": q_recent_hq, "return_type": "entry",
"request_options": {"paginate": {"rows": 5}}}
r = requests.post(SEARCH, json=payload, timeout=30)
print(f"Recent high-quality: {r.json()['total_count']} structures")
```
### Module 7: Pagination + Batch with Rate Limiting
Search responses include `total_count`. Paginate with `request_options.paginate.start` and `rows` (max ~10000 per page in practice; 100–500 is a good batch size).
```python
import requests, time
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
def search_all(query_node, return_type="entry", page=100, max_results=None, delay=0.3):
"""Paginate through every result; rate-limit between pages."""
out, start = [], 0
while True:
payload = {"query": query_node, "return_type": return_type,
"request_options": {"paginate": {"start": start, "rows": page}}}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
d = r.json()
batch = [h["identifier"] for h in d.get("result_set", [])]
if not batch:
break
out.extend(batch)
if max_results and len(out) >= max_results:
return out[:max_results]
if len(batch) < page or len(out) >= d.get("total_count", 0):
break
start += page
time.sleep(delay)
return out
# Example: every insulin entry
ids = search_all(
{"type": "terminal", "service": "full_text", "parameters": {"value": "insulin"}},
max_results=300,
)
print(f"Insulin entries collected: {len(ids)}")
```
```python
# Batch metadata fetch via GraphQL `entries(...)` to avoid one round-trip per ID
DATA = "https://data.rcsb.org/graphql"
def batch_metadata(pdb_ids, chunk=50):
"""Fetch (title, method, resolution) for many entries with one POST per chunk."""
all_rows = []
for i in range(0, len(pdb_ids), chunk):
ids_arr = pdb_ids[i:i+chunk]
ids_str = ", ".join(f'"{p}"' for p in ids_arr)
gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
rcsb_id
struct {{ title }}
exptl {{ method }}
rcsb_entry_info {{ resolution_combined }}
}} }}"""
r = requests.post(DATA, json={"query": gql}, timeout=60)
r.raise_for_status()
for e in r.json()["data"]["entries"]:
res = e["rcsb_entry_info"]["resolution_combined"]
all_rows.append({
"pdb_id": e["rcsb_id"],
"method": e["exptl"][0]["method"] if e["exptl"] else None,
"resolution": res[0] if isinstance(res, list) and res else res,
"title": e["struct"]["title"],
})
return all_rows
rows = batch_metadata(ids[:20])
for r in rows[:5]:
print(f" {r['pdb_id']}: {r['method']:<25} {r['resolution']} Å — {r['title'][:50]}")
```
## Key Concepts
### Search Service Cheat Sheet
| Service | Use case | Required parameters |
|---|---|---|
| `full_text` | Free-text keyword across all indexed fields | `value` (string) |
| `text` | Structured attribute filter | `attribute`, `operator`, `value` |
| `sequence` | MMseqs2 sequence similarity | `target` ∈ {`pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence`}, `value` (sequence), `evalue_cutoff`, `identity_cutoff` |
| `seqmotif` | Pattern / regex / PROSITE motif | `value` (pattern), `pattern_type` ∈ {`simple`, `prosite`, `regex`} |
| `structure` | 3D shape similarity (BioZernike) | `value` (`{entry_id, assembly_id}`), `operator` ∈ {`strict_shape_match`, `relaxed_shape_match`} |
| `strucmotif` | 3D residue-arrangement motif | `value` (residue list), `rmsd_cutoff` |
| `chemical` | Ligand similarity by SMILES/InChI | `value`, `match_type` ∈ {`graph-exact`, `graph-relaxed`, `fingerprint-similarity`, `sub-structure-stereo-relaxed`} |
### AttributeQuery Operators (service: `"text"`)
| Operator | Value shape | Example |
|---|---|---|
| `exact_match` | string | `"Homo sapiens"` |
| `contains_words` / `contains_phrase` | string | `"tyrosine kinase"` |
| `equals` / `greater` / `less` / `greater_or_equal` / `less_or_equal` | number | `2.0` |
| `range` | `{from, to, include_lower, include_upper}` | `{"from": 1.5, "to": 2.5, "include_lower": True, "include_upper": True}` |
| `exists` | (none) | — |
| `in` | array | `["X-RAY DIFFRACTION", "ELECTRON MICROSCOPY"]` |
### Return Types
`return_type` controls the granularity of identifiers in `result_set`:
| `return_type` | Identifier shape | Example |
|---|---|---|
| `entry` | `4HHB` | One per PDB ID |
| `polymer_entity` | `4HHB_1` | One per polymer chain entity |
| `non_polymer_entity` | `4HHB_2` | Ligands, cofactors |
| `assembly` | `4HHB-1` | Biological unit |
| `polymer_instance` | `4HHB.A` | Individual chain coordinates |
| `mol_definition` | `HEM` | Chemical component (PDB ligand code) |
### Common Data API GraphQL Roots
| Root | Identifier shape | Returns |
|---|---|---|
| `entry(entry_id: ...)` | `"4HHB"` | Entry-level metadata |
| `entries(entry_ids: [...])` | array | Batch entry lookup |
| `polymer_entity(entry_id: ..., entity_id: ...)` | `"4HHB"`, `"1"` | Sequence + organism |
| `polymer_entity_instance(entry_id: ..., asym_id: ...)` | `"4HHB"`, `"A"` | Chain-level coords/metadata |
| `assembly(entry_id: ..., assembly_id: ...)` | `"4HHB"`, `"1"` | Biological assembly |
| `chem_comp(comp_id: ...)` | `"HEM"` | Small molecule reference |
### File Formats
| Format | URL pattern | Notes |
|---|---|---|
| mmCIF | `https://files.rcsb.org/download/{id}.cif` | Recommended; no atom-count limit |
| PDB | `https://files.rcsb.org/download/{id}.pdb` | Legacy; 99,999 atom limit |
| Assembly (mmCIF) | `https://files.rcsb.org/download/{id}-assembly{N}.cif` | Biological unit |
| FASTA | `https://www.rcsb.org/fasta/entry/{id}` | Sequence only |
## Common Workflows
### Workflow 1: Drug Target Structure Set
**Goal**: Find high-resolution human EGFR structures with bound ligands.
```python
import requests, time
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
DATA = "https://data.rcsb.org/graphql"
payload = {
"query": {
"type": "group", "logical_operator": "and",
"nodes": [
{"type": "terminal", "service": "full_text",
"parameters": {"value": "EGFR epidermal growth factor receptor"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
"operator": "exact_match", "value": "Homo sapiens"}},
{"type": "terminal", "service": "text",
"parameters": {"attribute": "rcsb_entry_info.resolution_combined",
"operator": "less", "value": 2.5}},
],
},
"return_type": "entry",
"request_options": {"paginate": {"rows": 50}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
r.raise_for_status()
pdb_ids = [h["identifier"] for h in r.json()["result_set"]]
print(f"EGFR ≤2.5 Å human structures: {len(pdb_ids)}")
# Filter to entries with bound ligands via batch GraphQL
ids_str = ", ".join(f'"{p}"' for p in pdb_ids[:20])
gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
rcsb_id
struct {{ title }}
rcsb_entry_info {{ resolution_combined nonpolymer_entity_count }}
}} }}"""
r2 = requests.post(DATA, json={"query": gql}, timeout=60)
for e in r2.json()["data"]["entries"]:
n_lig = e["rcsb_entry_info"]["nonpolymer_entity_count"] or 0
if n_lig > 0:
res = e["rcsb_entry_info"]["resolution_combined"]
res_v = res[0] if isinstance(res, list) else res
print(f" {e['rcsb_id']}: {res_v} Å, ligands={n_lig} — {e['struct']['title'][:60]}")
time.sleep(0.05)
```
### Workflow 2: Protein Family — Sequence-Similar Structures
**Goal**: Find all PDB structures with sequence similar to a query (KRAS), then summarize their resolution + experimental method.
```python
import requests, time
SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
DATA = "https://data.rcsb.org/graphql"
kras_seq = ("MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ"
"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS"
"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK")
payload = {
"query": {
"type": "terminal", "service": "sequence",
"parameters": {"target": "pdb_protein_sequence", "value": kras_seq,
"evalue_cutoff": 1e-5, "identity_cutoff": 0.5},
},
"return_type": "polymer_entity",
"request_options": {"paginate": {"rows": 20}},
}
r = requests.post(SEARCH, json=payload, timeout=30)
hits = r.json()["result_set"]
print(f"KRAS family hits: {len(hits)}")
# Unique PDB IDs from polymer_entity identifiers (e.g., "4OBE_1" -> "4OBE")
entry_ids = sorted({h["identifier"].split("_")[0] for h in hits})
# Batch metadata
ids_str = ", ".join(f'"{p}"' for p in entry_ids)
gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
rcsb_id
struct {{ title }}
exptl {{ method }}
rcsb_entry_info {{ resolution_combined }}
}} }}"""
r2 = requests.post(DATA, json={"query": gql}, timeout=60)
for e in sorted(r2.json()["data"]["entries"],
key=lambda x: (x["rcsb_entry_info"]["resolution_combined"] or [99])[0] if isinstance(x["rcsb_entry_info"]["resolution_combined"], list) else (x["rcsb_entry_info"]["resolution_combined"] or 99)):
res = e["rcsb_entry_info"]["resolution_combined"]
res_v = res[0] if isinstance(res, list) else res
print(f" {e['rcsb_id']}: {res_v} Å {e['exptl'][0]['method']:<25} {e['struct']['title'][:50]}")
```
### Workflow 3: Download + Parse with BioPython
**Goal**: Download mmCIF, then enumerate chains with BioPython.
```python
import requests
from Bio.PDB import MMCIFParser
pdb_id = "4HHB"
r = requests.get(f"https://files.rcsb.org/download/{pdb_id}.cif", timeout=60)
r.raise_for_status()
with open(f"{pdb_id}.cif", "w") as f:
f.write(r.text)
parser = MMCIFParser(QUIET=True)
structure = parser.get_structure(pdb_id, f"{pdb_id}.cif")
for model in structure:
for chain in model:
std_res = [r for r in chain if r.id[0] == " "]
atoms = sum(len(list(r.get_atoms())) for r in std_res)
print(f"Chain {chain.id}: {len(std_res)} residues, {atoms} atoms")
```
## Key Parameters
| Parameter | Endpoint | Default | Range / Options | Effect |
|-----------|----------|---------|-----------------|--------|
| `value` | search `sequence` | required | protein/DNA/RNA sequence string | Query sequence for MMseqs2 |
| `evalue_cutoff` | search `sequence` | `0.1` | `1e-10`–`10` | E-value threshold |
| `identity_cutoff` | search `sequence` | `0.9` | `0.0`–`1.0` | Minimum identity fraction |
| `target` | search `sequence` | `"pdb_protein_sequence"` | `pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence` | Sequence type |
| `operator` | search `text` (attribute) | required | see Attribute Operators | Comparison kind |
| `operator` | search `structure` | `strict_shape_match` | `strict_shape_match`, `relaxed_shape_match` | 3D match stringency |
| `return_type` | all search | `entry` | `entry`, `polymer_entity`, `assembly`, `polymer_instance`, `mol_definition`, … | Identifier granularity |
| `paginate.start` / `paginate.rows` | `request_options` | `0` / `25` | up to ~10000 rows/page in practice | Pagination window |
| GraphQL field `entries(entry_ids: [...])` | `data.rcsb.org/graphql` | — | array of PDB IDs | Batch entry metadata |
## Best Practices
1. **Search → fetch**: Use the Search API to get a list of IDs, then GraphQL `entries(entry_ids: [...])` for batch metadata. Avoid one GraphQL request per ID.
2. **Use `full_text` vs `text` deliberately**: free-text keyword search needs `"service": "full_text"`. Structured attribute filters need `"service": "text"`. They are not interchangeable.
3. **mmCIF over PDB format**: PDB format is being phased out and has a 99,999 atom limit. Always download `.cif` for new code.
4. **Set realistic `paginate.rows`**: `rows: 100` is a good default for batch work; the API may slow down beyond ~10000. Loop with `paginate.start` for full traversal.
5. **Rate limit with `time.sleep(0.2)` in batch loops**: No published hard cap, but the public infrastructure is shared. On `HTTP 429`, back off exponentially.
6. **Inspect the payload before posting**: `print(json.dumps(payload, indent=2))` is the cheapest way to debug `HTTP 400` errors.
7. **`entries(entry_ids: [...])` does not validate every ID**: if one ID is wrong, the whole array returns `null` entries. Validate IDs separately if you can't trust the source.
## Common Recipes
### Recipe: Get FASTA for a PDB ID
```python
import requests
r = requests.get("https://www.rcsb.org/fasta/entry/4HHB", timeout=30)
print(r.text)
```
### Recipe: All chains in an entry
```python
import requests
DATA = "https://data.rcsb.org/graphql"
gql = """{ entry(entry_id: "4HHB") {
polymer_entities {
rcsb_id
rcsb_polymer_entity_container_identifiers { auth_asym_ids }
entity_poly { rcsb_entity_polymer_type pdbx_seq_one_letter_code_can }
}
} }"""
r = requests.post(DATA, json={"query": gql}, timeout=30)
for pe in r.json()["data"]["entry"]["polymer_entities"]:
chains = pe["rcsb_polymer_entity_container_identifiers"]["auth_asym_ids"]
seq = pe["entity_poly"]["pdbx_seq_one_letter_code_can"][:50]
print(f" {pe['rcsb_id']} chains={chains} type={pe['entity_poly']['rcsb_entity_polymer_type']} seq={seq}…")
```
### Recipe: List all ligands in an entry
```python
import requests
DATA = "https://data.rcsb.org/graphql"
gql = """{ entry(entry_id: "1IEP") {
nonpolymer_entities {
rcsb_id
nonpolymer_comp { chem_comp { id name formula } }
}
} }"""
r = requests.post(DATA, json={"query": gql}, timeout=30)
for npe in r.json()["data"]["entry"]["nonpolymer_entities"]:
cc = npe["nonpolymer_comp"]["chem_comp"]
print(f" {npe['rcsb_id']}: {cc['id']} ({cc['name']}) {cc['formula']}")
```
### Recipe: Inspect available search attributes
The Search API exposes a JSON schema at `https://search.rcsb.org/rcsbsearch/v2/metadata/schema`. Use it to look up valid attribute paths.
```python
import requests
r = requests.get("https://search.rcsb.org/rcsbsearch/v2/metadata/schema", timeout=30)
schema = r.json()
# Schema lists hundreds of attribute paths; sample a few
sample_paths = [k for k in schema if "resolution" in k.lower()][:5]
print(sample_paths)
```
## Troubleshooting
| Problem | Cause | Solution |
|---------|-------|----------|
| `HTTP 400 — Invalid request to the [ text ] service` on a free-text query | Wrong service name | Use `"service": "full_text"` for keyword search; `"service": "text"` is for structured attribute filters |
| `HTTP 400` with cryptic schema message | Bad operator/value shape | Check the AttributeQuery Operators table; `range` needs the `{from,to,include_lower,include_upper}` dict |
| Empty `result_set` | Filters too strict | Relax filters one at a time; verify attribute names via the schema endpoint |
| `HTTP 404` on `entries(entry_ids: ["XYZW"])` | The entry doesn't exist | RCSB returns `null` rather than 404 inside the GraphQL response — check each `data.entries[i]` for null |
| `HTTP 429 Too Many Requests` | Burst pace | Add `time.sleep(0.3)` between requests; exponential backoff on 429 |
| `HTTP 500` from search | Server-side glitch | Retry after 5–10 s; check `status.rcsb.org` |
| Downloaded `.pdb` file truncated | >99,999 atoms (legacy format limit) | Download `.cif` instead |
| GraphQL response has `errors` array | Field name typo or wrong root | Read the error message; the API is strict about field names — check the schema browser at <https://data.rcsb.org/index.html#graphql-api> |
## Related Skills
- **alphafold-database-access** — AI-predicted structures; use when no experimental structure exists
- **uniprot-protein-database** — protein annotations, sequences, ID mapping (UniProt accession needed for AlphaFold)
- **biopython-molecular-biology** — parse downloaded PDB/mmCIF files, extract coordinates, compute distances
- **autodock-vina-docking** — downstream molecular docking using PDB structures as receptors
- **rdkit-cheminformatics** — analyze ligands extracted from PDB complexes
## References
- [RCSB PDB](https://www.rcsb.org) — main portal and web search
- [Search API v2 docs](https://search.rcsb.org/) — full JSON-payload reference and Swagger
- [Data API GraphQL browser](https://data.rcsb.org/index.html#graphql-api) — schema explorer
- [Search API attribute schema](https://search.rcsb.org/rcsbsearch/v2/metadata/schema) — JSON of every searchable attribute
- [RCSB PDB Web APIs overview](https://www.rcsb.org/docs/programmatic-access/web-apis-overview) — index of all programmatic surfaces
- For SDK-based usage, see the `rcsb-api` PyPI package; this SKILL.md uses the underlying REST/GraphQL directly so no SDK install is needed.
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.