Skip to content
cccskills
FreePublished on GitHub

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/catalogs, and assemble compound sets for docking. Property (MW/logP) filtering is done locally with RDKit. For bioactivity use chembl-database-bioactivity; for approved drugs use drugbank-database-access.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md20.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

ZINC Chemical Library Database (ZINC22 / CartBlanche)

Overview

ZINC (ZINC Is Not Commercial) is a free database of commercially available compounds curated for virtual screening. The current generation, ZINC22, holds billions of make-on-demand and in-stock molecules and is served through the CartBlanche web application at cartblanche22.docking.org. CartBlanche exposes a small JSON API to look up substances by ZINC ID, resolve a SMILES to its ZINC identifier, and inspect supplier/catalog purchasability.

Important — the old ZINC15 REST API no longer works for automated access. zinc15.docking.org (and zinc.docking.org) now sits behind a site-wide CAPTCHA: every request to /substances.json, /tranches/..., /substances/{id}.json, etc. is redirected to a /captcha/ page or returns 403 Forbidden. Any script using the mwt__gte / availability / similarity query parameters against zinc15.docking.org will fail. Use the ZINC22 CartBlanche endpoints documented below instead.

Three things about ZINC22/CartBlanche that change how you use this skill:

  1. There is no server-side property-range query. ZINC22/CartBlanche has no mwt__gte / logp__lte / hbd__lte search. You filter by molecular property by either (a) selecting tranches (a 2D MW x logP grid) for bulk download, or (b) retrieving compounds and filtering locally with RDKit. This skill shows the local-RDKit approach.
  2. SMILES lookup is asynchronous. You POST a SMILES, receive a task id, and poll for the result. Exact-match lookup completes in seconds.
  3. Reliable programmatic search = exact match. The API's broader analog search (dist > 0) is slow and frequently times out on the public server, and the Smallworld similarity search is a website-only flow. Treat exact SMILES → ZINC ID lookup as the dependable primitive; see the "Analog / Similarity Search" note for the (limited) alternatives.

When to Use

  • Looking up a known ZINC ID to get its SMILES, computed properties, and purchasability/suppliers
  • Resolving a molecule you have as a SMILES to its ZINC22 identifier(s) to check availability
  • Checking whether a hit compound is purchasable and from which catalogs before ordering
  • Assembling a SMILES/ZINC-ID set to feed into a docking campaign
  • For property-filtered library building, combine SMILES/tranche retrieval here with local RDKit filtering (rdkit-cheminformatics)
  • For known drug bioactivity data use chembl-database-bioactivity; for approved drug structures use drugbank-database-access

Prerequisites

  • Python packages: requests, pandas (and rdkit for local property filtering)
  • Data requirements: a ZINC ID, or a SMILES string
  • Environment: internet connection; no API key needed; SMILES lookups are async (poll for the result)
  • Rate limits: be courteous — serialize searches, cache results, and do not poll the task endpoint faster than once every few seconds
pip install requests pandas
# optional, for local property filtering:
pip install rdkit

Quick Start

import requests

BASE = "https://cartblanche22.docking.org"
HEADERS = {"User-Agent": "sciagent-zinc-skill/1.0"}

# Look up a substance by ZINC ID (synchronous, returns JSON immediately)
r = requests.get(f"{BASE}/substance/ZINC000000029632.json", headers=HEADERS, timeout=30)
r.raise_for_status()
c = r.json()

td = c["tranche_details"]
print(f"ZINC ID : {c['zinc_id']}  (db: {c['db']})")
print(f"SMILES  : {c['smiles']}")
print(f"MW      : {td['mwt']:.2f}   logP: {td['logp']:.2f}   heavy atoms: {td['heavy_atoms']}")
print(f"InChIKey: {td['inchikey']}")
print(f"Catalogs: {len(c.get('catalogs', []))} supplier entries")

Expected output (abridged):

ZINC ID : ZINC000000029632  (db: zinc20)
SMILES  : O=C([C@H]1CCCN1C(=O)Cc1c[nH]c2ccccc12)N1CCc2ccccc2C1
MW      : 387.48   logP: 3.29   heavy atoms: 29
InChIKey: QCEMBLKSDIWPBK-JOCHJYFZSA-N
Catalogs: 4 supplier entries

Core API

The CartBlanche base URL and two reusable helpers used throughout:

import requests, time

BASE = "https://cartblanche22.docking.org"
HEADERS = {"User-Agent": "sciagent-zinc-skill/1.0"}

def get_substance(zinc_id):
    """Fetch full record for one ZINC ID (synchronous)."""
    r = requests.get(f"{BASE}/substance/{zinc_id}.json", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json()

def smiles_lookup(smiles, dist=0, adist=0, database="zinc22", timeout=120, poll=5):
    """Resolve a SMILES to ZINC22 substance dicts via the async task API.

    dist=0, adist=0 -> exact match (fast, reliable; use this).
    dist/adist > 0  -> near-neighbor analog search (slow; may time out — see note).
    Returns a list of substance dicts (possibly empty).
    """
    files = {"smiles": (None, smiles), "dist": (None, str(dist)),
             "adist": (None, str(adist)), "database": (None, database)}
    sub = requests.post(f"{BASE}/smiles.json", files=files, headers=HEADERS, timeout=60)
    sub.raise_for_status()
    task = sub.json()["task"]                       # async: returns {"task": "<uuid>"}

    deadline = time.time() + timeout
    while time.time() < deadline:
        res = requests.get(f"{BASE}/search/result/{task}", headers=HEADERS, timeout=30).json()
        if res.get("status") == "SUCCESS":
            data = res.get("result")
            # result is {"zinc22": [...], "zinc20": [...]} when hits exist,
            # or an empty list when there are none.
            if isinstance(data, dict):
                return data.get("zinc22", []) + data.get("zinc20", [])
            return data or []
        time.sleep(poll)                            # status == "PROGRESS" until done
    raise TimeoutError(f"ZINC22 search task {task} did not finish in {timeout}s")

Query 1: Retrieve a Compound by ZINC ID

Fetch the full record (SMILES, computed properties, formula, ring/heteroatom counts, supplier catalogs) for a known ZINC identifier.

c = get_substance("ZINC000000029632")
td = c["tranche_details"]

print(f"ZINC ID    : {c['zinc_id']}")
print(f"SMILES     : {c['smiles']}")
print(f"Formula    : {c['mol_formula']}")
print(f"MW         : {td['mwt']:.2f}")
print(f"logP       : {td['logp']:.2f}")
print(f"Heavy atoms: {td['heavy_atoms']}")
print(f"Rings      : {c['rings']}   Hetero atoms: {c['hetero_atoms']}")
print(f"InChIKey   : {td['inchikey']}")
print(f"Suppliers  : {len(c.get('catalogs', []))}")

Both the modern ZINCbq.../ZINCbj... style IDs and the legacy 12-digit ZINC000... IDs resolve through this endpoint.

Query 2: Resolve a ZINC ID from a SMILES (Exact Match)

Find the ZINC22 identifier(s) for a molecule you have as a SMILES string. This is the dependable search primitive.

hits = smiles_lookup("CC(=O)Nc1ccc(O)cc1")   # acetaminophen, exact (dist=0)
print(f"Exact matches: {len(hits)}")
for h in hits[:5]:
    td = h["tranche_details"]
    print(f"  {h['zinc_id']:22s}  MW {td['mwt']:.1f}  logP {td['logp']:.2f}  {h['smiles']}")

A single molecule can map to several ZINC IDs across ZINC22 sub-databases (e.g. ZINCbq... and ZINCbj...) — each represents the same structure in a different build.

Query 3: Purchasability and Supplier Catalogs

Purchasability is embedded in the substance record under catalogs — no separate request needed.

c = get_substance("ZINC000000029632")
catalogs = c.get("catalogs", [])
print(f"Supplier entries: {len(catalogs)}")
for cat in catalogs:
    print(f"  {cat.get('catalog_name', 'n/a'):24s} | "
          f"purchasable: {cat.get('purchase')} | "
          f"code: {cat.get('supplier_code', 'n/a')} | "
          f"price: {cat.get('price', 'n/a')} {cat.get('unit', '')}")

purchase: 1 indicates the compound is orderable from that catalog; entries may include price, quantity, and lead time (shipping).

Query 4: Property-Filtered Library (Local RDKit Filtering)

ZINC22 has no server-side MW/logP query. The portable pattern is: gather candidate SMILES (resolved ZINC IDs, a downloaded tranche file, or your own enumeration), then filter locally with RDKit.

import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski

# Candidate SMILES (here: a small hand list; in practice load a tranche .smi or your own set)
smiles_list = [
    "CC(=O)Nc1ccc(O)cc1",            # acetaminophen  (MW 151 - too small, filtered out)
    "CC(C)NCC(O)COc1cccc2ccccc12",   # propranolol    (MW 259, logP 2.6 - lead-like)
    "CN(C)CCOC(c1ccccc1)c1ccccc1",   # diphenhydramine
    "c1ccc(NC(=O)c2ccccc2)cc1",      # benzanilide    (MW 197 - too small, filtered out)
]

def lead_like(smi):
    m = Chem.MolFromSmiles(smi)
    if m is None:
        return None
    mw, logp = Descriptors.MolWt(m), Descriptors.MolLogP(m)
    hbd, hba = Lipinski.NumHDonors(m), Lipinski.NumHAcceptors(m)
    ok = 250 <= mw <= 350 and 1 <= logp <= 3 and hbd <= 3 and hba <= 7
    return {"smiles": Chem.MolToSmiles(m), "mw": round(mw, 1), "logp": round(logp, 2),
            "hbd": hbd, "hba": hba} if ok else None

rows = [r for r in (lead_like(s) for s in smiles_list) if r]
df = pd.DataFrame(rows).drop_duplicates(subset=["smiles"])
print(f"Lead-like compounds after RDKit filter: {len(df)}")
print(df.head())

Analog / Similarity Search (limited — read before relying on it)

The submit endpoint accepts dist (topological graph-edit distance, 0–3) and adist ("anon" distance, scaffold hops) for a near-neighbor analog search:

# Near-neighbor analogs. Keep dist small (1) and the timeout generous; expect FEW results.
try:
    analogs = smiles_lookup("c1ccc(NC(=O)c2ccccc2)cc1", dist=1, adist=0, timeout=240)
    print(f"Analogs: {len(analogs)}")
    for h in analogs[:10]:
        print("  ", h["zinc_id"], h["smiles"])
except TimeoutError as e:
    print("Analog search timed out:", e)

Caveats verified against the live public server:

  • dist=0 (exact) is fast and reliable. dist >= 1 runs across billions of molecules on a shared cluster and frequently times out (e.g. dist=3 did not return within ~270 s); even when it succeeds it often returns only the query and a handful of near-identical structures.
  • For broad whole-molecule similarity (Tanimoto over graph-edit distance), CartBlanche provides a separate Smallworld search on the website (cartblanche22.docking.org → Similarity → Smallworld, /similarity/sw). It is an interactive flow and is not exposed as a simple GET/POST JSON endpoint here — use the web UI for large similarity jobs.
  • Do not build automated pipelines that assume analog search returns a rich set. Base workflows on exact lookup + local RDKit/cheminformatics expansion instead.

Key Concepts

ZINC22 IDs

ZINC22 introduced short alphanumeric IDs such as ZINCbq0000001gLA (the prefix letters encode the sub-database / tranche). Legacy 12-digit IDs like ZINC000000029632 (from ZINC15/ZINC20) still resolve through /substance/{id}.json. Always pass the ID exactly as given.

Asynchronous Search Model

SMILES searches are queued as tasks:

  1. POST /smiles.json (multipart form: smiles, dist, adist, database) → {"task": "<uuid>"}
  2. GET /search/result/{task} → {"status": "PROGRESS"} while running, then {"status": "SUCCESS", "result": {...}}
  3. On success, result is a dict keyed by sub-database (zinc22, sometimes zinc20) when there are hits, or an empty list when there are none. The helper above normalizes both shapes.

Tranches

ZINC organizes molecules into a 2D grid of "tranches" by heavy-atom count / MW (H-codes, e.g. H11) and logP (letter bins). The CartBlanche Tranches browser (cartblanche22.docking.org, "3D"/"2D" tranche pages) lets you select property regions and bulk-download SMILES/SDF for docking. Tranche bulk download is interactive (and large); for programmatic property filtering prefer the RDKit approach in Query 4, or fetch tranche files from files.docking.org and filter locally.

Result Fields

Each search/substance record exposes: zinc_id, smiles, mol_formula, rings, hetero_atoms, db, catalogs[], and tranche_details (mwt, logp, heavy_atoms, inchi, inchikey). SMILES search rows additionally include sub_id, matched_smiles, and the tranche code.

Common Workflows

Workflow 1: Check Purchasability of a Hit List

Goal: Given SMILES of docking hits, resolve their ZINC22 IDs and report which are purchasable.

import pandas as pd

hit_smiles = [
    "CC(=O)Nc1ccc(O)cc1",            # acetaminophen
    "c1ccc(NC(=O)c2ccccc2)cc1",      # benzanilide
]

rows = []
for smi in hit_smiles:
    matches = smiles_lookup(smi)                   # exact
    if not matches:
        rows.append({"query_smiles": smi, "zinc_id": None, "purchasable": False})
        continue
    for m in matches:
        cats = m.get("catalogs", [])
        rows.append({
            "query_smiles": smi,
            "zinc_id": m["zinc_id"],
            "mw": m["tranche_details"]["mwt"],
            "n_catalogs": len(cats),
            "purchasable": any(c.get("purchase") for c in cats) or bool(cats),
        })

df = pd.DataFrame(rows)
print(df)
df.to_csv("hit_purchasability.csv", index=False)
print("Saved: hit_purchasability.csv")

Workflow 2: Resolve and Property-Filter a Candidate Set for Docking

Goal: Take a list of candidate SMILES (your own enumeration or a tranche file), keep the ones present/purchasable in ZINC22, filter to lead-like space, and export SMILES for docking.

import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors

candidates = [
    "CC(=O)Nc1ccc(O)cc1",
    "O=C([C@H]1CCCN1C(=O)Cc1c[nH]c2ccccc12)N1CCc2ccccc2C1",
    "c1ccc(NC(=O)c2ccccc2)cc1",
]

rows = []
for smi in candidates:
    m = Chem.MolFromSmiles(smi)
    if m is None:
        continue
    mw, logp = Descriptors.MolWt(m), Descriptors.MolLogP(m)
    if not (200 <= mw <= 500 and -1 <= logp <= 5):     # lead-like local filter
        continue
    matches = smiles_lookup(smi)                        # confirm it exists in ZINC22
    if not matches:
        continue
    m0 = matches[0]
    rows.append({"zinc_id": m0["zinc_id"], "smiles": Chem.MolToSmiles(m),
                 "mw": round(mw, 1), "logp": round(logp, 2),
                 "purchasable": bool(m0.get("catalogs"))})

df = pd.DataFrame(rows).drop_duplicates(subset=["smiles"])
print(f"ZINC22-confirmed lead-like compounds: {len(df)}")
df[["smiles", "zinc_id"]].to_csv("docking_library.smi", sep=" ", index=False, header=False)
print("Saved: docking_library.smi")
print(df)

Key Parameters

ParameterEndpointDefaultRange / OptionsEffect
(path)GET /substance/{zinc_id}.json—ZINC IDSynchronous substance lookup
smilesPOST /smiles.json—valid SMILESQuery molecule
distPOST /smiles.json00–3Topological distance; 0 = exact (reliable), >=1 = analog search (slow, may time out)
adistPOST /smiles.json00–3Anonymous-graph distance (scaffold hopping); 0 = off
databasePOST /smiles.jsonzinc22"zinc22", "zinc20"Which ZINC build to search
taskGET /search/result/{task}—UUID from submitPoll handle for async result

Best Practices

  1. Use ZINC22 (CartBlanche), not ZINC15. The zinc15.docking.org REST API is CAPTCHA-walled and will not work from scripts. All endpoints here target cartblanche22.docking.org.

  2. Lean on exact lookup; don't depend on analog search. dist=0 is the reliable primitive. For analogs/similarity, expand locally (RDKit, rdkit-cheminformatics) or use the Smallworld web UI — the analog API times out on broad queries.

  3. Filter properties locally with RDKit. There is no server-side MW/logP/HBD query in ZINC22. Retrieve candidates, then filter with RDKit — this is also where you apply PAINS/Brenk alerts.

  4. Poll politely. SMILES lookups are async; poll /search/result/{task} every few seconds, set a sensible timeout, and handle the PROGRESS → SUCCESS transition (and TimeoutError).

  5. Deduplicate by canonical SMILES. A structure can appear under multiple ZINC IDs / catalogs. Canonicalize with RDKit (Chem.MolToSmiles(Chem.MolFromSmiles(smi))) before docking.

  6. Verify purchasability via catalogs. Treat catalogs entries with purchase: 1 as orderable; record supplier_code and catalog_name for procurement.

  7. Cache results. ZINC data updates periodically; cache substance/search JSON with a date-stamped filename and avoid re-querying within a project.

Common Recipes

Recipe: Lookup ZINC22 ID and purchasability from a SMILES

matches = smiles_lookup("CC(=O)Nc1ccc(O)cc1")   # acetaminophen
for m in matches:
    cats = m.get("catalogs", [])
    print(f"{m['zinc_id']} | MW {m['tranche_details']['mwt']:.1f} | "
          f"catalogs {len(cats)} | purchasable {bool(cats)}")

Recipe: Batch-resolve a list of ZINC IDs

zinc_ids = ["ZINC000000029632", "ZINCbq0000001gLA"]
for zid in zinc_ids:
    try:
        c = get_substance(zid)
        print(f"{zid}: {c['smiles']}  MW {c['tranche_details']['mwt']:.1f}")
    except requests.HTTPError as e:
        print(f"{zid}: not found ({e.response.status_code})")

Recipe: Property Distribution of a Library

import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors

df = pd.read_csv("docking_library.smi", sep=" ", names=["smiles", "zinc_id"])
df["mw"] = df["smiles"].map(lambda s: Descriptors.MolWt(Chem.MolFromSmiles(s)))
df["logp"] = df["smiles"].map(lambda s: Descriptors.MolLogP(Chem.MolFromSmiles(s)))
print(f"Library size: {len(df)}")
print(df[["mw", "logp"]].describe())

Troubleshooting

ProblemCauseSolution
Requests to zinc15.docking.org redirect to /captcha/ or return 403The legacy ZINC15 REST API is CAPTCHA-walled and unusable from scriptsSwitch all calls to cartblanche22.docking.org (ZINC22 / CartBlanche) as shown here
GET /substances/{id}.json (plural) returns HTMLWrong path — that is the SPA catch-allUse the singular /substance/{id}.json
Endpoint returns 200 with content-type: application/json but body is HTMLCartBlanche serves the SPA shell for unknown routes (misleading content-type)Check the body actually parses as JSON; only the documented endpoints return data
POST /smiles.json returns {"task": ...} but you expected resultsSMILES search is asynchronousPoll GET /search/result/{task} until status == "SUCCESS"
400 No Valid SMILES, please try againSMILES rejected (or wrong request form)Validate the SMILES with RDKit; submit via multipart form fields smiles/dist/adist/database
KeyError: 'data' / 'zinc22' on a successful taskresult is a dict when there are hits, but an empty list when there are noneHandle both shapes (see smiles_lookup helper)
Analog search (dist >= 1) times out or returns only the exact matchVery large search space on a shared clusterUse dist=0 (exact); for similarity use the Smallworld web UI; expand analogs locally with RDKit
/substance/random.json returns 400; /substance/random/{n}.json returns {"status":"PENDING"}The random endpoint is async/non-trivialNot needed for scripting; pick known ZINC IDs instead
HTTP 404 for a compound IDID typo or not present in this buildVerify the ZINC ID; try database="zinc20" for legacy IDs

Related Skills

  • rdkit-cheminformatics — Compute properties and apply PAINS/Brenk filters on retrieved ZINC compounds (required for property filtering here)
  • autodock-vina-docking — Use exported ZINC SMILES/SDF files for molecular docking campaigns
  • chembl-database-bioactivity — Bioactivity data for compounds identified in ZINC virtual screens
  • drugbank-database-access — Approved-drug structures and annotations

References

  • CartBlanche / ZINC22 web app — Substance lookup, SMILES search, tranche browser
  • ZINC22 paper — Tingle et al., "ZINC-22, A Free Multi-Billion-Scale Database of Tangible Compounds for Ligand Discovery," J. Chem. Inf. Model. 2023
  • docking.org wiki — CartBlanche usage and ZINC22 download documentation
  • files.docking.org — Bulk tranche file downloads for ZINC22

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

API + Python SDK for ordering cell-free protein expression and binding assays. Submit sequences for expression (10–100 µg), measure binding affinity (KD) against targets, track status, and retrieve results programmatically — no wet-lab setup. Built for ML-guided directed evolution and antibody/nanobody optimization. Requires Adaptyv account and API key.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

aeon

Free

scikit-learn compatible Python toolkit for time series ML: classify, cluster, regress, segment, transform with 30+ algorithms (ROCKET, InceptionTime, KNN-DTW, HIVE-COTE, WEASEL). Handles panel, multivariate, and unequal-length series. Maintained successor to sktime. Alternatives: sktime (larger ecosystem), tslearn (fewer algorithms), catch22 (features only).

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

AiZynthFinder retrosynthetic route planning (CASP) from AstraZeneca Molecular AI. Monte Carlo tree search guided by a template-based neural expansion policy recursively disconnects a target SMILES until precursors are found in a purchasable stock. Covers config.yml (v4 format), aizynthcli batch screening, the AiZynthFinder/AiZynthExpander Python API, one-step disconnections, custom stocks via smiles2stock, scorers, Retro*/breadth-first/DFPN search alternatives, and reading output.json.gz / trees.json. Use for synthesis route planning, synthesizability screening, and building-block/precursor search. For reaction barriers use neb-irc-activation-energy; for 2D reaction scheme drawing use rdkit-chemdraw-cdxml.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

Access AlphaFold DB's 200M+ predicted structures by UniProt ID. Download PDB/mmCIF, analyze pLDDT/PAE, bulk-fetch proteomes via Google Cloud. For experimental structures use PDB; for prediction use ColabFold or ESMFold.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

Annotated matrices for single-cell genomics. Stores X with obs/var metadata, layers, embeddings (obsm/varm), graphs (obsp/varp), uns. Use for .h5ad/.zarr I/O, concatenation, scverse integration. For analysis use scanpy; for probabilistic models use scvi-tools.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

GRN inference from expression via GRNBoost2 (gradient boosting) or GENIE3 (Random Forest). Load matrix, filter by TFs, infer TF-target-importance links, save network. Dask-parallelized to single-cell scale. Core SCENIC component.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

See all skills by jaechang-hits

Report a problem with this skill