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.
Scanned 5/28/2026
Install to Claude Code
npx -y skills add jaechang-hits/SciAgent-Skills --skill pdb-database --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Pdb Database?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jaechang-hits-pdb-database)More formats (shields.io, HTML) on the badges page.
---
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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!