ChEMBL Database — Bioactivity Queries
Why no SDK? Thechembl_webresource_clientpackage is convenient sugar over a public, no-auth REST/JSON API athttps://www.ebi.ac.uk/chembl/api/data/. When the SDK is unavailable, every operation can be reproduced with plainrequestsand URL parameters. This SKILL.md uses the REST path throughout so the code runs in any environment withrequestsinstalled. Django-style filter syntax (field__icontains=…,field__lte=…,field__range=a,b) works as URL query parameters.
Overview
ChEMBL is EMBL-EBI's bioactive molecule database: 2M+ compounds, 19M+ bioactivity measurements (IC50, Ki, EC50, Kd, …), 13K+ targets. The REST API at https://www.ebi.ac.uk/chembl/api/data/ returns JSON (append .json) or XML/YAML, requires no authentication, and supports Django-style query filters via URL parameters plus cursor-style pagination via page_meta.next.
When to Use
- Finding compounds by name, ChEMBL ID, or physicochemical properties
- Querying bioactivity data (IC50, Ki, EC50) for specific targets
- Performing similarity or substructure searches using SMILES
- Retrieving drug mechanisms of action and clinical indications
- Identifying inhibitors, agonists, or bioactive molecules for a target
- Analyzing structure-activity relationships (SAR) across compound series
- Filtering molecules by Lipinski rule-of-5 or other drug-likeness criteria
- For general cheminformatics (SMILES manipulation, fingerprints, descriptors) use
rdkit-cheminformaticsinstead - For an alternative compound database (NIH, broader coverage) use
pubchem-compound-search
Prerequisites
- Python packages:
requests(only requirement). Optional:pandasfor tabular analysis. - No API key required: ChEMBL is freely accessible.
- Rate limits: No published hard limit. The infrastructure is shared — add
time.sleep(0.2-0.5)between requests in batch loops; back off on HTTP 429.
pip install requests
# Optional, for DataFrame work:
pip install pandas
Quick Start
import requests
BASE = "https://www.ebi.ac.uk/chembl/api/data"
# Retrieve a molecule by ChEMBL ID
r = requests.get(f"{BASE}/molecule/CHEMBL25.json", timeout=15)
r.raise_for_status()
aspirin = r.json()
print(f"{aspirin['pref_name']}: MW={aspirin['molecule_properties']['mw_freebase']}")
# ASPIRIN: MW=180.16
# Search targets by full name (acronyms like 'EGFR' don't match pref_name — use full term)
r = requests.get(
f"{BASE}/target.json",
params={"pref_name__icontains": "epidermal growth factor receptor",
"target_type": "SINGLE PROTEIN", "limit": 5},
timeout=15,
)
targets = r.json()["targets"]
print(f"EGFR-like targets: {len(targets)}, first={targets[0]['target_chembl_id']}")
# Potent bioactivities: EGFR (CHEMBL203) IC50 <= 100 nM
r = requests.get(
f"{BASE}/activity.json",
params={"target_chembl_id": "CHEMBL203",
"standard_type": "IC50",
"standard_value__lte": 100,
"standard_units": "nM",
"limit": 5},
timeout=30,
)
data = r.json()
print(f"EGFR IC50 ≤ 100 nM records: {data['page_meta']['total_count']}")
Key Concepts
Filter Operators (Django-style, as URL parameters)
The SDK's field__operator=value syntax maps 1:1 to URL query parameters. Use & to combine filters.
| Operator | URL pattern | Example URL fragment | |----------|-------------|----------------------| | __exact | field=value | target_type=SINGLE+PROTEIN | | __iexact | field__iexact=value | pref_name__iexact=aspirin | | __contains / __icontains | field__icontains=value | pref_name__icontains=kinase | | __startswith / __endswith | field__startswith=Epi | pref_name__endswith=nib | | __gt / __gte / __lt / __lte | field__lte=100 | standard_value__lte=100 | | __range | field__range=lo,hi | molecule_properties__mw_freebase__range=300,500 | | __in | field__in=a,b,c | standard_type__in=IC50,Ki,Kd | | __isnull | field__isnull=False (Python False/True strings) | pchembl_value__isnull=False | | __regex | field__regex=… | pref_name__regex=^EGF.*kinase$ | | __search | field__search=… | description__search=apoptosis |
When passed via requests.get(..., params={...}), the library handles URL encoding automatically (including the commas in __range and __in).
Core Endpoints
All endpoints accept .json, .xml, or .yaml suffix. JSON is the default below.
| Endpoint URL | Returns | Key fields | |--------------|---------|------------| | /molecule/{chembl_id}.json | Compound by ID | pref_name, molecule_chembl_id, molecule_properties, molecule_structures | | /molecule.json?<filters> | Compound search | paginated molecules[] | | /target/{chembl_id}.json | Target by ID | pref_name, target_type, organism, target_components | | /target.json?<filters> | Target search | paginated targets[] | | /activity.json?<filters> | Bioactivity records | paginated activities[] | | /assay.json?<filters> | Assay details | paginated assays[] | | /drug.json?<filters> | Approved drug info | paginated drugs[]; supports /drug/{chembl_id}.json | | /mechanism.json?<filters> | Mechanism of action | paginated mechanisms[] | | /drug_indication.json?<filters> | Therapeutic indications | paginated drug_indications[] | | /similarity/{smiles}/{tanimoto}.json | Tanimoto similarity (0–100) | paginated molecules[] with similarity field | | /substructure/{smiles}.json | Substructure search | paginated molecules[] | | /image/{chembl_id}.svg | SVG structure image | binary SVG (NOT JSON) | | /molecule_form/{chembl_id}.json | Parent/salt forms | molecule_forms[] | | /protein_class.json | Protein classification hierarchy | hierarchical browse | | /document.json?<filters> | Literature source records | paginated documents[] |
Response Shape
{
"page_meta": {
"limit": 20,
"offset": 0,
"total_count": 12145,
"next": "/chembl/api/data/activity.json?...&offset=20",
"previous": null
},
"activities": [ /* or molecules[], targets[], etc. */ ]
}
Walk page_meta.next (a relative URL — prefix with https://www.ebi.ac.uk) until it becomes null.
Molecular Properties
Properties accessible via molecule_properties on each record:
| Field | Description | |-------|-------------| | mw_freebase | Molecular weight (free base) | | full_mwt | Full molecular weight (including salts) | | alogp | Calculated LogP | | hba | Hydrogen bond acceptors | | hbd | Hydrogen bond donors | | psa | Polar surface area | | rtb | Rotatable bonds | | num_ro5_violations | Lipinski rule-of-5 violations | | ro3_pass | Rule of 3 compliance | | cx_most_apka / cx_most_bpka | Most acidic / basic pKa |
Target Information Fields
| Field | Description | |-------|-------------| | target_chembl_id | ChEMBL target identifier | | pref_name | Preferred (full) target name — acronyms like "EGFR" do NOT match; use the spelled-out term | | target_type | SINGLE PROTEIN, PROTEIN COMPLEX, ORGANISM, … | | organism | Target organism (e.g., Homo sapiens) | | tax_id | NCBI taxonomy ID | | target_components[] | Components (UniProt accession, sequence, …) |
Bioactivity Data Fields
| Field | Description | |-------|-------------| | standard_type | Activity type: IC50, Ki, Kd, EC50, … | | standard_value | Numerical activity value | | standard_units | Units: nM, uM, … | | pchembl_value | Normalized -log10 activity (>6 = potent) | | activity_comment | Activity annotations | | data_validity_comment | Data quality flags (check before analysis) | | potential_duplicate | Duplicate flag |
Core API
1. Molecule Queries
import requests
BASE = "https://www.ebi.ac.uk/chembl/api/data"
# By ChEMBL ID
r = requests.get(f"{BASE}/molecule/CHEMBL25.json", timeout=15)
aspirin = r.json()
print(f"{aspirin['pref_n