references/citation.bib
@ARTICLE{Kim2025,
ABSTRACT = {PubChem (https://pubchem.ncbi.nlm.nih.gov) is a large and highly-integrated public chemical database resource at NIH. In the past two years, significant updates were made to PubChem. With additions from over 130 new sources, PubChem contains \>1000 data sources, 119 million compounds, 322 million substances and 295 million bioactivities. New interfaces, such as the consolidated literature panel and the patent knowledge panel, were developed. The consolidated literature panel combines all references about a compound into a single list, allowing users to easily find, sort, and export all relevant articles for a chemical in one place. The patent knowledge panels for a given query chemical or gene display chemicals, genes, and diseases co-mentioned with the query in patent documents, helping users to explore relationships between co-occurring entities within patent documents. PubChemRDF was expanded to include the co-occurrence data underlying the literature knowledge panel, enabling users to exploit semantic web technologies to explore entity relationships based on the co-occurrences in the scientific literature. The usability and accessibility of information on chemicals with non-discrete structures (e.g. biologics, minerals, polymers, UVCBs and glycans) were greatly improved with dedicated web pages that provide a comprehensive view of all available information in PubChem for these chemicals.},
AUTHOR = {Kim, Sunghwan and Chen, Jie and Cheng, Tiejun and Gindulyte, Asta and He, Jia and He, Siqian and Li, Qingliang and Shoemaker, Benjamin A and Thiessen, Paul A and Yu, Bo and Zaslavsky, Leonid and Zhang, Jian and Bolton, Evan E},
URL = {https://doi.org/10.1093/nar/gkae1059},
DATE = {2025-01},
DOI = {10.1093/nar/gkae1059},
EPRINT = {https://academic.oup.com/nar/article-pdf/53/D1/D1516/60743708/gkae1059.pdf},
ISSN = {1362-4962},
JOURNALTITLE = {Nucleic Acids Research},
NUMBER = {D1},
PAGES = {D1516--D1525},
TITLE = {PubChem 2025 update},
VOLUME = {53},
}
references/endpoints.md
# Advanced PubChem API Reference
This file documents the raw PUG-REST and PUG-View APIs for cases where the
`pubchem_api.py` wrapper does not support your specific query.
## PUG-REST (Computed Properties & Search)
**Base URL:** `https://pubchem.ncbi.nlm.nih.gov/rest/pug`
The URL path always follows this structure:
`/<domain>/<namespace>/<identifiers>/<operation>/<output>[?options]`
### 1. Domain
The core data type: `compound`, `substance`, `assay`, `gene`, `protein`,
`pathway`, `taxonomy`, `cell`.
### 2. Namespace & Identifiers
How you are identifying the target record(s):
- `cid/<cid>`: Compound ID
- `name/<name>`: Exact chemical name
- `smiles/<smiles>`: Exact SMILES match
- `inchikey/<inchikey>`: Exact InChIKey match
- `formula/<formula>`: Exact molecular formula
- Search namespaces (use `fast` prefix for synchronous):
- `fastsubstructure/smiles/<smiles>`
- `fastsimilarity_2d/smiles/<smiles>`
- `fastidentity/smiles/<smiles>`
### 3. Operation
What data you want to extract:
- `record` (default): The full raw record.
- `property/<property_list>`: Specific properties (e.g.,
`MolecularWeight,XLogP,TPSA`).
- `synonyms`: List of synonyms.
- `cids`: Return only the CIDs (useful after a search).
- `assaysummary`: Summary of bioassays.
- `xrefs/<xref_type>`: Cross-references (e.g., `PatentID`, `PubMedID`).
### 4. Output
Format for the response: `JSON`, `XML`, `CSV`, `TXT`, `PNG`.
### Examples
* **Properties by CID (JSON)**: `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/2244/property/MolecularWeight,MolecularFormula/JSON`
* **Mass Range Search (JSON)**: `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/molecular_weight/range/400.0/400.05/cids/JSON`
* **Patents by SID (JSON)**: `https://pubchem.ncbi.nlm.nih.gov/rest/pug/substance/sid/137349406/xrefs/PatentID/JSON`
---
## PUG-View (Third-Party Annotations & Text)
Used for retrieving comprehensive textual annotations (like GHS Safety,
Pharmacology, Toxicity) compiled from external sources.
**Base URL:** `https://pubchem.ncbi.nlm.nih.gov/rest/pug_view`
The standard structure for retrieving specific sections:
`https://pubchem.ncbi.nlm.nih.gov/rest/pug_view/data/compound/<cid>/JSON?heading=<Section+Heading>`
*Note: Spaces in headings must be replaced with `+`.*
### Common Headings
* `Safety+and+Hazards`
* `Pharmacology+and+Biochemistry`
* `Toxicity`
* `Drug+and+Medication+Information`
* `Experimental+Properties`
scripts/pubchem_api.py
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""PubChem API CLI.
This script provides command-line access to various PubChem API endpoints,
including resolving chemical names, fetching properties, synonyms, safety data,
pharmacology, images, and performing similarity/substructure searches.
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "polite-http",
# ]
# ///
import argparse
import json
import sys
import urllib.parse
from polite_http import http_client
PUBCHEM_BASE_URL = "https://pubchem.ncbi.nlm.nih.gov/rest"
_CLIENT = http_client.HttpClient(PUBCHEM_BASE_URL, qps=5)
def make_request(url):
"""Makes an HTTP GET request via http_client."""
try:
resp = _CLIENT.fetch(url)
content_type = resp.headers.get("Content-Type", "")
if "application/json" in content_type:
return resp.json()
elif "text/plain" in content_type or "text/csv" in content_type:
return resp.text
else:
return resp.data
except http_client.HttpError as e:
if e.status_code == 404:
return {"error": "Record not found (HTTP 404)."}
elif e.status_code == 400:
return {"error": "Bad request (HTTP 400). Please check your inputs."}
else:
return {"error": f"HTTP Error {e.status_code or 'Error'}: {e.body}"}
def write_output(data, output_file):
"""Writes output to a JSON file."""
try:
with open(output_file, "w", encoding="utf-8") as f:
if isinstance(data, str):
json.dump({"result": data}, f, indent=2)
else:
json.dump(data, f, indent=2)
print(f"Success! Data written to: {output_file}")
except (OSError, TypeError) as e:
print(f"Error writing to file {output_file}: {e}")
sys.exit(1)
def resolve(name=None, inchi=None):
"""Resolves a chemical name or InChI to CIDs and SMILES."""
if name:
encoded_val = urllib.parse.quote(name)
input_type = "name"
elif inchi:
encoded_val = urllib.parse.quote(inchi)
input_type = "inchi"
else:
return {"error": "Either name or inchi must be provided."}
url_cids = (
f"{PUBCHEM_BASE_URL}/pug/compound/{input_type}/{encoded_val}/cids/JSON"
)
url_props = f"{PUBCHEM_BASE_URL}/pug/compound/{input_type}/{encoded_val}/property/CanonicalSMILES,IsomericSMILES,InChIKey/JSON"
cids_data = make_request(url_cids)
if isinstance(cids_data, dict) and "error" in cids_data:
return cids_data
props_data = make_request(url_props)
return {"identifiers": cids_data, "properties": props_data}
def properties(cid):
url = f"{PUBCHEM_BASE_URL}/pug/compound/cid/{cid}/property/MolecularFormula,MolecularWeight,XLogP,TPSA,ExactMass,HBondDonorCount,HBondAcceptorCount,RotatableBondCount/JSON"
return make_request(url)
def synonyms(cid):
url = f"{PUBCHEM_BASE_URL}/pug/compound/cid/{cid}/synonyms/JSON"
return make_request(url)
def safety(cid):
url = f"{PUBCHEM_BASE_URL}/pug_view/data/compound/{cid}/JSON?heading=Safety+and+Hazards"
return make_request(url)
def pharmacology(cid):
url = f"{PUBCHEM_BASE_URL}/pug_view/data/compound/{cid}/JSON?heading=Pharmacology+and+Biochemistry"
return make_request(url)
def view(cid, heading):
encoded_heading = urllib.parse.quote(heading)
url = f"{PUBCHEM_BASE_URL}/pug_view/data/compound/{cid}/JSON?heading={encoded_heading}"
return make_request(url)
def xrefs(cid, xref_type):
url = f"{PUBCHEM_BASE_URL}/pug/compound/cid/{cid}/xrefs/{xref_type}/JSON"
return make_request(url)
def query(path):
clean_path = path.lstrip("/")
url = f"{PUBCHEM_BASE_URL}/{clean_path}"
return make_request(url)
def image(cid):
url = f"{PUBCHEM_BASE_URL}/pug/compound/cid/{cid}/PNG"
return {"image_url": url, "markdown": f""}
def similarity(smiles):
encoded_smiles = urllib.parse.quote(smiles)
url = f"{PUBCHEM_BASE_URL}/pug/compound/fastsimilarity_2d/smiles/{encoded_smiles}/cids/JSON"
return make_request(url)
def substructure(smiles):
encoded_smiles = urllib.parse.quote(smiles)
url = f"{PUBCHEM_BASE_URL}/pug/compound/fastsubstructure/smiles/{encoded_smiles}/cids/JSON"
return make_request(url)
def assays(cid, active_only=False):
url = f"{PUBCHEM_BASE_URL}/pug/compound/cid/{cid}/assaysummary/JSON"
data = make_request(url)
if active_only:
data = filter_active_assays(data)
return data
def filter_active_assays(data):
if not isinstance(data, dict) or "Table" not in data:
return data
table = data["Table"]
columns = table.get("Columns", {}).get("Column", [])
try:
outcome_idx = columns.index("Activity Outcome")
except ValueError:
return data
filtered_rows = []
for row in table.get("Row", []):
cell = row.get("Cell", [])
if len(cell) > outcome_idx and cell[outcome_idx] == "Active":
filtered_rows.append(row)
table["Row"] = filtered_rows
return data
def range_search(feature, min_val, max_val):
url = f"{PUBCHEM_BASE_URL}/pug/compound/{feature}/range/{min_val}/{max_val}/cids/JSON"
return make_request(url)
def main():
parser = argparse.ArgumentParser(description="PubChem API Wrapper Script")
subparsers = parser.add_subparsers(dest="command", required=True)
# Resolve
p_resolve = subparsers.add_parser(
"resolve", help="Resolve a chemical name or InChI to CIDs and SMILES"
)
group = p_resolve.add_mutually_exclusive_group(required=True)
group.add_argument("--name", help="Chemical name")
group.add_argument("--inchi", help="InChI string")
p_resolve.add_argument(
"--output", required=True, help="Output JSON file path"
)
# Properties
p_props = subparsers.add_parser(
"properties", help="Get chemical properties for a CID"
)
p_props.add_argument("--cid", required=True, help="Compound ID")
p_props.add_argument("--output", required=True, help="Output JSON file path")
# Synonyms
p_syn = subparsers.add_parser("synonyms", help="Get synonyms for a CID")
p_syn.add_argument("--cid", required=True, help="Compound ID")
p_syn.add_argument("--output", required=True, help="Output JSON file path")
# Safety
p_safe = subparsers.add_parser("safety", help="Get GHS safety data for a CID")
p_safe.add_argument("--cid", required=True, help="Compound ID")
p_safe.add_argument("--output", required=True, help="Output JSON file path")
# Pharmacology
p_pharm = subparsers.add_parser(
"pharmacology", help="Get pharmacology data for a CID"
)
p_pharm.add_argument("--cid", required=True, help="Compound ID")
p_pharm.add_argument("--output", required=True, help="Output JSON file path")
# View
p_view = subparsers.add_parser(
"view", help="Get specific PUG-View heading for a CID"
)
p_view.add_argument("--cid", required=True, help="Compound ID")
p_view.add_argument(
"--heading", required=True, help="Heading (e.g. 'Geometry')"
)
p_view.add_argument("--output", required=True, help="Output JSON file path")
# Xrefs
p_xrefs = subparsers.add_parser(
"xrefs", help="Get cross-references (PatentID, PubMedID, etc.) for a CID"
)
p_xrefs.add_argument("--cid", required=True, help="Compound ID")
p_xrefs.add_argument(
"--type", required=True, help="Xref type (e.g. 'PatentID')"
)
p_xrefs.add_argument("--output", required=True, help="Output JSON file path")
# Query
p_query = subparsers.add_parser(
"query", help="Execute a custom PUG-REST path"
)
p_query.add_argument(
"--path",
required=True,
help="e.g., compound/cid/2244/xrefs/PatentID/JSON",
)
p_query.add_argument("--output", required=True, help="Output JSON file path")
# Image
p_img = subparsers.add_parser("image", help="Get image URL for a CID")
p_img.add_argument("--cid", required=True, help="Compound ID")
p_img.add_argument("--output", required=True, help="Output JSON file path")
# Similarity
p_sim = subparsers.add_parser(
"similarity", help="Fast 2D similarity search by SMILES"
)
p_sim.add_argument("--smiles", required=True, help="SMILES string")
p_sim.add_argument("--output", required=True, help="Output JSON file path")
# Substructure
p_sub = subparsers.add_parser(
"substructure", help="Fast substructure search by SMILES"
)
p_sub.add_argument("--smiles", required=True, help="SMILES string")
p_sub.add_argument("--output", required=True, help="Output JSON file path")
# Assays
p_assay = subparsers.add_parser("assays", help="Get assay summary for a CID")
p_assay.add_argument("--cid", required=True, help="Compound ID")
p_assay.add_argument(
"--active-only", action="store_true", help="Filter for active assays only"
)
p_assay.add_argument("--output", required=True, help="Output JSON file path")
# Range
p_range = subparsers.add_parser("range", help="Search by property range")
p_range.add_argument(
"--feature", required=True, help="Property name (e.g. molecular_weight)"
)
p_range.add_argument("--min", required=True, help="Minimum value")
p_range.add_argument("--max", required=True, help="Maximum value")
p_range.add_argument("--output", required=True, help="Output JSON file path")
args = parser.parse_args()
if args.command == "resolve":
data = resolve(name=args.name, inchi=args.inchi)
elif args.command == "properties":
data = properties(args.cid)
elif args.command == "synonyms":
data = synonyms(args.cid)
elif args.command == "safety":
data = safety(args.cid)
elif args.command == "pharmacology":
data = pharmacology(args.cid)
elif args.command == "view":
data = view(args.cid, args.heading)
elif args.command == "xrefs":
data = xrefs(args.cid, args.type)
elif args.command == "query":
data = query(args.path)
elif args.command == "image":
data = image(args.cid)
elif args.command == "similarity":
data = similarity(args.smiles)
elif args.command == "substructure":
data = substructure(args.smiles)
elif args.command == "assays":
data = assays(args.cid, active_only=args.active_only)
elif args.command == "range":
data = range_search(args.feature, args.min, args.max)
else:
print("Unknown command")
sys.exit(1)
write_output(data, args.output)
if __name__ == "__main__":
main()
SKILL.md
---
name: pubchem-database
description: >
Query PubChem, search by name/CID/SMILES, retrieve properties,
similarity/substructure searches, bioactivity, for cheminformatics. Use when a
user asks about a specific chemical, drug, or molecule.
---
# PubChem Database
## Prerequisites
1. **`uv`**: Read the `uv` skill and follow its Setup instructions to ensure
`uv` is installed and on PATH.
2. **User Notification**: If .licenses/pubchem_database_LICENSE.txt does not
already exist in the workspace root directory then (1) prominently notify
the user to check the terms at
https://pubchem.ncbi.nlm.nih.gov/docs/citation-guidelines and
https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest, then (2) create the file
recording the notification text and timestamp.
## Quick Start
PubChem queries are executed via a robust Python wrapper script to respect
terms-of-service and handle complex JSON parsing. This script allows safe
multi-agent use of APIs.
Example: Resolve a chemical name to its Compound ID (CID)
```bash
uv run scripts/pubchem_api.py resolve --name "aspirin" --output result.json
```
## Core Rules
- **Use the Wrapper**: ALWAYS execute the provided helper scripts to query the
database rather than accessing the database directly. The scripts
automatically enforce the required rate limit gracefully.
- Read the generated JSON output file, and process it with jq or code.
- **Verify Facts**: ALWAYS verify information retrieved from memory with a
database query if the user asks for a specific fact that can be checked in
PubChem. Do not rely solely on internal knowledge.
- **Notification**: If this skill is used, ensure this is mentioned in the
output.
## Core Capabilities
**1. Compound Resolution (Name or InChI to Identifiers)** Convert chemical/trade
names or InChI strings into PubChem CIDs, SMILES, and InChIKeys.
```bash
uv run scripts/pubchem_api.py resolve --name "ibuprofen" --output result.json
# OR
uv run scripts/pubchem_api.py resolve --inchi "InChI=1S/C3/c1-3-2/i1+1" --output result.json
```
**2. Physical & Chemical Property Retrieval** Fetch computed properties (e.g.,
MolecularWeight, XLogP, TPSA).
```bash
uv run scripts/pubchem_api.py properties --cid 2244 --output result.json
```
**3. Synonyms and Trade Names** Find alternative names and brand names.
```bash
uv run scripts/pubchem_api.py synonyms --cid 2244 --output result.json
```
## Advanced Context
**4. Safety and Hazard Information (GHS)** Retrieve Global Harmonized System
hazard statements and handling precautions (uses PUG-View).
```bash
uv run scripts/pubchem_api.py safety --cid 2244 --output result.json
```
**5. Drug and Medication Information** Fetch FDA pharmacology data, mechanism of
action, and therapeutic uses (uses PUG-View).
```bash
uv run scripts/pubchem_api.py pharmacology --cid 2244 --output result.json
```
**6. Custom Heading (PUG-View)** Retrieve any specific heading from the PUG-View
system (e.g., 'Geometry', 'Crystal Structures').
```bash
uv run scripts/pubchem_api.py view --cid 3939 --heading "Crystal Structures" --output result.json
```
**7. Image Generation** Retrieve 2D chemical structure images. The script
returns a Markdown-formatted image link.
```bash
uv run scripts/pubchem_api.py image --cid 2244 --output result.json
```
## Complex Search & Biology
**8. Structure-Based Searching (Similarity & Substructure)** Find molecules
similar to a SMILES string or containing a specific substructure.
```bash
uv run scripts/pubchem_api.py similarity --smiles "CC(=O)OC1=CC=CC=C1C(=O)O" --output result.json
```
and
```bash
uv run scripts/pubchem_api.py substructure --smiles "C1=CC=CC=C1" --output result.json
```
**9. BioAssay & Target Interactions** Identify genes or proteins a chemical
interacts with.
```bash
uv run scripts/pubchem_api.py assays --cid 2244 --output result.json
```
## Advanced Usage & Workflows
**10. Cross-references (Xrefs)** Fetch identifiers cross-referenced with a CID
(e.g., PatentID, PubMedID).
```bash
uv run scripts/pubchem_api.py xrefs --cid 2244 --type "PatentID" --output result.json
```
**11. Property Range Search** Find CIDs within a specific property range.
Supported features include: `molecular_weight`, `heavy_atom_count`, `xlogp`,
`tpsa`, `h_bond_donor_count`, `h_bond_acceptor_count`, `rotatable_bond_count`,
`exact_mass`, `monoisotopic_mass`, and `complexity`.
```bash
uv run scripts/pubchem_api.py range --feature molecular_weight --min 400.0 --max 400.05 --output result.json
```
**12. Custom PUG-REST Query** Execute a raw path against the PUG-REST API.
```bash
uv run scripts/pubchem_api.py query --path "compound/cid/2244/xrefs/PatentID/JSON" --output result.json
```
## Fallback Search Strategies
If direct resolution by name or formula fails (e.g., for complex compounds or
specific ions):
- **Search for parent/neutral molecule**: If searching for an ion or salt, try
searching for the neutral parent compound.
- **Deconstruct complex formulas**: If a complex formula returns no results,
try searching for major components or ligands.
- **Use substructure or similarity search**: If you have a SMILES string or
can generate one for a component, use it to find related compounds.
## Complex Queries and Multi-Step Tasks
* **Custom/Complex Queries**: For more details, read
[references/endpoints.md](references/endpoints.md) to construct raw PUG-REST
URLs.
* **Multi-Step Tasks**: For complex tasks like drug discovery pipelines,
follow the checklists in [references/workflows.md](references/workflows.md).