pubchem-compound-search · diff
git:20260428.81ea2d2 to git:20260522.d4a4f76
422 added, 177 removed. Audit A to A.
---
name: "pubchem-compound-search"
- description: "Query PubChem (110M+ compounds) via PubChemPy/PUG-REST. Search by name/CID/SMILES, get properties (MW, LogP, TPSA), similarity/substructure search, bioactivity. For local cheminformatics use rdkit; for multi-DB queries use bioservices."
+ description: "Query PubChem (110M+ compounds) directly via the PUG-REST/JSON API with plain `requests` — no SDK install required. Search by name/CID/SMILES/InChIKey/formula, retrieve properties (MW, XLogP, TPSA, H-bond counts), do similarity/substructure searches with async ListKey polling, fetch synonyms, descriptions, assay summaries, and download SDF/PNG. For local cheminformatics use rdkit; for bioactivity-centric workflows use chembl-database-bioactivity."
license: "CC-BY-4.0"
---
# PubChem Compound Search
## Overview
- PubChem is the world's largest freely available chemical database with 110M+ compounds. This skill covers searching compounds by name, structure, or identifier, retrieving molecular properties, performing similarity/substructure searches, and accessing bioactivity data through PubChemPy (Python wrapper) and PUG-REST API (direct HTTP).
+ PubChem (NCBI) is the largest freely available chemical database — 110M+ compounds, 280M+ substances, and millions of bioassay records. Its **PUG-REST JSON API** is the canonical programmatic surface, and every example here uses it directly via plain `requests`. The Python `pubchempy` wrapper is *not* required; the PUG-REST URL grammar is small enough that direct calls are more transparent, easier to retry/cache, and avoid sandbox dependency issues (the library is not in `TOOL_STATUS.md`).
+ The URL pattern is fixed and predictable:
+
+ ```
+ https://pubchem.ncbi.nlm.nih.gov/rest/pug/<input>/<operation>/<output>
+ ```
+
+ - `<input>` = `compound/{name,cid,smiles,inchikey,formula}/<value>`
+ - `<operation>` = `cids`, `property/<list>`, `synonyms`, `description`, `assaysummary`, `JSON` (full record), `SDF`, `PNG`
+ - `<output>` = `JSON`, `CSV`, `TXT`, `SDF`, `PNG`
+
+ For long-running operations (similarity, substructure, formula) the API returns HTTP 202 + `{"Waiting": {"ListKey": "..."}}`; poll `compound/listkey/{key}/cids/JSON` until it returns `IdentifierList`. The skill handles this pattern in Module 4.
+
## When to Use
- - Looking up a compound by name, CAS number, or SMILES to get its PubChem CID and properties
- - Retrieving molecular properties (molecular weight, LogP, TPSA, H-bond counts) for known compounds
- - Finding structurally similar compounds via Tanimoto similarity search
- - Searching for compounds containing a specific substructure (pharmacophore screening)
- - Converting between chemical identifier formats (name ↔ CID ↔ SMILES ↔ InChI)
- - Accessing bioactivity screening data (assay results, active/inactive status)
- - Batch property comparison across a set of drug candidates
- - For local molecular computation (fingerprints, descriptors, 3D conformers), use `rdkit` instead
- - For querying multiple databases (UniProt, KEGG, ChEMBL) in one workflow, use `bioservices` instead
+ - Looking up a compound by name, SMILES, InChIKey, or formula to get its PubChem CID
+ - Retrieving molecular properties (molecular weight, XLogP, TPSA, H-bond donor/acceptor counts, rotatable bonds, formula, IUPAC name) for one or many CIDs in a single request
+ - Finding structurally similar compounds via Tanimoto similarity (async ListKey poll)
+ - Searching for compounds containing a substructure / pharmacophore motif
+ - Fetching every synonym / trade name / CAS number for a CID
+ - Pulling assay summary tables (active/inactive screening results) for a compound
+ - Converting between identifier formats (name ↔ CID ↔ SMILES ↔ InChI ↔ InChIKey) in one API call
+ - Downloading 2D SDF or PNG structures for figures or downstream RDKit work
+ - For local cheminformatics (fingerprints, descriptors, 3D conformers, scaffold extraction) use `rdkit`
+ - For deeper bioactivity / target-binding data (IC50, Ki against specific targets) use `chembl-database-bioactivity`
## Prerequisites
- - **Python packages**: `pubchempy`, `requests` (for direct API), `pandas` (for batch processing)
- - **No API key required**: PubChem is freely accessible
- - **Rate limits**: Max 5 requests/second, 400 requests/minute
+ - **Python packages**: `requests`, `pandas` — both already in standard environments
+ - **No API key required** — PubChem is fully public
+ - **Rate limits**: max 5 requests/second and 400 requests/minute per IP. Throttle with `time.sleep(0.25)` in loops; return code 503 means you tripped the limit.
+ If you are inside a pixi/conda environment that already provides `requests` and `pandas`, skip the install and invoke scripts with `pixi run python ...`.
+
```bash
- pip install pubchempy requests pandas
+ pip install requests pandas
```
## Quick Start
```python
- import pubchempy as pcp
+ import requests
- # Search by name → get properties
- compound = pcp.get_compounds("aspirin", "name")[0]
- print(f"CID: {compound.cid}")
- print(f"SMILES: {compound.canonical_smiles}")
- print(f"MW: {compound.molecular_weight}, LogP: {compound.xlogp}")
- print(f"HBD: {compound.h_bond_donor_count}, HBA: {compound.h_bond_acceptor_count}")
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+
+ # name → CID
+ cid = requests.get(f"{BASE}/compound/name/aspirin/cids/JSON").json()["IdentifierList"]["CID"][0]
+
+ # CID → properties (single call, many fields)
+ r = requests.get(
+ f"{BASE}/compound/cid/{cid}/property/"
+ "MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,SMILES,IUPACName/JSON")
+ p = r.json()["PropertyTable"]["Properties"][0]
+ print(f"CID {cid} — {p['IUPACName']}")
+ print(f" MW={p['MolecularWeight']} XLogP={p['XLogP']} TPSA={p['TPSA']}")
+ print(f" HBD={p['HBondDonorCount']} HBA={p['HBondAcceptorCount']}")
+ print(f" SMILES={p['SMILES']}")
```
- ## Workflow
+ ## Core API
- ### Step 1: Compound Search
+ ### Module 1: Identifier lookup
- Search by name, CID, SMILES, InChI, or molecular formula.
+ Resolve any external identifier to a PubChem CID via `/compound/{namespace}/{value}/cids/JSON`. Namespaces: `name`, `cid`, `smiles`, `inchikey`, `inchi`, `formula`.
```python
- import pubchempy as pcp
+ import requests
+ from urllib.parse import quote
- # By name
- compounds = pcp.get_compounds("caffeine", "name")
- print(f"Found {len(compounds)} compounds for 'caffeine'")
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
- # By CID (fastest)
- compound = pcp.Compound.from_cid(2244) # Aspirin
- print(f"CID 2244 = {compound.iupac_name}")
+ # By name (returns all matching CIDs as a list)
+ cids = requests.get(f"{BASE}/compound/name/caffeine/cids/JSON").json()["IdentifierList"]["CID"]
+ print(f"caffeine CIDs: {cids}")
- # By SMILES
- compound = pcp.get_compounds("CC(=O)OC1=CC=CC=C1C(=O)O", "smiles")[0]
- print(f"SMILES lookup: CID {compound.cid}")
+ # By canonical SMILES (URL-encode!)
+ smi = quote("CC(=O)OC1=CC=CC=C1C(=O)O", safe="")
+ cid = requests.get(f"{BASE}/compound/smiles/{smi}/cids/JSON").json()["IdentifierList"]["CID"][0]
+ print(f"aspirin SMILES → CID {cid}")
- # By molecular formula (returns all matches)
- formula_matches = pcp.get_compounds("C9H8O4", "formula")
- print(f"Formula C9H8O4 matches: {len(formula_matches)} compounds")
+ # By InChIKey (exact match, fastest if you already have one)
+ ikey = "BSYNRYMUTXBXSQ-UHFFFAOYSA-N"
+ cid = requests.get(f"{BASE}/compound/inchikey/{ikey}/cids/JSON").json()["IdentifierList"]["CID"][0]
+ print(f"InChIKey → CID {cid}")
```
- ### Step 2: Property Retrieval
+ ### Module 2: Property retrieval
- Get molecular properties for one or more compounds.
+ `/compound/cid/{cid_or_csv}/property/<csv-list>/JSON` returns all requested properties in one round trip. **CIDs and property names are both CSV-joinable** — batch up to ~200 CIDs and many properties at once.
```python
- import pubchempy as pcp
+ import requests
- # Full compound object
- compound = pcp.get_compounds("ibuprofen", "name")[0]
- print(f"MW: {compound.molecular_weight}")
- print(f"LogP: {compound.xlogp}")
- print(f"TPSA: {compound.tpsa}")
- print(f"Rotatable bonds: {compound.rotatable_bond_count}")
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
- # Selective property retrieval (more efficient for specific needs)
- props = pcp.get_properties(
- ["MolecularWeight", "XLogP", "TPSA", "HBondDonorCount"],
- "aspirin", "name"
- )
- print(props) # List of dicts
+ # Full property set for a single compound (ibuprofen CID 3672)
+ url = (f"{BASE}/compound/cid/3672/property/"
+ "MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,"
+ "RotatableBondCount,SMILES,InChIKey,IUPACName,MolecularFormula/JSON")
+ p = requests.get(url).json()["PropertyTable"]["Properties"][0]
+ print(f"{p['IUPACName']} formula={p['MolecularFormula']}")
+ print(f" MW={p['MolecularWeight']} XLogP={p['XLogP']} TPSA={p['TPSA']}")
+ print(f" HBD={p['HBondDonorCount']} HBA={p['HBondAcceptorCount']} RotB={p['RotatableBondCount']}")
```
- ### Step 3: Similarity Search
+ ```python
+ import requests, pandas as pd
- Find structurally similar compounds using Tanimoto coefficient.
+ # Batch: 4 CIDs, 3 properties — one request, one round trip
+ cids = "2244,3672,2157,2662" # aspirin, ibuprofen, naproxen, celecoxib
+ r = requests.get(
+ f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cids}/property/"
+ "MolecularWeight,XLogP,TPSA/JSON")
+ df = pd.DataFrame(r.json()["PropertyTable"]["Properties"])
+ print(df.to_string(index=False))
+ ```
+ ### Module 3: Synonyms and description
+
+ Synonyms (trade names, CAS numbers, alternative spellings) and curated descriptions live at `/compound/{ns}/{value}/{synonyms|description}/JSON`.
+
```python
- import pubchempy as pcp
+ import requests
- # Get reference compound SMILES
- ref = pcp.get_compounds("gefitinib", "name")[0]
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
- # Similarity search (may take 15-30s for async processing)
- similar = pcp.get_compounds(
- ref.canonical_smiles, "smiles",
- searchtype="similarity",
- Threshold=85, # Tanimoto threshold (0-100)
- MaxRecords=50
- )
- print(f"Found {len(similar)} compounds with ≥85% similarity to gefitinib")
- for comp in similar[:5]:
- print(f" CID {comp.cid}: MW={comp.molecular_weight}")
+ # Full synonym list (aspirin has ~700)
+ info = requests.get(f"{BASE}/compound/cid/2244/synonyms/JSON").json()["InformationList"]["Information"][0]
+ print(f"aspirin synonyms: {len(info['Synonym'])}")
+ for s in info["Synonym"][:8]:
+ print(f" {s}")
```
- ### Step 4: Substructure Search
+ ```python
+ import requests
- Find compounds containing a specific structural motif.
+ # Curated descriptions (NCBI MeSH, CAMEO, etc.)
+ r = requests.get("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/aspirin/description/JSON")
+ for item in r.json()["InformationList"]["Information"]:
+ if "Description" in item:
+ print(f"[{item.get('DescriptionSourceName','?')}]")
+ print(f" {item['Description'][:200]}…")
+ print()
+ ```
+ ### Module 4: Similarity & substructure search (async ListKey pattern)
+
+ Structure searches return HTTP 202 + `{"Waiting": {"ListKey": "..."}}`. Poll `/compound/listkey/{key}/cids/JSON` every ~2s until it returns `IdentifierList`. Wrap this in a helper since it's used everywhere.
+
```python
- import pubchempy as pcp
+ import requests, time
+ from urllib.parse import quote
- # Search for sulfonamide-containing compounds
- hits = pcp.get_compounds(
- "S(=O)(=O)N", "smiles",
- searchtype="substructure",
- MaxRecords=100
- )
- print(f"Found {len(hits)} compounds with sulfonamide group")
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+
+ def poll_listkey(listkey, max_polls=10, interval=2.0):
+ """Block until PubChem finishes async search; return CID list."""
+ for _ in range(max_polls):
+ time.sleep(interval)
+ j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON", timeout=20).json()
+ if "IdentifierList" in j:
+ return j["IdentifierList"]["CID"]
+ raise TimeoutError(f"ListKey {listkey} did not complete")
+
+ # Tanimoto similarity (90% threshold, max 20 hits) — starting from aspirin SMILES
+ smi = quote("CC(=O)OC1=CC=CC=C1C(=O)O", safe="")
+ init = requests.get(
+ f"{BASE}/compound/similarity/smiles/{smi}/JSON?Threshold=90&MaxRecords=20").json()
+ cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
+ else init["IdentifierList"]["CID"]
+ print(f"aspirin @90% similarity: {len(cids)} hits, sample={cids[:5]}")
```
- ### Step 5: Bioactivity Data Access
+ ```python
+ import requests
+ from urllib.parse import quote
- Retrieve biological screening results via PUG-REST API.
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+ # Substructure search — all compounds containing a sulfonamide group
+ smi = quote("S(=O)(=O)N", safe="")
+ init = requests.get(
+ f"{BASE}/compound/substructure/smiles/{smi}/JSON?MaxRecords=20").json()
+ cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
+ else init["IdentifierList"]["CID"]
+ print(f"sulfonamide-containing CIDs: {len(cids)}, sample={cids[:5]}")
+ ```
+
+ ### Module 5: Assay summary (bioactivity data)
+
+ `/compound/cid/{cid}/assaysummary/JSON` returns a `Table` of every PubChem BioAssay the compound appears in (assay AID, target, outcome, micromolar activity if available).
+
```python
+ import requests, pandas as pd
+
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+
+ r = requests.get(f"{BASE}/compound/cid/2244/assaysummary/JSON", timeout=30)
+ rows = r.json().get("Table", {}).get("Row", [])
+ cols = r.json().get("Table", {}).get("Columns", {}).get("Column", [])
+ print(f"aspirin appears in {len(rows)} bioassays")
+
+ # First few columns + rows as a DataFrame
+ df = pd.DataFrame([row["Cell"] for row in rows[:5]], columns=cols)
+ print(df.iloc[:, :6].to_string(index=False))
+ ```
+
+ ### Module 6: Structure file download (SDF / PNG)
+
+ `/compound/cid/{cid}/SDF` returns 2D MOL/SDF; `/compound/cid/{cid}/PNG` returns a structure image (use `?image_size=large` for higher resolution).
+
+ ```python
import requests
- cid = 2244 # Aspirin
- url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/assaysummary/JSON"
- response = requests.get(url)
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+ cid = 2519 # caffeine
- if response.status_code == 200:
- data = response.json()
- rows = data.get("Table", {}).get("Row", [])
- print(f"Aspirin has {len(rows)} bioassay records")
+ # 2D SDF for downstream RDKit / OpenBabel
+ sdf = requests.get(f"{BASE}/compound/cid/{cid}/SDF", timeout=15).text
+ with open("caffeine.sdf", "w") as f:
+ f.write(sdf)
+ print(f"caffeine.sdf: {len(sdf)} chars, ends with M END={'M END' in sdf}")
+
+ # PNG structure image
+ png = requests.get(f"{BASE}/compound/cid/{cid}/PNG?image_size=large", timeout=15).content
+ with open("caffeine.png", "wb") as f:
+ f.write(png)
+ print(f"caffeine.png: {len(png)} bytes")
```
- ### Step 6: Batch Property Comparison
+ ## Key Concepts
- Compare properties across multiple compounds.
+ ### Async ListKey pattern
+ Similarity, substructure, and formula searches are **asynchronous** — the API kicks off a background job and returns HTTP 202 with `{"Waiting": {"ListKey": "<id>"}}`. Poll `/compound/listkey/{id}/cids/JSON` every ~2 seconds until the response contains `IdentifierList`. Most searches finish in 5–15s; tighten polling for tiny searches, loosen for very large ones. Use the `poll_listkey` helper from Module 4 everywhere.
+
+ A small fraction of fast searches return `IdentifierList` directly on the first call (no `Waiting` field); check for both possibilities.
+
+ ### Property name reference
+
+ | API name | Meaning |
+ | --------------------- | ----------------------------------------- |
+ | `MolecularWeight` | Molecular weight (g/mol, string) |
+ | `MolecularFormula` | Hill-system formula |
+ | `SMILES` | Canonical SMILES (the 2025+ name; the old `CanonicalSMILES` may still work) |
+ | `IUPACName` | Curated IUPAC name |
+ | `InChI` / `InChIKey` | IUPAC InChI / InChIKey |
+ | `XLogP` | Computed logP (octanol/water) |
+ | `TPSA` | Topological polar surface area (Ų) |
+ | `HBondDonorCount` | Number of H-bond donors |
+ | `HBondAcceptorCount` | Number of H-bond acceptors |
+ | `RotatableBondCount` | Number of rotatable bonds |
+ | `HeavyAtomCount` | Non-hydrogen atom count |
+ | `Charge` | Formal charge |
+
+ CSV-join any subset in a single `/property/<csv>/JSON` URL. Note that `MolecularWeight` returns as a string; cast to `float` before arithmetic.
+
+ ### Response envelope shapes
+
+ - `cids/JSON` → `{"IdentifierList": {"CID": [int, ...]}}`
+ - `property/.../JSON` → `{"PropertyTable": {"Properties": [{...}, ...]}}` (one dict per CID, in input order)
+ - `synonyms/JSON` → `{"InformationList": {"Information": [{"CID": int, "Synonym": [str, ...]}]}}`
+ - `description/JSON` → `{"InformationList": {"Information": [{"CID": int, "Description": str, "DescriptionSourceName": str, ...}, ...]}}`
+ - `assaysummary/JSON` → `{"Table": {"Columns": {"Column": [...]}, "Row": [{"Cell": [...]}, ...]}}`
+ - async-init (similarity, substructure, formula) → 202 with `{"Waiting": {"ListKey": "..."}}`
+ - listkey poll → `{"IdentifierList": {"CID": [...]}}` when ready
+
+ ## Common Workflows
+
+ ### Workflow 1: Property comparison across a drug panel
+
+ **Goal**: side-by-side physicochemical comparison of a small molecule set.
+
```python
- import pubchempy as pcp
- import pandas as pd
- import time
+ import requests, pandas as pd, time
+ from urllib.parse import quote
- compounds = ["aspirin", "ibuprofen", "naproxen", "celecoxib"]
- results = []
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
- for name in compounds:
- comp = pcp.get_compounds(name, "name")[0]
- results.append({
- "Name": name, "CID": comp.cid,
- "MW": comp.molecular_weight, "LogP": comp.xlogp,
- "TPSA": comp.tpsa, "HBD": comp.h_bond_donor_count,
- "HBA": comp.h_bond_acceptor_count,
- })
- time.sleep(0.25) # Respect rate limits
+ drugs = ["aspirin", "ibuprofen", "naproxen", "celecoxib"]
- df = pd.DataFrame(results)
+ # Resolve names → CIDs in a loop (rate-limited)
+ cids = []
+ for d in drugs:
+ cid = requests.get(f"{BASE}/compound/name/{quote(d)}/cids/JSON",
+ timeout=15).json()["IdentifierList"]["CID"][0]
+ cids.append(cid)
+ time.sleep(0.25)
+
+ # Single batched property pull
+ cid_csv = ",".join(str(c) for c in cids)
+ r = requests.get(
+ f"{BASE}/compound/cid/{cid_csv}/property/"
+ "MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount/JSON")
+ df = pd.DataFrame(r.json()["PropertyTable"]["Properties"])
+ df["Name"] = drugs
+ df = df[["Name", "CID", "MolecularWeight", "XLogP", "TPSA",
+ "HBondDonorCount", "HBondAcceptorCount"]]
print(df.to_string(index=False))
```
- ### Step 7: Identifier Format Conversion
+ ### Workflow 2: Lead compound → similar analogs → properties
- Convert between chemical identifier formats.
+ **Goal**: starting from a kinase inhibitor (gefitinib), find 85%-similar analogs and pull their properties.
```python
- import pubchempy as pcp
+ import requests, time
+ from urllib.parse import quote
- compound = pcp.get_compounds("caffeine", "name")[0]
- print(f"CID: {compound.cid}")
- print(f"IUPAC: {compound.iupac_name}")
- print(f"SMILES: {compound.canonical_smiles}")
- print(f"InChI: {compound.inchi}")
- print(f"InChIKey: {compound.inchikey}")
- print(f"Formula: {compound.molecular_formula}")
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
- # Download structure files
- pcp.download("SDF", "caffeine", "name", "caffeine.sdf", overwrite=True)
- print("Downloaded caffeine.sdf")
+ def poll_listkey(listkey, max_polls=10, interval=2.0):
+ for _ in range(max_polls):
+ time.sleep(interval)
+ j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON",
+ timeout=20).json()
+ if "IdentifierList" in j:
+ return j["IdentifierList"]["CID"]
+ raise TimeoutError("listkey timeout")
+
+ # 1. lead → CID → canonical SMILES
+ ref_cid = requests.get(
+ f"{BASE}/compound/name/gefitinib/cids/JSON").json()["IdentifierList"]["CID"][0]
+ ref_smi = requests.get(
+ f"{BASE}/compound/cid/{ref_cid}/property/SMILES/JSON"
+ ).json()["PropertyTable"]["Properties"][0]["SMILES"]
+ print(f"gefitinib CID={ref_cid} SMILES={ref_smi}")
+
+ # 2. similarity search
+ smi_q = quote(ref_smi, safe="")
+ init = requests.get(
+ f"{BASE}/compound/similarity/smiles/{smi_q}/JSON?Threshold=85&MaxRecords=15"
+ ).json()
+ sim_cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
+ else init["IdentifierList"]["CID"]
+ print(f" {len(sim_cids)} analogs @85% Tanimoto")
+
+ # 3. batch-pull properties for top 5 analogs
+ cid_csv = ",".join(str(c) for c in sim_cids[:5])
+ r = requests.get(
+ f"{BASE}/compound/cid/{cid_csv}/property/"
+ "MolecularWeight,XLogP,TPSA,RotatableBondCount/JSON")
+ for row in r.json()["PropertyTable"]["Properties"]:
+ print(f" CID {row['CID']}: MW={row['MolecularWeight']} XLogP={row['XLogP']}")
```
+ ### Workflow 3: Pharmacophore screen via substructure → bioactivity check
+
+ **Goal**: find compounds with a sulfonamide motif and check which have bioactivity records.
+
+ ```python
+ import requests, time
+ from urllib.parse import quote
+
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+
+ def poll_listkey(listkey, max_polls=10, interval=2.0):
+ for _ in range(max_polls):
+ time.sleep(interval)
+ j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON",
+ timeout=20).json()
+ if "IdentifierList" in j:
+ return j["IdentifierList"]["CID"]
+ raise TimeoutError("listkey timeout")
+
+ smi = quote("S(=O)(=O)N", safe="")
+ init = requests.get(
+ f"{BASE}/compound/substructure/smiles/{smi}/JSON?MaxRecords=10").json()
+ cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
+ else init["IdentifierList"]["CID"]
+ print(f"sulfonamide CIDs: {cids}")
+
+ # Bioactivity row counts for the first few hits
+ for cid in cids[:3]:
+ rows = requests.get(f"{BASE}/compound/cid/{cid}/assaysummary/JSON",
+ timeout=30).json().get("Table", {}).get("Row", [])
+ print(f" CID {cid}: {len(rows)} assay rows")
+ time.sleep(0.3)
+ ```
+
## Key Parameters
- | Parameter | Function | Default | Range / Options | Effect |
- |-----------|----------|---------|-----------------|--------|
- | `namespace` | `get_compounds` | required | `"name"`, `"cid"`, `"smiles"`, `"inchi"`, `"formula"` | Identifier type for search |
- | `searchtype` | `get_compounds` | `None` | `"similarity"`, `"substructure"` | Type of structure search |
- | `Threshold` | similarity search | `90` | `0`-`100` | Tanimoto similarity cutoff (%) |
- | `MaxRecords` | structure search | `None` | `1`-`10000` | Maximum results returned |
- | `properties` | `get_properties` | required | See API reference | Which molecular properties to retrieve |
- | `record_type` | `download` | `"2d"` | `"2d"`, `"3d"` | Structure dimensionality |
+ | Parameter | Endpoint / Module | Default | Range / Options | Effect |
+ | ----------------- | -------------------------------- | ------- | ------------------------------------------ | ----------------------------------------------------------------------- |
+ | `<namespace>` | `/compound/{ns}/<value>/...` | — | `name`, `cid`, `smiles`, `inchikey`, `inchi`, `formula` | Input identifier type |
+ | `<property csv>` | `/.../property/<csv>/JSON` | — | any subset of property names (see table) | Which properties to return (one DB call per request) |
+ | `Threshold` | similarity (M4) | `90` | `0`–`100` | Tanimoto cutoff (percent) |
+ | `MaxRecords` | similarity / substructure / formula | (server-side default) | `1`–`10000` | Cap on async result list |
+ | `image_size` | `/compound/cid/{cid}/PNG` | medium | `small`, `large`, `WxH` (e.g. `500x500`) | PNG output resolution |
+ | `record_type` | `/compound/cid/{cid}/SDF` | `2d` | `2d`, `3d` | SDF dimensionality (`?record_type=3d`) |
+ | `MaxAssayResults` | `/compound/cid/{cid}/assaysummary/JSON` | — | int | Limit assay rows when compound has thousands of records |
- ## Common Recipes
+ ## Best Practices
- ### Recipe: Drug-Likeness Screening (Lipinski's Rule of Five)
+ 1. **Always go through `cids/JSON` first** when starting from a name or external identifier. The name→CID resolution and the CID→property lookup are separate calls; doing both at once via `name → property` works but throws away the canonical CID list that downstream queries need.
- When to use: Quick check if a compound is orally bioavailable.
+ 2. **Batch properties, never loop them.** `compound/cid/2244,3672,2157,.../property/MolecularWeight,XLogP,.../JSON` accepts up to ~200 CIDs and any number of properties — one round trip instead of N. Looping `get_compounds` per name is the most common rate-limit trap.
+ 3. **URL-encode every SMILES.** Use `urllib.parse.quote(smiles, safe="")`. Bare SMILES with `=`, `#`, `(`, `)`, `[`, `]` will sometimes work but breaks unpredictably on `+`, `/`, `\`, or query-string-looking substrings.
+
+ 4. **Treat similarity/substructure/formula as async.** Branch on `"Waiting" in response` and poll `listkey` rather than re-issuing the search. Re-issuing creates a new ListKey and wastes the server's job slot.
+
+ 5. **Throttle ≤ 5 req/sec, ≤ 400/min.** Insert `time.sleep(0.25)` in any tight loop. HTTP 503 means you tripped the limit — wait 10s and reduce concurrency.
+
+ 6. **Cast `MolecularWeight` to `float`.** It's returned as a string (`"180.16"`) for full decimal fidelity. Comparing strings against numeric thresholds is a silent bug.
+
+ 7. **For 100+ CIDs use POST.** GET URLs over ~2000 chars get truncated by some HTTP proxies. PubChem also accepts `POST` with `cid` in the form body: `requests.post(f"{BASE}/compound/cid/property/MolecularWeight/JSON", data={"cid": cid_csv})`.
+
+ 8. **`SMILES` vs `CanonicalSMILES`.** PubChem renamed the canonical SMILES property to plain `SMILES` in the 2025 PUG-REST schema. Use `SMILES` in new code; the response field is also keyed `SMILES`.
+
+ ## Common Recipes
+
+ ### Recipe 1 — Lipinski's Rule of Five check
+
```python
- import pubchempy as pcp
+ import requests
+ from urllib.parse import quote
+ BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
+
def check_lipinski(name):
- comp = pcp.get_compounds(name, "name")[0]
- rules = {
- "MW ≤ 500": comp.molecular_weight <= 500,
- "LogP ≤ 5": (comp.xlogp or 0) <= 5,
- "HBD ≤ 5": comp.h_bond_donor_count <= 5,
- "HBA ≤ 10": comp.h_bond_acceptor_count <= 10,
- }
- violations = sum(1 for v in rules.values() if not v)
- return rules, violations
+ cid = requests.get(f"{BASE}/compound/name/{quote(name)}/cids/JSON"
+ ).json()["IdentifierList"]["CID"][0]
+ p = requests.get(
+ f"{BASE}/compound/cid/{cid}/property/"
+ "MolecularWeight,XLogP,HBondDonorCount,HBondAcceptorCount/JSON"
+ ).json()["PropertyTable"]["Properties"][0]
+ mw, xlogp = float(p["MolecularWeight"]), p.get("XLogP", 0) or 0
+ hbd, hba = p["HBondDonorCount"], p["HBondAcceptorCount"]
+ rules = {"MW ≤ 500": mw <= 500, "XLogP ≤ 5": xlogp <= 5,
+ "HBD ≤ 5": hbd <= 5, "HBA ≤ 10": hba <= 10}
+ v = sum(1 for ok in rules.values() if not ok)
+ return rules, v
rules, v = check_lipinski("metformin")
- print(f"Violations: {v}/4 — {'PASS' if v <= 1 else 'FAIL'}")
- for rule, passed in rules.items():
- print(f" {'✓' if passed else '✗'} {rule}")
+ print(f"violations: {v}/4 ({'PASS' if v <= 1 else 'FAIL'})")
+ for r, ok in rules.items(): print(f" {'✓' if ok else '✗'} {r}")
```
- ### Recipe: Get All Synonyms for a Compound
-
- When to use: Finding alternative names, trade names, or CAS numbers.
+ ### Recipe 2 — Pull every synonym / trade name
```python
- import pubchempy as pcp
+ import requests
- synonyms = pcp.get_synonyms("aspirin", "name")
- if synonyms:
- names = synonyms[0]["Synonym"]
- print(f"Found {len(names)} synonyms for aspirin:")
- for name in names[:10]:
- print(f" {name}")
+ r = requests.get("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/aspirin/synonyms/JSON")
+ syns = r.json()["InformationList"]["Information"][0]["Synonym"]
+ print(f"{len(syns)} synonyms")
+ for s in syns[:10]:
+ print(f" {s}")
```
- ### Recipe: Download 2D Structure Image
+ ### Recipe 3 — Download a structure PNG
- When to use: Generating structure images for reports or presentations.
+ ```python
+ import requests
+ cid = 2519 # caffeine
+ png = requests.get(
+ f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/PNG?image_size=large"
+ ).content
+ with open("caffeine.png", "wb") as f:
+ f.write(png)
+ print(f"wrote caffeine.png ({len(png)} bytes)")
+ ```
+
+ ### Recipe 4 — Robust session with retry / 429 handling
+
```python
import requests
+ from requests.adapters import HTTPAdapter
+ from urllib3.util.retry import Retry
- cid = 2519 # Caffeine
- url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/PNG?image_size=large"
- response = requests.get(url)
- with open("caffeine_structure.png", "wb") as f:
- f.write(response.content)
- print("Saved caffeine_structure.png")
+ s = requests.Session()
+ s.headers.update({"Accept": "application/json"})
+ s.mount("https://", HTTPAdapter(max_retries=Retry(
+ total=4, backoff_factor=1.0,
+ status_forcelist=[429, 500, 502, 503, 504],
+ allowed_methods=["GET", "POST"])))
+
+ r = s.get(
+ "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/2244/property/MolecularWeight/JSON",
+ timeout=15)
+ r.raise_for_status()
+ print(r.json()["PropertyTable"]["Properties"][0]["MolecularWeight"])
```
## Expected Outputs
- - **Compound search**: `pubchempy.Compound` objects with properties (CID, name, SMILES, MW, etc.)
- - **Property retrieval**: List of dictionaries with requested properties
- - **Similarity search**: List of `Compound` objects sorted by similarity
- - **Bioactivity query**: JSON with assay results (activity outcome, assay ID, target)
- - **Structure download**: SDF, JSON, or PNG files
+ - **CID lookup** (`/.../cids/JSON`): `{"IdentifierList": {"CID": [...]}}` — list of integer CIDs, ordered by relevance for name searches.
+ - **Properties** (`/.../property/.../JSON`): `{"PropertyTable": {"Properties": [{"CID": ..., "MolecularWeight": "180.16", ...}, ...]}}`. One row per CID in input order; `MolecularWeight` is a *string*.
+ - **Synonyms**: `{"InformationList": {"Information": [{"CID": 2244, "Synonym": ["aspirin", "ACETYLSALICYLIC ACID", "50-78-2", ...]}]}}`.
+ - **Async search init**: HTTP 202 with `{"Waiting": {"ListKey": "12345..."}}`. Poll `/compound/listkey/{key}/cids/JSON` until it returns `IdentifierList`.
+ - **Assay summary**: `{"Table": {"Columns": {"Column": [...col names...]}, "Row": [{"Cell": [...]}, ...]}}`.
+ - **SDF**: plain text starting with the CID header line; ends with `M END` followed by SDF property blocks.
+ - **PNG**: binary `image/png`, ~2–4 KB at default size, ~10–20 KB at `image_size=large`.
## Troubleshooting
- | Problem | Cause | Solution |
- |---------|-------|----------|
- | `IndexError: list index out of range` | No compounds found for query | Check spelling; try alternative names or CID |
- | Request timeout (>30s) | Large similarity/substructure search | Reduce `MaxRecords`; PubChemPy handles async polling automatically |
- | Empty property values (`None`) | Property not available for this compound | Check if property exists before use: `if comp.xlogp is not None` |
- | `HTTP 503 Service Unavailable` | Rate limit exceeded | Add `time.sleep(0.25)` between requests; max 5 req/sec |
- | `BadRequestError` | Invalid SMILES or identifier | Validate SMILES syntax; use canonical SMILES from RDKit |
- | Formula search returns too many hits | Common formula shared by many isomers | Use SMILES or InChI for more specific searches |
- | Bioactivity API returns empty | Compound has no bioassay data | Not all compounds have been tested; check PubChem web interface |
+ | Problem | Cause | Solution |
+ | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+ | HTTP 404 `PUGREST.NotFound` | Name / SMILES / formula matched no record | Try a CAS number or InChIKey; check spelling in the PubChem web UI; canonical SMILES from RDKit often resolves where input SMILES doesn't |
+ | HTTP 202 stuck in `{"Waiting":...}` for a similarity/substructure call | Async job still running | Poll `/compound/listkey/{key}/cids/JSON` every 2s up to ~30s; reduce `MaxRecords` if it never completes |
+ | HTTP 503 `PUGREST.ServerBusy` | Tripped the 5-req/s or 400-req/min rate limit | Insert `time.sleep(0.25)` in loops; use the Retry session in Recipe 4; reduce concurrency |
+ | HTTP 400 on a SMILES URL | SMILES wasn't URL-encoded | Wrap in `urllib.parse.quote(smi, safe="")` — `#`, `+`, `/` and `\` all break path parsing |
+ | `KeyError: 'SMILES'` | Asked for `CanonicalSMILES` (old name); 2025 PUG-REST returns just `SMILES` | Use `SMILES` in the property CSV and read `p["SMILES"]` |
+ | `TypeError: '>' not supported between instances of 'str' and 'int'` | `MolecularWeight` is a string | `float(p["MolecularWeight"])` before any arithmetic comparison |
+ | Batch `cid/2244,3672,...` returns only some rows | URL exceeded server limit | Switch to `requests.post(url, data={"cid": "2244,3672,..."})`; same URL minus the value, body carries the CSV |
+ | Empty `assaysummary` Table | CID has no bioassay records | Not all compounds are assayed; verify on the PubChem web page |
+ | `XLogP` is `None` for a valid CID | Property not computed for that compound | Guard with `p.get("XLogP", 0) or 0` before arithmetic |
+ ## Related Skills
+
+ - `chembl-database-bioactivity` — IC50 / Ki / Kd target-binding data, deeper than PubChem's assay summaries
+ - `rdkit-cheminformatics` — local SMILES/MOL manipulation, fingerprints, descriptors, scaffold extraction
+ - `pdb-database` — protein structures co-crystallized with the small molecules found via PubChem CIDs
+
## References
- - [PubChem PUG-REST API](https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest) — official REST API docs
- - [PubChemPy documentation](https://pubchempy.readthedocs.io/) — Python wrapper docs
- - [PubChem PUG-REST tutorial](https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest-tutorial) — step-by-step guide
- - [PubChem database](https://pubchem.ncbi.nlm.nih.gov/) — web interface
+ - [PubChem PUG-REST documentation](https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest) — Full URL grammar reference
+ - [PUG-REST tutorial with worked examples](https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest-tutorial) — Step-by-step API walk-through
+ - [PubChem property table](https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest#section=Compound-Property-Tables) — Authoritative list of `/property/<name>` values and their semantics
+ - [PubChem main site](https://pubchem.ncbi.nlm.nih.gov/) — Web interface for spot-checks
+ - [Kim et al. (2023) Nucleic Acids Res — PubChem 2023 update](https://doi.org/10.1093/nar/gkac956)