[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M73HPN0DCY7HT2YNE9MJC2YY":3,"skill-same:01M73HPN0DCY7HT2YNE9MJC2YY":50},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":26,"commitSha":27,"skillDirectory":28,"forksCount":29,"repositoryHtmlUrl":30,"repositoryHomepageUrl":31,"ownerHtmlUrl":32,"ownerBlogUrl":33,"ownerDescription":34,"updatedAt":35,"install":36,"resources":49},"01M73HPN0DCY7HT2YNE9MJC2YY","pdb-database",null,"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\u002FmmCIF from files.rcsb.org. For AlphaFold predictions use alphafold-database-access; for protein sequences only use uniprot-protein-database.","bg-szy\u002FTOP-SKILLS","bg-szy","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F106576190?v=4","01M73A4E8KPV67SFXRY8XZGNBX","ShuZhiyong","01M73A4E96MQ294MHM74TZBVQD",6,"BSD-3-Clause","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fawesome-skills\u002Fpdb-database\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: \"pdb-database\"\ndescription: \"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\u002FmmCIF from files.rcsb.org. For AlphaFold predictions use alphafold-database-access; for protein sequences only use uniprot-protein-database.\"\nlicense: \"BSD-3-Clause\"\nmetadata:\n  quick_use: 'one entry: POST https:\u002F\u002Fdata.rcsb.org\u002Fgraphql with {\"query\": \"{ entry(entry_id: \\\"1M17\\\") { struct { title } exptl { method } rcsb_entry_info { resolution_combined } nonpolymer_entities { nonpolymer_comp { chem_comp { id name } } } } }\"}. An unknown ID returns HTTP 200 with data.entry = null, not 404. Coordinates: GET https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{ID}.cif (prefer mmCIF). Cite https:\u002F\u002Fwww.rcsb.org\u002Fstructure\u002F{ID}'\n---\n\n# PDB Database\n\n> **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.\n\n## Overview\n\nRCSB 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:\n\n| API | Base URL | Method | Purpose |\n|---|---|---|---|\n| **Search** | `https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery` | `POST` JSON | Find PDB IDs by text, attribute filters, sequence, or 3D similarity |\n| **Data** | `https:\u002F\u002Fdata.rcsb.org\u002Fgraphql` | `POST` GraphQL | Retrieve structured metadata (entries, polymer entities, assemblies, ligands) |\n| **Files** | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.{format}` | `GET` | Download coordinate files (mmCIF, PDB, FASTA) |\n\nUse this skill for programmatic structural biology queries, drug target analysis, and protein family comparisons.\n\n## When to Use\n\n- Searching for protein or nucleic acid crystal\u002Fcryo-EM\u002FNMR structures by keyword or property\n- Finding structures similar to a query sequence (MMseqs2) or 3D geometry (BioZernike)\n- Retrieving experimental metadata (resolution, method, organism, deposition date) for structure sets\n- Downloading coordinate files (PDB, mmCIF) for molecular dynamics, docking, or visualization\n- Building structure-based datasets for machine learning or drug discovery pipelines\n- Comparing protein-ligand complexes across a target family\n- For AlphaFold predicted structures, use `alphafold-database-access` instead\n- For protein sequence\u002Fannotation queries without structures, use `uniprot-protein-database` instead\n\n## Prerequisites\n\n- **Python packages**: `requests` (only requirement). Optional: `biopython` for parsing downloaded coordinate files.\n- **No API key required**: RCSB PDB is freely accessible.\n- **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.\n\n```bash\npip install requests\n# Optional, for coordinate parsing:\npip install biopython\n```\n\n## Quick Start\n\nTypical search-then-fetch pattern: hit the Search API, get a list of PDB IDs, then resolve metadata via the GraphQL Data API.\n\n```python\nimport requests\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\n# 1. Search: human X-ray structures of \"kinase\" at resolution \u003C 2.0 Å\npayload = {\n    \"query\": {\n        \"type\": \"group\", \"logical_operator\": \"and\",\n        \"nodes\": [\n            {\"type\": \"terminal\", \"service\": \"full_text\",\n             \"parameters\": {\"value\": \"kinase\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                            \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                            \"operator\": \"less\", \"value\": 2.0}},\n        ],\n    },\n    \"return_type\": \"entry\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\nresult = r.json()\npdb_ids = [hit[\"identifier\"] for hit in result[\"result_set\"]]\nprint(f\"Total matches: {result['total_count']}, first batch: {pdb_ids}\")\n\n# 2. Fetch metadata for the first hit via GraphQL\ngql = \"\"\"{ entry(entry_id: \"%s\") {\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count }\n} }\"\"\" % pdb_ids[0]\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=30)\nentry = r2.json()[\"data\"][\"entry\"]\nprint(entry[\"struct\"][\"title\"])\nprint(f\"Method: {entry['exptl'][0]['method']}, Resolution: {entry['rcsb_entry_info']['resolution_combined']} Å\")\n```\n\n## Core API\n\n### Module 1: Text and Attribute Search\n\n**Free-text search** uses `service: \"full_text\"` and searches across all indexed fields.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef text_search(keyword, rows=25):\n    payload = {\n        \"query\": {\"type\": \"terminal\", \"service\": \"full_text\",\n                  \"parameters\": {\"value\": keyword}},\n        \"return_type\": \"entry\",\n        \"request_options\": {\"paginate\": {\"rows\": rows}},\n    }\n    r = requests.post(SEARCH, json=payload, timeout=30)\n    r.raise_for_status()\n    data = r.json()\n    return [hit[\"identifier\"] for hit in data[\"result_set\"]], data[\"total_count\"]\n\nids, total = text_search(\"hemoglobin\")\nprint(f\"Found {total} structures; first batch: {ids[:5]}\")\n```\n\n**Attribute search** uses `service: \"text\"` with structured `attribute`\u002F`operator`\u002F`value` parameters.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef attribute_search(attribute, operator, value, return_type=\"entry\", rows=25):\n    payload = {\n        \"query\": {\"type\": \"terminal\", \"service\": \"text\",\n                  \"parameters\": {\"attribute\": attribute,\n                                 \"operator\": operator,\n                                 \"value\": value}},\n        \"return_type\": return_type,\n        \"request_options\": {\"paginate\": {\"rows\": rows}},\n    }\n    r = requests.post(SEARCH, json=payload, timeout=30)\n    r.raise_for_status()\n    return r.json()\n\n# Human proteins\nhuman = attribute_search(\"rcsb_entity_source_organism.scientific_name\",\n                          \"exact_match\", \"Homo sapiens\", rows=5)\nprint(f\"Human structures: {human['total_count']}\")\n\n# X-ray only\nxray = attribute_search(\"exptl.method\", \"exact_match\", \"X-RAY DIFFRACTION\", rows=5)\nprint(f\"X-ray structures: {xray['total_count']}\")\n\n# Resolution range: 1.5–2.5 Å\nres = attribute_search(\n    \"rcsb_entry_info.resolution_combined\", \"range\",\n    {\"from\": 1.5, \"to\": 2.5, \"include_lower\": True, \"include_upper\": True},\n    rows=5\n)\nprint(f\"1.5–2.5 Å: {res['total_count']}\")\n```\n\n### Module 2: Sequence Similarity Search\n\nFind structures with similar sequences using MMseqs2. Service is `\"sequence\"`; `target` selects protein vs. nucleic acid.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\nkras_seq = (\"MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ\"\n            \"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS\"\n            \"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK\")\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"sequence\",\n        \"parameters\": {\n            \"target\": \"pdb_protein_sequence\",  # or \"pdb_dna_sequence\", \"pdb_rna_sequence\"\n            \"value\": kras_seq,\n            \"evalue_cutoff\": 0.1,\n            \"identity_cutoff\": 0.9,\n        },\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\ndata = r.json()\nprint(f\"KRAS-like hits: {data['total_count']}\")\nfor hit in data[\"result_set\"][:5]:\n    print(f\"  {hit['identifier']}  score={hit.get('score', 'n\u002Fa')}\")\n```\n\n### Module 3: Structure Similarity Search\n\nFind structures with similar 3D geometry using BioZernike descriptors. Service is `\"structure\"`; pass the reference entry + assembly ID.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"structure\",\n        \"parameters\": {\n            \"value\": {\"entry_id\": \"4HHB\", \"assembly_id\": \"1\"},\n            \"operator\": \"strict_shape_match\",  # or \"relaxed_shape_match\"\n        },\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\ndata = r.json()\nprint(f\"Structurally similar to 4HHB: {data['total_count']}\")\nfor hit in data[\"result_set\"][:5]:\n    print(f\"  {hit['identifier']}  score={hit.get('score', 'n\u002Fa')}\")\n```\n\n### Module 4: Data Retrieval (GraphQL)\n\nThe GraphQL endpoint at `data.rcsb.org\u002Fgraphql` 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).\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\n# Entry-level metadata\ngql = \"\"\"{ entry(entry_id: \"4HHB\") {\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count nonpolymer_entity_count }\n  rcsb_accession_info { deposit_date initial_release_date }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nentry = r.json()[\"data\"][\"entry\"]\nprint(f\"Title       : {entry['struct']['title']}\")\nprint(f\"Method      : {entry['exptl'][0]['method']}\")\nprint(f\"Resolution  : {entry['rcsb_entry_info']['resolution_combined']} Å\")\nprint(f\"Atoms       : {entry['rcsb_entry_info']['deposited_atom_count']}\")\n```\n\n```python\n# Polymer entity (sequence, organism, MW)\ngql = \"\"\"{ polymer_entity(entry_id: \"4HHB\", entity_id: \"1\") {\n  entity_poly { pdbx_seq_one_letter_code }\n  rcsb_polymer_entity { formula_weight }\n  rcsb_entity_source_organism { scientific_name ncbi_taxonomy_id }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\npe = r.json()[\"data\"][\"polymer_entity\"]\nprint(f\"Sequence (first 50): {pe['entity_poly']['pdbx_seq_one_letter_code'][:50]}\")\nprint(f\"Organism : {pe['rcsb_entity_source_organism'][0]['scientific_name']}\")\nprint(f\"MW       : {pe['rcsb_polymer_entity']['formula_weight']}\")\n```\n\n```python\n# Batch: pull metadata for many entries in one request\ngql = \"\"\"{ entries(entry_ids: [\"4HHB\", \"1A3N\", \"1HHB\"]) {\n  rcsb_id\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor e in r.json()[\"data\"][\"entries\"]:\n    res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n    print(f\"  {e['rcsb_id']}: {e['exptl'][0]['method']:\u003C25} {res} Å  — {e['struct']['title'][:40]}\")\n```\n\n### Module 5: File Download\n\nCoordinate files (mmCIF, PDB, FASTA, assembly variants) are served directly from `files.rcsb.org`.\n\n```python\nimport requests\n\ndef download_structure(pdb_id, fmt=\"cif\", output_dir=\".\"):\n    \"\"\"Download mmCIF \u002F PDB \u002F FASTA. URLs: .pdb, .cif, \u002Ffasta\u002Fentry\u002F{ID}, .pdb1 (assembly).\"\"\"\n    url = f\"https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{pdb_id}.{fmt}\"\n    r = requests.get(url, timeout=60)\n    if r.status_code == 200:\n        path = f\"{output_dir}\u002F{pdb_id}.{fmt}\"\n        # mmCIF \u002F PDB are text; assemblies and biological units are also text\n        with open(path, \"w\") as f:\n            f.write(r.text)\n        print(f\"Downloaded {path}  ({len(r.text)\u002F1024:.1f} KB)\")\n        return path\n    print(f\"HTTP {r.status_code} for {pdb_id}.{fmt}\")\n    return None\n\ndownload_structure(\"4HHB\", fmt=\"cif\")\ndownload_structure(\"4HHB\", fmt=\"pdb\")\n```\n\n```python\n# FASTA sequence for an entry\nr = requests.get(\"https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F4HHB\", timeout=30)\nr.raise_for_status()\nprint(r.text[:400])\n```\n\n### Module 6: Query Composition (group + logical_operator)\n\nCombine terminal queries with `type: \"group\"` and a `logical_operator` of `\"and\"` \u002F `\"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).\n\n```python\nimport requests, datetime\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\n# AND: high-resolution human structures\nq_and = {\n    \"type\": \"group\", \"logical_operator\": \"and\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                        \"operator\": \"less\", \"value\": 2.0}},\n    ],\n}\n\n# OR: human or mouse\nq_or = {\n    \"type\": \"group\", \"logical_operator\": \"or\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Mus musculus\"}},\n    ],\n}\n\n# Combined: recent (last 30 days) + high-quality\none_month_ago = (datetime.date.today() - datetime.timedelta(days=30)).isoformat()\ntoday = datetime.date.today().isoformat()\nq_recent_hq = {\n    \"type\": \"group\", \"logical_operator\": \"and\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                        \"operator\": \"less\", \"value\": 2.0}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"refine.ls_R_factor_R_free\",\n                        \"operator\": \"less\", \"value\": 0.25}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_accession_info.initial_release_date\",\n                        \"operator\": \"range\",\n                        \"value\": {\"from\": one_month_ago, \"to\": today,\n                                  \"include_lower\": True, \"include_upper\": True}}},\n    ],\n}\n\npayload = {\"query\": q_recent_hq, \"return_type\": \"entry\",\n           \"request_options\": {\"paginate\": {\"rows\": 5}}}\nr = requests.post(SEARCH, json=payload, timeout=30)\nprint(f\"Recent high-quality: {r.json()['total_count']} structures\")\n```\n\n### Module 7: Pagination + Batch with Rate Limiting\n\nSearch 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).\n\n```python\nimport requests, time\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef search_all(query_node, return_type=\"entry\", page=100, max_results=None, delay=0.3):\n    \"\"\"Paginate through every result; rate-limit between pages.\"\"\"\n    out, start = [], 0\n    while True:\n        payload = {\"query\": query_node, \"return_type\": return_type,\n                   \"request_options\": {\"paginate\": {\"start\": start, \"rows\": page}}}\n        r = requests.post(SEARCH, json=payload, timeout=30)\n        r.raise_for_status()\n        d = r.json()\n        batch = [h[\"identifier\"] for h in d.get(\"result_set\", [])]\n        if not batch:\n            break\n        out.extend(batch)\n        if max_results and len(out) >= max_results:\n            return out[:max_results]\n        if len(batch) \u003C page or len(out) >= d.get(\"total_count\", 0):\n            break\n        start += page\n        time.sleep(delay)\n    return out\n\n# Example: every insulin entry\nids = search_all(\n    {\"type\": \"terminal\", \"service\": \"full_text\", \"parameters\": {\"value\": \"insulin\"}},\n    max_results=300,\n)\nprint(f\"Insulin entries collected: {len(ids)}\")\n```\n\n```python\n# Batch metadata fetch via GraphQL `entries(...)` to avoid one round-trip per ID\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\ndef batch_metadata(pdb_ids, chunk=50):\n    \"\"\"Fetch (title, method, resolution) for many entries with one POST per chunk.\"\"\"\n    all_rows = []\n    for i in range(0, len(pdb_ids), chunk):\n        ids_arr = pdb_ids[i:i+chunk]\n        ids_str = \", \".join(f'\"{p}\"' for p in ids_arr)\n        gql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n            rcsb_id\n            struct {{ title }}\n            exptl {{ method }}\n            rcsb_entry_info {{ resolution_combined }}\n        }} }}\"\"\"\n        r = requests.post(DATA, json={\"query\": gql}, timeout=60)\n        r.raise_for_status()\n        for e in r.json()[\"data\"][\"entries\"]:\n            res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n            all_rows.append({\n                \"pdb_id\": e[\"rcsb_id\"],\n                \"method\": e[\"exptl\"][0][\"method\"] if e[\"exptl\"] else None,\n                \"resolution\": res[0] if isinstance(res, list) and res else res,\n                \"title\": e[\"struct\"][\"title\"],\n            })\n    return all_rows\n\nrows = batch_metadata(ids[:20])\nfor r in rows[:5]:\n    print(f\"  {r['pdb_id']}: {r['method']:\u003C25} {r['resolution']} Å  — {r['title'][:50]}\")\n```\n\n## Key Concepts\n\n### Search Service Cheat Sheet\n\n| Service | Use case | Required parameters |\n|---|---|---|\n| `full_text` | Free-text keyword across all indexed fields | `value` (string) |\n| `text` | Structured attribute filter | `attribute`, `operator`, `value` |\n| `sequence` | MMseqs2 sequence similarity | `target` ∈ {`pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence`}, `value` (sequence), `evalue_cutoff`, `identity_cutoff` |\n| `seqmotif` | Pattern \u002F regex \u002F PROSITE motif | `value` (pattern), `pattern_type` ∈ {`simple`, `prosite`, `regex`} |\n| `structure` | 3D shape similarity (BioZernike) | `value` (`{entry_id, assembly_id}`), `operator` ∈ {`strict_shape_match`, `relaxed_shape_match`} |\n| `strucmotif` | 3D residue-arrangement motif | `value` (residue list), `rmsd_cutoff` |\n| `chemical` | Ligand similarity by SMILES\u002FInChI | `value`, `match_type` ∈ {`graph-exact`, `graph-relaxed`, `fingerprint-similarity`, `sub-structure-stereo-relaxed`} |\n\n### AttributeQuery Operators (service: `\"text\"`)\n\n| Operator | Value shape | Example |\n|---|---|---|\n| `exact_match` | string | `\"Homo sapiens\"` |\n| `contains_words` \u002F `contains_phrase` | string | `\"tyrosine kinase\"` |\n| `equals` \u002F `greater` \u002F `less` \u002F `greater_or_equal` \u002F `less_or_equal` | number | `2.0` |\n| `range` | `{from, to, include_lower, include_upper}` | `{\"from\": 1.5, \"to\": 2.5, \"include_lower\": True, \"include_upper\": True}` |\n| `exists` | (none) | — |\n| `in` | array | `[\"X-RAY DIFFRACTION\", \"ELECTRON MICROSCOPY\"]` |\n\n### Return Types\n\n`return_type` controls the granularity of identifiers in `result_set`:\n\n| `return_type` | Identifier shape | Example |\n|---|---|---|\n| `entry` | `4HHB` | One per PDB ID |\n| `polymer_entity` | `4HHB_1` | One per polymer chain entity |\n| `non_polymer_entity` | `4HHB_2` | Ligands, cofactors |\n| `assembly` | `4HHB-1` | Biological unit |\n| `polymer_instance` | `4HHB.A` | Individual chain coordinates |\n| `mol_definition` | `HEM` | Chemical component (PDB ligand code) |\n\n### Common Data API GraphQL Roots\n\n| Root | Identifier shape | Returns |\n|---|---|---|\n| `entry(entry_id: ...)` | `\"4HHB\"` | Entry-level metadata |\n| `entries(entry_ids: [...])` | array | Batch entry lookup |\n| `polymer_entity(entry_id: ..., entity_id: ...)` | `\"4HHB\"`, `\"1\"` | Sequence + organism |\n| `polymer_entity_instance(entry_id: ..., asym_id: ...)` | `\"4HHB\"`, `\"A\"` | Chain-level coords\u002Fmetadata |\n| `assembly(entry_id: ..., assembly_id: ...)` | `\"4HHB\"`, `\"1\"` | Biological assembly |\n| `chem_comp(comp_id: ...)` | `\"HEM\"` | Small molecule reference |\n\n### File Formats\n\n| Format | URL pattern | Notes |\n|---|---|---|\n| mmCIF | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.cif` | Recommended; no atom-count limit |\n| PDB | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.pdb` | Legacy; 99,999 atom limit |\n| Assembly (mmCIF) | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}-assembly{N}.cif` | Biological unit |\n| FASTA | `https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F{id}` | Sequence only |\n\n## Common Workflows\n\n### Workflow 1: Drug Target Structure Set\n\n**Goal**: Find high-resolution human EGFR structures with bound ligands.\n\n```python\nimport requests, time\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\npayload = {\n    \"query\": {\n        \"type\": \"group\", \"logical_operator\": \"and\",\n        \"nodes\": [\n            {\"type\": \"terminal\", \"service\": \"full_text\",\n             \"parameters\": {\"value\": \"EGFR epidermal growth factor receptor\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                            \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                            \"operator\": \"less\", \"value\": 2.5}},\n        ],\n    },\n    \"return_type\": \"entry\",\n    \"request_options\": {\"paginate\": {\"rows\": 50}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\npdb_ids = [h[\"identifier\"] for h in r.json()[\"result_set\"]]\nprint(f\"EGFR ≤2.5 Å human structures: {len(pdb_ids)}\")\n\n# Filter to entries with bound ligands via batch GraphQL\nids_str = \", \".join(f'\"{p}\"' for p in pdb_ids[:20])\ngql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n    rcsb_id\n    struct {{ title }}\n    rcsb_entry_info {{ resolution_combined nonpolymer_entity_count }}\n}} }}\"\"\"\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=60)\nfor e in r2.json()[\"data\"][\"entries\"]:\n    n_lig = e[\"rcsb_entry_info\"][\"nonpolymer_entity_count\"] or 0\n    if n_lig > 0:\n        res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n        res_v = res[0] if isinstance(res, list) else res\n        print(f\"  {e['rcsb_id']}: {res_v} Å, ligands={n_lig}  — {e['struct']['title'][:60]}\")\n    time.sleep(0.05)\n```\n\n### Workflow 2: Protein Family — Sequence-Similar Structures\n\n**Goal**: Find all PDB structures with sequence similar to a query (KRAS), then summarize their resolution + experimental method.\n\n```python\nimport requests, time\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\nkras_seq = (\"MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ\"\n            \"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS\"\n            \"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK\")\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"sequence\",\n        \"parameters\": {\"target\": \"pdb_protein_sequence\", \"value\": kras_seq,\n                       \"evalue_cutoff\": 1e-5, \"identity_cutoff\": 0.5},\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 20}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nhits = r.json()[\"result_set\"]\nprint(f\"KRAS family hits: {len(hits)}\")\n\n# Unique PDB IDs from polymer_entity identifiers (e.g., \"4OBE_1\" -> \"4OBE\")\nentry_ids = sorted({h[\"identifier\"].split(\"_\")[0] for h in hits})\n\n# Batch metadata\nids_str = \", \".join(f'\"{p}\"' for p in entry_ids)\ngql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n    rcsb_id\n    struct {{ title }}\n    exptl {{ method }}\n    rcsb_entry_info {{ resolution_combined }}\n}} }}\"\"\"\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=60)\nfor e in sorted(r2.json()[\"data\"][\"entries\"],\n                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)):\n    res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n    res_v = res[0] if isinstance(res, list) else res\n    print(f\"  {e['rcsb_id']}: {res_v} Å  {e['exptl'][0]['method']:\u003C25} {e['struct']['title'][:50]}\")\n```\n\n### Workflow 3: Download + Parse with BioPython\n\n**Goal**: Download mmCIF, then enumerate chains with BioPython.\n\n```python\nimport requests\nfrom Bio.PDB import MMCIFParser\n\npdb_id = \"4HHB\"\nr = requests.get(f\"https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{pdb_id}.cif\", timeout=60)\nr.raise_for_status()\nwith open(f\"{pdb_id}.cif\", \"w\") as f:\n    f.write(r.text)\n\nparser = MMCIFParser(QUIET=True)\nstructure = parser.get_structure(pdb_id, f\"{pdb_id}.cif\")\nfor model in structure:\n    for chain in model:\n        std_res = [r for r in chain if r.id[0] == \" \"]\n        atoms = sum(len(list(r.get_atoms())) for r in std_res)\n        print(f\"Chain {chain.id}: {len(std_res)} residues, {atoms} atoms\")\n```\n\n## Key Parameters\n\n| Parameter | Endpoint | Default | Range \u002F Options | Effect |\n|-----------|----------|---------|-----------------|--------|\n| `value` | search `sequence` | required | protein\u002FDNA\u002FRNA sequence string | Query sequence for MMseqs2 |\n| `evalue_cutoff` | search `sequence` | `0.1` | `1e-10`–`10` | E-value threshold |\n| `identity_cutoff` | search `sequence` | `0.9` | `0.0`–`1.0` | Minimum identity fraction |\n| `target` | search `sequence` | `\"pdb_protein_sequence\"` | `pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence` | Sequence type |\n| `operator` | search `text` (attribute) | required | see Attribute Operators | Comparison kind |\n| `operator` | search `structure` | `strict_shape_match` | `strict_shape_match`, `relaxed_shape_match` | 3D match stringency |\n| `return_type` | all search | `entry` | `entry`, `polymer_entity`, `assembly`, `polymer_instance`, `mol_definition`, … | Identifier granularity |\n| `paginate.start` \u002F `paginate.rows` | `request_options` | `0` \u002F `25` | up to ~10000 rows\u002Fpage in practice | Pagination window |\n| GraphQL field `entries(entry_ids: [...])` | `data.rcsb.org\u002Fgraphql` | — | array of PDB IDs | Batch entry metadata |\n\n## Best Practices\n\n1. **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.\n\n2. **Use `full_text` vs `text` deliberately**: free-text keyword search needs `\"service\": \"full_text\"`. Structured attribute filters need `\"service\": \"text\"`. They are not interchangeable.\n\n3. **mmCIF over PDB format**: PDB format is being phased out and has a 99,999 atom limit. Always download `.cif` for new code.\n\n4. **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.\n\n5. **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.\n\n6. **Inspect the payload before posting**: `print(json.dumps(payload, indent=2))` is the cheapest way to debug `HTTP 400` errors.\n\n7. **`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.\n\n## Common Recipes\n\n### Recipe: Get FASTA for a PDB ID\n\n```python\nimport requests\nr = requests.get(\"https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F4HHB\", timeout=30)\nprint(r.text)\n```\n\n### Recipe: All chains in an entry\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\ngql = \"\"\"{ entry(entry_id: \"4HHB\") {\n  polymer_entities {\n    rcsb_id\n    rcsb_polymer_entity_container_identifiers { auth_asym_ids }\n    entity_poly { rcsb_entity_polymer_type pdbx_seq_one_letter_code_can }\n  }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor pe in r.json()[\"data\"][\"entry\"][\"polymer_entities\"]:\n    chains = pe[\"rcsb_polymer_entity_container_identifiers\"][\"auth_asym_ids\"]\n    seq    = pe[\"entity_poly\"][\"pdbx_seq_one_letter_code_can\"][:50]\n    print(f\"  {pe['rcsb_id']} chains={chains}  type={pe['entity_poly']['rcsb_entity_polymer_type']}  seq={seq}…\")\n```\n\n### Recipe: List all ligands in an entry\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\ngql = \"\"\"{ entry(entry_id: \"1IEP\") {\n  nonpolymer_entities {\n    rcsb_id\n    nonpolymer_comp { chem_comp { id name formula } }\n  }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor npe in r.json()[\"data\"][\"entry\"][\"nonpolymer_entities\"]:\n    cc = npe[\"nonpolymer_comp\"][\"chem_comp\"]\n    print(f\"  {npe['rcsb_id']}: {cc['id']} ({cc['name']})  {cc['formula']}\")\n```\n\n### Recipe: Inspect available search attributes\n\nThe Search API exposes a JSON schema at `https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema`. Use it to look up valid attribute paths.\n\n```python\nimport requests\nr = requests.get(\"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema\", timeout=30)\nschema = r.json()\n# Schema lists hundreds of attribute paths; sample a few\nsample_paths = [k for k in schema if \"resolution\" in k.lower()][:5]\nprint(sample_paths)\n```\n\n## Troubleshooting\n\n| Problem | Cause | Solution |\n|---------|-------|----------|\n| `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 |\n| `HTTP 400` with cryptic schema message | Bad operator\u002Fvalue shape | Check the AttributeQuery Operators table; `range` needs the `{from,to,include_lower,include_upper}` dict |\n| Empty `result_set` | Filters too strict | Relax filters one at a time; verify attribute names via the schema endpoint |\n| `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 |\n| `HTTP 429 Too Many Requests` | Burst pace | Add `time.sleep(0.3)` between requests; exponential backoff on 429 |\n| `HTTP 500` from search | Server-side glitch | Retry after 5–10 s; check `status.rcsb.org` |\n| Downloaded `.pdb` file truncated | >99,999 atoms (legacy format limit) | Download `.cif` instead |\n| 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 \u003Chttps:\u002F\u002Fdata.rcsb.org\u002Findex.html#graphql-api> |\n\n## Related Skills\n\n- **alphafold-database-access** — AI-predicted structures; use when no experimental structure exists\n- **uniprot-protein-database** — protein annotations, sequences, ID mapping (UniProt accession needed for AlphaFold)\n- **biopython-molecular-biology** — parse downloaded PDB\u002FmmCIF files, extract coordinates, compute distances\n- **autodock-vina-docking** — downstream molecular docking using PDB structures as receptors\n- **rdkit-cheminformatics** — analyze ligands extracted from PDB complexes\n\n## References\n\n- [RCSB PDB](https:\u002F\u002Fwww.rcsb.org) — main portal and web search\n- [Search API v2 docs](https:\u002F\u002Fsearch.rcsb.org\u002F) — full JSON-payload reference and Swagger\n- [Data API GraphQL browser](https:\u002F\u002Fdata.rcsb.org\u002Findex.html#graphql-api) — schema explorer\n- [Search API attribute schema](https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema) — JSON of every searchable attribute\n- [RCSB PDB Web APIs overview](https:\u002F\u002Fwww.rcsb.org\u002Fdocs\u002Fprogrammatic-access\u002Fweb-apis-overview) — index of all programmatic surfaces\n- For SDK-based usage, see the `rcsb-api` PyPI package; this SKILL.md uses the underlying REST\u002FGraphQL directly so no SDK install is needed.\n","# PDB Database\n\n> **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.\n\n## Overview\n\nRCSB 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:\n\n| API | Base URL | Method | Purpose |\n|---|---|---|---|\n| **Search** | `https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery` | `POST` JSON | Find PDB IDs by text, attribute filters, sequence, or 3D similarity |\n| **Data** | `https:\u002F\u002Fdata.rcsb.org\u002Fgraphql` | `POST` GraphQL | Retrieve structured metadata (entries, polymer entities, assemblies, ligands) |\n| **Files** | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.{format}` | `GET` | Download coordinate files (mmCIF, PDB, FASTA) |\n\nUse this skill for programmatic structural biology queries, drug target analysis, and protein family comparisons.\n\n## When to Use\n\n- Searching for protein or nucleic acid crystal\u002Fcryo-EM\u002FNMR structures by keyword or property\n- Finding structures similar to a query sequence (MMseqs2) or 3D geometry (BioZernike)\n- Retrieving experimental metadata (resolution, method, organism, deposition date) for structure sets\n- Downloading coordinate files (PDB, mmCIF) for molecular dynamics, docking, or visualization\n- Building structure-based datasets for machine learning or drug discovery pipelines\n- Comparing protein-ligand complexes across a target family\n- For AlphaFold predicted structures, use `alphafold-database-access` instead\n- For protein sequence\u002Fannotation queries without structures, use `uniprot-protein-database` instead\n\n## Prerequisites\n\n- **Python packages**: `requests` (only requirement). Optional: `biopython` for parsing downloaded coordinate files.\n- **No API key required**: RCSB PDB is freely accessible.\n- **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.\n\n```bash\npip install requests\n# Optional, for coordinate parsing:\npip install biopython\n```\n\n## Quick Start\n\nTypical search-then-fetch pattern: hit the Search API, get a list of PDB IDs, then resolve metadata via the GraphQL Data API.\n\n```python\nimport requests\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\n# 1. Search: human X-ray structures of \"kinase\" at resolution \u003C 2.0 Å\npayload = {\n    \"query\": {\n        \"type\": \"group\", \"logical_operator\": \"and\",\n        \"nodes\": [\n            {\"type\": \"terminal\", \"service\": \"full_text\",\n             \"parameters\": {\"value\": \"kinase\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                            \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                            \"operator\": \"less\", \"value\": 2.0}},\n        ],\n    },\n    \"return_type\": \"entry\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\nresult = r.json()\npdb_ids = [hit[\"identifier\"] for hit in result[\"result_set\"]]\nprint(f\"Total matches: {result['total_count']}, first batch: {pdb_ids}\")\n\n# 2. Fetch metadata for the first hit via GraphQL\ngql = \"\"\"{ entry(entry_id: \"%s\") {\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count }\n} }\"\"\" % pdb_ids[0]\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=30)\nentry = r2.json()[\"data\"][\"entry\"]\nprint(entry[\"struct\"][\"title\"])\nprint(f\"Method: {entry['exptl'][0]['method']}, Resolution: {entry['rcsb_entry_info']['resolution_combined']} Å\")\n```\n\n## Core API\n\n### Module 1: Text and Attribute Search\n\n**Free-text search** uses `service: \"full_text\"` and searches across all indexed fields.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef text_search(keyword, rows=25):\n    payload = {\n        \"query\": {\"type\": \"terminal\", \"service\": \"full_text\",\n                  \"parameters\": {\"value\": keyword}},\n        \"return_type\": \"entry\",\n        \"request_options\": {\"paginate\": {\"rows\": rows}},\n    }\n    r = requests.post(SEARCH, json=payload, timeout=30)\n    r.raise_for_status()\n    data = r.json()\n    return [hit[\"identifier\"] for hit in data[\"result_set\"]], data[\"total_count\"]\n\nids, total = text_search(\"hemoglobin\")\nprint(f\"Found {total} structures; first batch: {ids[:5]}\")\n```\n\n**Attribute search** uses `service: \"text\"` with structured `attribute`\u002F`operator`\u002F`value` parameters.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef attribute_search(attribute, operator, value, return_type=\"entry\", rows=25):\n    payload = {\n        \"query\": {\"type\": \"terminal\", \"service\": \"text\",\n                  \"parameters\": {\"attribute\": attribute,\n                                 \"operator\": operator,\n                                 \"value\": value}},\n        \"return_type\": return_type,\n        \"request_options\": {\"paginate\": {\"rows\": rows}},\n    }\n    r = requests.post(SEARCH, json=payload, timeout=30)\n    r.raise_for_status()\n    return r.json()\n\n# Human proteins\nhuman = attribute_search(\"rcsb_entity_source_organism.scientific_name\",\n                          \"exact_match\", \"Homo sapiens\", rows=5)\nprint(f\"Human structures: {human['total_count']}\")\n\n# X-ray only\nxray = attribute_search(\"exptl.method\", \"exact_match\", \"X-RAY DIFFRACTION\", rows=5)\nprint(f\"X-ray structures: {xray['total_count']}\")\n\n# Resolution range: 1.5–2.5 Å\nres = attribute_search(\n    \"rcsb_entry_info.resolution_combined\", \"range\",\n    {\"from\": 1.5, \"to\": 2.5, \"include_lower\": True, \"include_upper\": True},\n    rows=5\n)\nprint(f\"1.5–2.5 Å: {res['total_count']}\")\n```\n\n### Module 2: Sequence Similarity Search\n\nFind structures with similar sequences using MMseqs2. Service is `\"sequence\"`; `target` selects protein vs. nucleic acid.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\nkras_seq = (\"MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ\"\n            \"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS\"\n            \"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK\")\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"sequence\",\n        \"parameters\": {\n            \"target\": \"pdb_protein_sequence\",  # or \"pdb_dna_sequence\", \"pdb_rna_sequence\"\n            \"value\": kras_seq,\n            \"evalue_cutoff\": 0.1,\n            \"identity_cutoff\": 0.9,\n        },\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\ndata = r.json()\nprint(f\"KRAS-like hits: {data['total_count']}\")\nfor hit in data[\"result_set\"][:5]:\n    print(f\"  {hit['identifier']}  score={hit.get('score', 'n\u002Fa')}\")\n```\n\n### Module 3: Structure Similarity Search\n\nFind structures with similar 3D geometry using BioZernike descriptors. Service is `\"structure\"`; pass the reference entry + assembly ID.\n\n```python\nimport requests\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"structure\",\n        \"parameters\": {\n            \"value\": {\"entry_id\": \"4HHB\", \"assembly_id\": \"1\"},\n            \"operator\": \"strict_shape_match\",  # or \"relaxed_shape_match\"\n        },\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 10}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\ndata = r.json()\nprint(f\"Structurally similar to 4HHB: {data['total_count']}\")\nfor hit in data[\"result_set\"][:5]:\n    print(f\"  {hit['identifier']}  score={hit.get('score', 'n\u002Fa')}\")\n```\n\n### Module 4: Data Retrieval (GraphQL)\n\nThe GraphQL endpoint at `data.rcsb.org\u002Fgraphql` 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).\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\n# Entry-level metadata\ngql = \"\"\"{ entry(entry_id: \"4HHB\") {\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count nonpolymer_entity_count }\n  rcsb_accession_info { deposit_date initial_release_date }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nentry = r.json()[\"data\"][\"entry\"]\nprint(f\"Title       : {entry['struct']['title']}\")\nprint(f\"Method      : {entry['exptl'][0]['method']}\")\nprint(f\"Resolution  : {entry['rcsb_entry_info']['resolution_combined']} Å\")\nprint(f\"Atoms       : {entry['rcsb_entry_info']['deposited_atom_count']}\")\n```\n\n```python\n# Polymer entity (sequence, organism, MW)\ngql = \"\"\"{ polymer_entity(entry_id: \"4HHB\", entity_id: \"1\") {\n  entity_poly { pdbx_seq_one_letter_code }\n  rcsb_polymer_entity { formula_weight }\n  rcsb_entity_source_organism { scientific_name ncbi_taxonomy_id }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\npe = r.json()[\"data\"][\"polymer_entity\"]\nprint(f\"Sequence (first 50): {pe['entity_poly']['pdbx_seq_one_letter_code'][:50]}\")\nprint(f\"Organism : {pe['rcsb_entity_source_organism'][0]['scientific_name']}\")\nprint(f\"MW       : {pe['rcsb_polymer_entity']['formula_weight']}\")\n```\n\n```python\n# Batch: pull metadata for many entries in one request\ngql = \"\"\"{ entries(entry_ids: [\"4HHB\", \"1A3N\", \"1HHB\"]) {\n  rcsb_id\n  struct { title }\n  exptl { method }\n  rcsb_entry_info { resolution_combined }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor e in r.json()[\"data\"][\"entries\"]:\n    res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n    print(f\"  {e['rcsb_id']}: {e['exptl'][0]['method']:\u003C25} {res} Å  — {e['struct']['title'][:40]}\")\n```\n\n### Module 5: File Download\n\nCoordinate files (mmCIF, PDB, FASTA, assembly variants) are served directly from `files.rcsb.org`.\n\n```python\nimport requests\n\ndef download_structure(pdb_id, fmt=\"cif\", output_dir=\".\"):\n    \"\"\"Download mmCIF \u002F PDB \u002F FASTA. URLs: .pdb, .cif, \u002Ffasta\u002Fentry\u002F{ID}, .pdb1 (assembly).\"\"\"\n    url = f\"https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{pdb_id}.{fmt}\"\n    r = requests.get(url, timeout=60)\n    if r.status_code == 200:\n        path = f\"{output_dir}\u002F{pdb_id}.{fmt}\"\n        # mmCIF \u002F PDB are text; assemblies and biological units are also text\n        with open(path, \"w\") as f:\n            f.write(r.text)\n        print(f\"Downloaded {path}  ({len(r.text)\u002F1024:.1f} KB)\")\n        return path\n    print(f\"HTTP {r.status_code} for {pdb_id}.{fmt}\")\n    return None\n\ndownload_structure(\"4HHB\", fmt=\"cif\")\ndownload_structure(\"4HHB\", fmt=\"pdb\")\n```\n\n```python\n# FASTA sequence for an entry\nr = requests.get(\"https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F4HHB\", timeout=30)\nr.raise_for_status()\nprint(r.text[:400])\n```\n\n### Module 6: Query Composition (group + logical_operator)\n\nCombine terminal queries with `type: \"group\"` and a `logical_operator` of `\"and\"` \u002F `\"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).\n\n```python\nimport requests, datetime\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\n# AND: high-resolution human structures\nq_and = {\n    \"type\": \"group\", \"logical_operator\": \"and\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                        \"operator\": \"less\", \"value\": 2.0}},\n    ],\n}\n\n# OR: human or mouse\nq_or = {\n    \"type\": \"group\", \"logical_operator\": \"or\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                        \"operator\": \"exact_match\", \"value\": \"Mus musculus\"}},\n    ],\n}\n\n# Combined: recent (last 30 days) + high-quality\none_month_ago = (datetime.date.today() - datetime.timedelta(days=30)).isoformat()\ntoday = datetime.date.today().isoformat()\nq_recent_hq = {\n    \"type\": \"group\", \"logical_operator\": \"and\",\n    \"nodes\": [\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                        \"operator\": \"less\", \"value\": 2.0}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"refine.ls_R_factor_R_free\",\n                        \"operator\": \"less\", \"value\": 0.25}},\n        {\"type\": \"terminal\", \"service\": \"text\",\n         \"parameters\": {\"attribute\": \"rcsb_accession_info.initial_release_date\",\n                        \"operator\": \"range\",\n                        \"value\": {\"from\": one_month_ago, \"to\": today,\n                                  \"include_lower\": True, \"include_upper\": True}}},\n    ],\n}\n\npayload = {\"query\": q_recent_hq, \"return_type\": \"entry\",\n           \"request_options\": {\"paginate\": {\"rows\": 5}}}\nr = requests.post(SEARCH, json=payload, timeout=30)\nprint(f\"Recent high-quality: {r.json()['total_count']} structures\")\n```\n\n### Module 7: Pagination + Batch with Rate Limiting\n\nSearch 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).\n\n```python\nimport requests, time\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\n\ndef search_all(query_node, return_type=\"entry\", page=100, max_results=None, delay=0.3):\n    \"\"\"Paginate through every result; rate-limit between pages.\"\"\"\n    out, start = [], 0\n    while True:\n        payload = {\"query\": query_node, \"return_type\": return_type,\n                   \"request_options\": {\"paginate\": {\"start\": start, \"rows\": page}}}\n        r = requests.post(SEARCH, json=payload, timeout=30)\n        r.raise_for_status()\n        d = r.json()\n        batch = [h[\"identifier\"] for h in d.get(\"result_set\", [])]\n        if not batch:\n            break\n        out.extend(batch)\n        if max_results and len(out) >= max_results:\n            return out[:max_results]\n        if len(batch) \u003C page or len(out) >= d.get(\"total_count\", 0):\n            break\n        start += page\n        time.sleep(delay)\n    return out\n\n# Example: every insulin entry\nids = search_all(\n    {\"type\": \"terminal\", \"service\": \"full_text\", \"parameters\": {\"value\": \"insulin\"}},\n    max_results=300,\n)\nprint(f\"Insulin entries collected: {len(ids)}\")\n```\n\n```python\n# Batch metadata fetch via GraphQL `entries(...)` to avoid one round-trip per ID\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\ndef batch_metadata(pdb_ids, chunk=50):\n    \"\"\"Fetch (title, method, resolution) for many entries with one POST per chunk.\"\"\"\n    all_rows = []\n    for i in range(0, len(pdb_ids), chunk):\n        ids_arr = pdb_ids[i:i+chunk]\n        ids_str = \", \".join(f'\"{p}\"' for p in ids_arr)\n        gql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n            rcsb_id\n            struct {{ title }}\n            exptl {{ method }}\n            rcsb_entry_info {{ resolution_combined }}\n        }} }}\"\"\"\n        r = requests.post(DATA, json={\"query\": gql}, timeout=60)\n        r.raise_for_status()\n        for e in r.json()[\"data\"][\"entries\"]:\n            res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n            all_rows.append({\n                \"pdb_id\": e[\"rcsb_id\"],\n                \"method\": e[\"exptl\"][0][\"method\"] if e[\"exptl\"] else None,\n                \"resolution\": res[0] if isinstance(res, list) and res else res,\n                \"title\": e[\"struct\"][\"title\"],\n            })\n    return all_rows\n\nrows = batch_metadata(ids[:20])\nfor r in rows[:5]:\n    print(f\"  {r['pdb_id']}: {r['method']:\u003C25} {r['resolution']} Å  — {r['title'][:50]}\")\n```\n\n## Key Concepts\n\n### Search Service Cheat Sheet\n\n| Service | Use case | Required parameters |\n|---|---|---|\n| `full_text` | Free-text keyword across all indexed fields | `value` (string) |\n| `text` | Structured attribute filter | `attribute`, `operator`, `value` |\n| `sequence` | MMseqs2 sequence similarity | `target` ∈ {`pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence`}, `value` (sequence), `evalue_cutoff`, `identity_cutoff` |\n| `seqmotif` | Pattern \u002F regex \u002F PROSITE motif | `value` (pattern), `pattern_type` ∈ {`simple`, `prosite`, `regex`} |\n| `structure` | 3D shape similarity (BioZernike) | `value` (`{entry_id, assembly_id}`), `operator` ∈ {`strict_shape_match`, `relaxed_shape_match`} |\n| `strucmotif` | 3D residue-arrangement motif | `value` (residue list), `rmsd_cutoff` |\n| `chemical` | Ligand similarity by SMILES\u002FInChI | `value`, `match_type` ∈ {`graph-exact`, `graph-relaxed`, `fingerprint-similarity`, `sub-structure-stereo-relaxed`} |\n\n### AttributeQuery Operators (service: `\"text\"`)\n\n| Operator | Value shape | Example |\n|---|---|---|\n| `exact_match` | string | `\"Homo sapiens\"` |\n| `contains_words` \u002F `contains_phrase` | string | `\"tyrosine kinase\"` |\n| `equals` \u002F `greater` \u002F `less` \u002F `greater_or_equal` \u002F `less_or_equal` | number | `2.0` |\n| `range` | `{from, to, include_lower, include_upper}` | `{\"from\": 1.5, \"to\": 2.5, \"include_lower\": True, \"include_upper\": True}` |\n| `exists` | (none) | — |\n| `in` | array | `[\"X-RAY DIFFRACTION\", \"ELECTRON MICROSCOPY\"]` |\n\n### Return Types\n\n`return_type` controls the granularity of identifiers in `result_set`:\n\n| `return_type` | Identifier shape | Example |\n|---|---|---|\n| `entry` | `4HHB` | One per PDB ID |\n| `polymer_entity` | `4HHB_1` | One per polymer chain entity |\n| `non_polymer_entity` | `4HHB_2` | Ligands, cofactors |\n| `assembly` | `4HHB-1` | Biological unit |\n| `polymer_instance` | `4HHB.A` | Individual chain coordinates |\n| `mol_definition` | `HEM` | Chemical component (PDB ligand code) |\n\n### Common Data API GraphQL Roots\n\n| Root | Identifier shape | Returns |\n|---|---|---|\n| `entry(entry_id: ...)` | `\"4HHB\"` | Entry-level metadata |\n| `entries(entry_ids: [...])` | array | Batch entry lookup |\n| `polymer_entity(entry_id: ..., entity_id: ...)` | `\"4HHB\"`, `\"1\"` | Sequence + organism |\n| `polymer_entity_instance(entry_id: ..., asym_id: ...)` | `\"4HHB\"`, `\"A\"` | Chain-level coords\u002Fmetadata |\n| `assembly(entry_id: ..., assembly_id: ...)` | `\"4HHB\"`, `\"1\"` | Biological assembly |\n| `chem_comp(comp_id: ...)` | `\"HEM\"` | Small molecule reference |\n\n### File Formats\n\n| Format | URL pattern | Notes |\n|---|---|---|\n| mmCIF | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.cif` | Recommended; no atom-count limit |\n| PDB | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}.pdb` | Legacy; 99,999 atom limit |\n| Assembly (mmCIF) | `https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{id}-assembly{N}.cif` | Biological unit |\n| FASTA | `https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F{id}` | Sequence only |\n\n## Common Workflows\n\n### Workflow 1: Drug Target Structure Set\n\n**Goal**: Find high-resolution human EGFR structures with bound ligands.\n\n```python\nimport requests, time\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\npayload = {\n    \"query\": {\n        \"type\": \"group\", \"logical_operator\": \"and\",\n        \"nodes\": [\n            {\"type\": \"terminal\", \"service\": \"full_text\",\n             \"parameters\": {\"value\": \"EGFR epidermal growth factor receptor\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entity_source_organism.scientific_name\",\n                            \"operator\": \"exact_match\", \"value\": \"Homo sapiens\"}},\n            {\"type\": \"terminal\", \"service\": \"text\",\n             \"parameters\": {\"attribute\": \"rcsb_entry_info.resolution_combined\",\n                            \"operator\": \"less\", \"value\": 2.5}},\n        ],\n    },\n    \"return_type\": \"entry\",\n    \"request_options\": {\"paginate\": {\"rows\": 50}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nr.raise_for_status()\npdb_ids = [h[\"identifier\"] for h in r.json()[\"result_set\"]]\nprint(f\"EGFR ≤2.5 Å human structures: {len(pdb_ids)}\")\n\n# Filter to entries with bound ligands via batch GraphQL\nids_str = \", \".join(f'\"{p}\"' for p in pdb_ids[:20])\ngql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n    rcsb_id\n    struct {{ title }}\n    rcsb_entry_info {{ resolution_combined nonpolymer_entity_count }}\n}} }}\"\"\"\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=60)\nfor e in r2.json()[\"data\"][\"entries\"]:\n    n_lig = e[\"rcsb_entry_info\"][\"nonpolymer_entity_count\"] or 0\n    if n_lig > 0:\n        res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n        res_v = res[0] if isinstance(res, list) else res\n        print(f\"  {e['rcsb_id']}: {res_v} Å, ligands={n_lig}  — {e['struct']['title'][:60]}\")\n    time.sleep(0.05)\n```\n\n### Workflow 2: Protein Family — Sequence-Similar Structures\n\n**Goal**: Find all PDB structures with sequence similar to a query (KRAS), then summarize their resolution + experimental method.\n\n```python\nimport requests, time\n\nSEARCH = \"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fquery\"\nDATA   = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\n\nkras_seq = (\"MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ\"\n            \"EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS\"\n            \"RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK\")\n\npayload = {\n    \"query\": {\n        \"type\": \"terminal\", \"service\": \"sequence\",\n        \"parameters\": {\"target\": \"pdb_protein_sequence\", \"value\": kras_seq,\n                       \"evalue_cutoff\": 1e-5, \"identity_cutoff\": 0.5},\n    },\n    \"return_type\": \"polymer_entity\",\n    \"request_options\": {\"paginate\": {\"rows\": 20}},\n}\nr = requests.post(SEARCH, json=payload, timeout=30)\nhits = r.json()[\"result_set\"]\nprint(f\"KRAS family hits: {len(hits)}\")\n\n# Unique PDB IDs from polymer_entity identifiers (e.g., \"4OBE_1\" -> \"4OBE\")\nentry_ids = sorted({h[\"identifier\"].split(\"_\")[0] for h in hits})\n\n# Batch metadata\nids_str = \", \".join(f'\"{p}\"' for p in entry_ids)\ngql = f\"\"\"{{ entries(entry_ids: [{ids_str}]) {{\n    rcsb_id\n    struct {{ title }}\n    exptl {{ method }}\n    rcsb_entry_info {{ resolution_combined }}\n}} }}\"\"\"\nr2 = requests.post(DATA, json={\"query\": gql}, timeout=60)\nfor e in sorted(r2.json()[\"data\"][\"entries\"],\n                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)):\n    res = e[\"rcsb_entry_info\"][\"resolution_combined\"]\n    res_v = res[0] if isinstance(res, list) else res\n    print(f\"  {e['rcsb_id']}: {res_v} Å  {e['exptl'][0]['method']:\u003C25} {e['struct']['title'][:50]}\")\n```\n\n### Workflow 3: Download + Parse with BioPython\n\n**Goal**: Download mmCIF, then enumerate chains with BioPython.\n\n```python\nimport requests\nfrom Bio.PDB import MMCIFParser\n\npdb_id = \"4HHB\"\nr = requests.get(f\"https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{pdb_id}.cif\", timeout=60)\nr.raise_for_status()\nwith open(f\"{pdb_id}.cif\", \"w\") as f:\n    f.write(r.text)\n\nparser = MMCIFParser(QUIET=True)\nstructure = parser.get_structure(pdb_id, f\"{pdb_id}.cif\")\nfor model in structure:\n    for chain in model:\n        std_res = [r for r in chain if r.id[0] == \" \"]\n        atoms = sum(len(list(r.get_atoms())) for r in std_res)\n        print(f\"Chain {chain.id}: {len(std_res)} residues, {atoms} atoms\")\n```\n\n## Key Parameters\n\n| Parameter | Endpoint | Default | Range \u002F Options | Effect |\n|-----------|----------|---------|-----------------|--------|\n| `value` | search `sequence` | required | protein\u002FDNA\u002FRNA sequence string | Query sequence for MMseqs2 |\n| `evalue_cutoff` | search `sequence` | `0.1` | `1e-10`–`10` | E-value threshold |\n| `identity_cutoff` | search `sequence` | `0.9` | `0.0`–`1.0` | Minimum identity fraction |\n| `target` | search `sequence` | `\"pdb_protein_sequence\"` | `pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence` | Sequence type |\n| `operator` | search `text` (attribute) | required | see Attribute Operators | Comparison kind |\n| `operator` | search `structure` | `strict_shape_match` | `strict_shape_match`, `relaxed_shape_match` | 3D match stringency |\n| `return_type` | all search | `entry` | `entry`, `polymer_entity`, `assembly`, `polymer_instance`, `mol_definition`, … | Identifier granularity |\n| `paginate.start` \u002F `paginate.rows` | `request_options` | `0` \u002F `25` | up to ~10000 rows\u002Fpage in practice | Pagination window |\n| GraphQL field `entries(entry_ids: [...])` | `data.rcsb.org\u002Fgraphql` | — | array of PDB IDs | Batch entry metadata |\n\n## Best Practices\n\n1. **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.\n\n2. **Use `full_text` vs `text` deliberately**: free-text keyword search needs `\"service\": \"full_text\"`. Structured attribute filters need `\"service\": \"text\"`. They are not interchangeable.\n\n3. **mmCIF over PDB format**: PDB format is being phased out and has a 99,999 atom limit. Always download `.cif` for new code.\n\n4. **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.\n\n5. **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.\n\n6. **Inspect the payload before posting**: `print(json.dumps(payload, indent=2))` is the cheapest way to debug `HTTP 400` errors.\n\n7. **`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.\n\n## Common Recipes\n\n### Recipe: Get FASTA for a PDB ID\n\n```python\nimport requests\nr = requests.get(\"https:\u002F\u002Fwww.rcsb.org\u002Ffasta\u002Fentry\u002F4HHB\", timeout=30)\nprint(r.text)\n```\n\n### Recipe: All chains in an entry\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\ngql = \"\"\"{ entry(entry_id: \"4HHB\") {\n  polymer_entities {\n    rcsb_id\n    rcsb_polymer_entity_container_identifiers { auth_asym_ids }\n    entity_poly { rcsb_entity_polymer_type pdbx_seq_one_letter_code_can }\n  }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor pe in r.json()[\"data\"][\"entry\"][\"polymer_entities\"]:\n    chains = pe[\"rcsb_polymer_entity_container_identifiers\"][\"auth_asym_ids\"]\n    seq    = pe[\"entity_poly\"][\"pdbx_seq_one_letter_code_can\"][:50]\n    print(f\"  {pe['rcsb_id']} chains={chains}  type={pe['entity_poly']['rcsb_entity_polymer_type']}  seq={seq}…\")\n```\n\n### Recipe: List all ligands in an entry\n\n```python\nimport requests\nDATA = \"https:\u002F\u002Fdata.rcsb.org\u002Fgraphql\"\ngql = \"\"\"{ entry(entry_id: \"1IEP\") {\n  nonpolymer_entities {\n    rcsb_id\n    nonpolymer_comp { chem_comp { id name formula } }\n  }\n} }\"\"\"\nr = requests.post(DATA, json={\"query\": gql}, timeout=30)\nfor npe in r.json()[\"data\"][\"entry\"][\"nonpolymer_entities\"]:\n    cc = npe[\"nonpolymer_comp\"][\"chem_comp\"]\n    print(f\"  {npe['rcsb_id']}: {cc['id']} ({cc['name']})  {cc['formula']}\")\n```\n\n### Recipe: Inspect available search attributes\n\nThe Search API exposes a JSON schema at `https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema`. Use it to look up valid attribute paths.\n\n```python\nimport requests\nr = requests.get(\"https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema\", timeout=30)\nschema = r.json()\n# Schema lists hundreds of attribute paths; sample a few\nsample_paths = [k for k in schema if \"resolution\" in k.lower()][:5]\nprint(sample_paths)\n```\n\n## Troubleshooting\n\n| Problem | Cause | Solution |\n|---------|-------|----------|\n| `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 |\n| `HTTP 400` with cryptic schema message | Bad operator\u002Fvalue shape | Check the AttributeQuery Operators table; `range` needs the `{from,to,include_lower,include_upper}` dict |\n| Empty `result_set` | Filters too strict | Relax filters one at a time; verify attribute names via the schema endpoint |\n| `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 |\n| `HTTP 429 Too Many Requests` | Burst pace | Add `time.sleep(0.3)` between requests; exponential backoff on 429 |\n| `HTTP 500` from search | Server-side glitch | Retry after 5–10 s; check `status.rcsb.org` |\n| Downloaded `.pdb` file truncated | >99,999 atoms (legacy format limit) | Download `.cif` instead |\n| 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 \u003Chttps:\u002F\u002Fdata.rcsb.org\u002Findex.html#graphql-api> |\n\n## Related Skills\n\n- **alphafold-database-access** — AI-predicted structures; use when no experimental structure exists\n- **uniprot-protein-database** — protein annotations, sequences, ID mapping (UniProt accession needed for AlphaFold)\n- **biopython-molecular-biology** — parse downloaded PDB\u002FmmCIF files, extract coordinates, compute distances\n- **autodock-vina-docking** — downstream molecular docking using PDB structures as receptors\n- **rdkit-cheminformatics** — analyze ligands extracted from PDB complexes\n\n## References\n\n- [RCSB PDB](https:\u002F\u002Fwww.rcsb.org) — main portal and web search\n- [Search API v2 docs](https:\u002F\u002Fsearch.rcsb.org\u002F) — full JSON-payload reference and Swagger\n- [Data API GraphQL browser](https:\u002F\u002Fdata.rcsb.org\u002Findex.html#graphql-api) — schema explorer\n- [Search API attribute schema](https:\u002F\u002Fsearch.rcsb.org\u002Frcsbsearch\u002Fv2\u002Fmetadata\u002Fschema) — JSON of every searchable attribute\n- [RCSB PDB Web APIs overview](https:\u002F\u002Fwww.rcsb.org\u002Fdocs\u002Fprogrammatic-access\u002Fweb-apis-overview) — index of all programmatic surfaces\n- For SDK-based usage, see the `rcsb-api` PyPI package; this SKILL.md uses the underlying REST\u002FGraphQL directly so no SDK install is needed.\n",{"name":5,"license":15,"metadata":24,"description":7},{"quick_use":25},"one entry: POST https:\u002F\u002Fdata.rcsb.org\u002Fgraphql with {\"query\": \"{ entry(entry_id: \\\"1M17\\\") { struct { title } exptl { method } rcsb_entry_info { resolution_combined } nonpolymer_entities { nonpolymer_comp { chem_comp { id name } } } } }\"}. An unknown ID returns HTTP 200 with data.entry = null, not 404. Coordinates: GET https:\u002F\u002Ffiles.rcsb.org\u002Fdownload\u002F{ID}.cif (prefer mmCIF). Cite https:\u002F\u002Fwww.rcsb.org\u002Fstructure\u002F{ID}","master","fd76f882a04e445e899d5cfb2732f2105ea054ee","skills\u002Fawesome-skills\u002Fpdb-database",0,"https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS","https:\u002F\u002Fbg-szy.github.io\u002FTOP-SKILLS\u002F","https:\u002F\u002Fgithub.com\u002Fbg-szy","https:\u002F\u002Fwww.ecust.edu.cn\u002F","a student of East China University of Science and Technology","2026-11-12 05:55:03",{"source":8,"skillName":5,"commands":37,"prompt":47,"zipAvailable":48},[38,41,44],{"tool":39,"command":40},"npx","npx skills add bg-szy\u002FTOP-SKILLS --skill pdb-database",{"tool":42,"command":43},"bunx","bunx skills add bg-szy\u002FTOP-SKILLS --skill pdb-database",{"tool":45,"command":46},"pnpm","pnpm dlx skills add bg-szy\u002FTOP-SKILLS --skill pdb-database","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fawesome-skills\u002Fpdb-database\u002FSKILL.md",true,[],[51,60,68,76,84,91],{"skillNo":52,"name":53,"nameZh":6,"description":54,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":55,"licenseFileUrl":56,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":19},"01M73WM4TPHEJ57D7G2AZ0ABVT","inbox-guardian-for-gmail","Review a personal Gmail inbox with local, owner-approved spam rules. Use for audit-first quarantine, Trash, repeated-evidence sender learning, and header review. Do not use for automatic unsubscribe requests or unreviewed mailbox actions.","MIT","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fmarketplace\u002Finbox-guardian-for-gmail\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fmarketplace\u002Finbox-guardian-for-gmail\u002FSKILL.md",[],[],{"skillNo":61,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":64,"licenseFileUrl":6,"sourceHtmlUrl":65,"categories":66,"keywords":67,"publishedAt":19},"01M73QERWCH2YEGTPC346PKGWW","chatgpt-app-builder","Guide developers through creating and updating ChatGPT apps.\nCovers the full lifecycle: brainstorming ideas against UX guidelines, bootstrapping projects, implementing tools\u002Fviews, debugging, running dev servers, deploying and connecting apps to ChatGPT.\nUse when a user wants to create or update a ChatGPT app \u002F MCP server for ChatGPT, or use the Skybridge framework.","NOASSERTION","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fmarketplace\u002Fchatgpt-app-builder\u002FSKILL.md",[],[],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":55,"licenseFileUrl":72,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":19},"01M73PZFMKXPVJQCV7T0BBV6EV","brewpage-publish","Publish content to brewpage.app — text, markdown, JSON, file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share link, upload to brewpage, host page, brewpage, publish site, upload site, upload directory, deploy site, сделай публичную ссылку, опубликуй.","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fmarketplace\u002Fbrewpage-publish\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fmarketplace\u002Fbrewpage-publish\u002FSKILL.md",[],[],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":83,"publishedAt":19},"01M73KATEQ37JEDHHGKYHSZK40","zinc-database","Query the ZINC22 virtual compound library (CartBlanche API, billions of make-on-demand + purchasable molecules). Look up substances by ZINC ID, resolve a SMILES to its ZINC ID (exact match), inspect purchasability\u002Fcatalogs, and assemble compound sets for docking. Property (MW\u002FlogP) filtering is done locally with RDKit. For bioactivity use chembl-database-bioactivity; for approved drugs use drugbank-database-access.","CC-BY-4.0","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fawesome-skills\u002Fzinc-database\u002FSKILL.md",[],[],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":55,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":90,"publishedAt":19},"01M73KAN6B77H0VZ0J0P4XX5M7","zarr-python","Stores and queries chunked N-D scientific arrays with Zarr-Python 3, including codecs, sharding, S3\u002FGCS storage, and NumPy\u002FDask\u002FXarray integration. Use for array layout, bounded I\u002FO, format migration, or scientific metadata preservation.","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fawesome-skills\u002Fzarr-python\u002FSKILL.md",[],[],{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":55,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":97,"publishedAt":19},"01M73K8904EZK0FDY5Z9A5NQ47","waypoint-bio","Supports work with Outpost Bio's open microbiome foundation models - the Waypoint checkpoints (Waypoint-6m, Waypoint-45m, Waypoint-170m), the Atlas pretraining corpus, the Compass eight-task benchmark, or the `waypoint` CLI from the `waypoint-bio` package. Covers embedding microbiome samples, fine-tuning on taxonomic abundance data, benchmarking a checkpoint on Compass, pretraining a GPT-2 model on taxonomic abundance profiles, and converting MetaPhlAn, Kraken2, QIIME 2, or MGnify abundance tables into waypoint format.","https:\u002F\u002Fgithub.com\u002Fbg-szy\u002FTOP-SKILLS\u002Fblob\u002Fmaster\u002Fskills\u002Fawesome-skills\u002Fwaypoint-bio\u002FSKILL.md",[],[]]