Skip to main content

Python client for the onepot API

Project description

onepot-python

Python client for the onepot API — find purchasable analogs of your query molecules, price exact molecules directly, with optional retrosynthesis decomposition and per-position building-block filtering.

Installation

uv add onepot
# or
pip install onepot

Quick start

from onepot import Client

client = Client(api_key="your-api-key")

resp = client.search(
    smiles_list=["c1ccc(NC(=O)c2ccccc2)cc1"],
    max_results=10,
)
for r in resp["queries"][0]["results"]:
    print(r["smiles"], r["similarity"], r["price_usd"])

Features

  • Similarity search — Tanimoto nearest analogs from the onepot catalog
  • Exact pricing — price the exact query molecule directly, skipping the analog search; fast, cheap bulk pricing of pre-enumerated libraries
  • Substructure search — purchasable molecules containing a SMILES/SMARTS pattern
  • Decomposition + BB filters — inspect the retro paths the system considered for your query, then refine which candidate BBs are eligible per position
  • Risk and price filters — exclude results above a chemistry-risk, supplier-risk, or price threshold
  • Streaming — single-molecule searches with SSE progress updates
  • Ordering — submit results for synthesis quoting
  • Screening-space sampling — seeded random or scaffold-balanced draws from the CORE pool

Search

Basic

resp = client.search(smiles_list=["c1ccc(-c2ccccc2)cc1"], max_results=10)
curl -X POST https://api.onepot.ai/v1/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{"smiles_list": ["c1ccc(-c2ccccc2)cc1"], "max_results": 10}'

Exact lookup

Use exact_lookup=True to price each query molecule directly and skip the analog/similarity search. Each query returns at most one result — the query molecule itself (similarity 1.0), priced from a catalog match or its cheapest single-step decomposition — or no result if it can't be made from catalog building blocks. It never substitutes an analog. This is the fast, cheap path for bulk pricing of a pre-enumerated library, and bills at 0.1× (see Pricing).

resp = client.search(
    smiles_list=my_enumerated_library,   # e.g. thousands of SMILES
    exact_lookup=True,
    include_chemistry_risk=True,
)
for q in resp["queries"]:
    if q["results"]:
        print(q["query_smiles"], q["results"][0]["price_usd"])
    else:
        print(q["query_smiles"], "not priceable")
curl -X POST https://api.onepot.ai/v1/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{"smiles_list": ["c1ccc(NC(=O)c2ccccc2)cc1"], "exact_lookup": true}'

The response uses the standard shape, with results holding 0 or 1 entry per query. Exact-lookup results are not annotated with reaction_class / bbs. Cannot be combined with substructure_search, decompose, or bb_filters (rejected as 422). Streaming supports it too via client.search_stream(..., exact_lookup=True).

Streaming

For single-molecule searches with real-time progress events. Status lifecycle: startingsynthesisrescoringcomplete. The final event includes a results list with the same fields as the batch endpoint.

for event in client.search_stream("c1ccc(NC(=O)c2ccccc2)cc1", max_results=10):
    print(event["status"], event["message"])
    if event["status"] == "complete":
        results = event["results"]
curl -sN -X POST https://api.onepot.ai/v1/search/stream \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{"smiles": "c1ccc(NC(=O)c2ccccc2)cc1", "max_results": 5}'

Substructure search

Pass substructure_search=True to return purchasable molecules that contain the query as a substructure, instead of similarity hits. The query can be a SMILES or a SMARTS pattern.

resp = client.search(
    smiles_list=["c1ccc(C(=O)N)cc1"],
    max_results=10,
    substructure_search=True,
)
curl -X POST https://api.onepot.ai/v1/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{"smiles_list": ["c1ccc(C(=O)N)cc1"], "max_results": 10, "substructure_search": true}'

Risk and price filters

All optional. When set, results that exceed the threshold are excluded.

Parameter Type Values
max_price int USD, e.g. 200, 500
max_supplier_risk string "low", "medium", "high"
max_chemistry_risk string "low", "medium", "high"

Setting max_chemistry_risk automatically includes the chemistry_risk field in the response. Pass include_chemistry_risk_score=True for the raw probability score.

resp = client.search(
    smiles_list=["c1ccc(-c2ccccc2)cc1"],
    max_results=10,
    max_price=500,
    max_supplier_risk="medium",
    max_chemistry_risk="low",
    include_chemistry_risk_score=True,
)

Decompose & bb_filters

Use decompose=True to receive the retrosynthetic paths the system considered for each query — every reaction_class it found and the BB SMILES of your query at each position. Then call back with bb_filters to constrain which candidate BBs are eligible per position. Every enumerated result is automatically tagged with the reaction_class it was made from and the bbs that built it.

Call 1 — discover.

resp = client.search(
    smiles_list=["c1ccc(NC(=O)c2ccccc2)cc1"],
    max_results=5,
    decompose=True,
)
decompositions = resp["queries"][0]["decompositions"]
rxn = decompositions[0]["reaction_class"]   # e.g. "rxn_5e820be4"

Call 2 — refine. Force the building block at position 1 to vary (Tanimoto ≤ 0.7 to the query's position-1 BB) while leaving position 0 free.

resp = client.search(
    smiles_list=["c1ccc(NC(=O)c2ccccc2)cc1"],
    max_results=10,
    bb_filters=[{"reaction_class": rxn, "bb_index": 1, "max_similarity": 0.7}],
)
for r in resp["queries"][0]["results"]:
    if r.get("reaction_class") == rxn:
        bb_smiles = [b["smiles"] for b in r["bbs"]]
        print(r["smiles"], "←", " + ".join(bb_smiles))
curl -X POST https://api.onepot.ai/v1/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "smiles_list": ["c1ccc(NC(=O)c2ccccc2)cc1"],
    "max_results": 10,
    "bb_filters": [
      {"reaction_class": "rxn_<from-call-1>", "bb_index": 1, "max_similarity": 0.7}
    ]
  }'

reaction_class values like "rxn_5e820be4" come from a prior decompose=True response and are stable across calls — pass them through as strings. Each bb_filters entry takes optional min_similarity and max_similarity (Tanimoto, 0.0–1.0); omit a bound to leave that side open. Combine multiple entries to constrain multiple positions in one call. bb_index is the 0-based position of the building block within the reaction, matching the ordering in the bbs field of a decomposition or annotated result. Unknown reaction_class or min_similarity > max_similarity is rejected as 422. Streaming searches accept the same parameters via client.search_stream(...).

When a retro decomposition produces multiple paths under the same reaction_class, filters apply to each path's candidates independently (similarity is measured against that path's BB SMILES, so the same SMILES can pass one path's filter and fail another's).

Response shape

{
    "queries": [
        {
            "query_smiles": "c1ccc(NC(=O)c2ccccc2)cc1",
            "query_inchikey": "...",
            "results": [
                {
                    "smiles": "...",
                    "inchikey": "...",
                    "similarity": 0.92,
                    "price_usd": 590,
                    "supplier_risk": "low",
                    "chemistry_risk": "medium",       # if include_chemistry_risk=True
                    "chemistry_risk_score": 0.5,      # if include_chemistry_risk_score=True
                    # present on enumerated results (synthesized analogs):
                    "reaction_class": "rxn_5e820be4",
                    "bbs": [
                        {"bb_index": 0, "smiles": "<bb-smiles>"},
                        {"bb_index": 1, "smiles": "<bb-smiles>"},
                    ],
                },
                ...
            ],
            # if decompose=True:
            "decompositions": [
                {
                    "reaction_class": "rxn_5e820be4",
                    "bbs": [
                        {"bb_index": 0, "smiles": "<bb-smiles>"},
                        {"bb_index": 1, "smiles": "<bb-smiles>"},
                    ],
                },
                ...
            ],
        },
        ...
    ],
    "credits_used": 10,
    "credits_remaining": 990,
}

Sample screening space

Use sample_space() to draw virtual- or physical-screening libraries from the precomputed CORE v1.1 index. Each successful request returns exactly 1–10,000 molecules and can be replayed with its seed.

Quick start

Random sampling is the default and molecule properties are omitted by default, which keeps large responses compact:

from onepot import Client

client = Client(api_key="your-api-key")
sample = client.sample_space(count=384, seed=42)

print(len(sample["molecules"]), sample["seed"])
for molecule in sample["molecules"]:
    print(molecule["smiles"], molecule["inchikey"], molecule["price_usd"])

If seed is omitted, the API generates one and returns it in sample["seed"]. Store that value with the request to replay the same sample while the serving index is unchanged.

Screening-oriented property window

All property bounds are optional and inclusive. This example is a useful starting point for a lead-like screen, not an additional admission rule:

sample = client.sample_space(
    count=384,
    strategy="diverse",
    seed=42,
    include_properties=True,
    properties={
        "molecular_weight": {"min": 250, "max": 450},
        "clogp": {"min": 1, "max": 4},
        "tpsa": {"min": 40, "max": 120},
        "hbd": {"max": 3},
        "hba": {"max": 8},
        "rotatable_bonds": {"max": 8},
        "qed": {"min": 0.5},
    },
)

for molecule in sample["molecules"]:
    print(molecule["smiles"], molecule["price_usd"], molecule["properties"]["qed"])

Parameters

Parameter Type Default Meaning
count int 384 Number of molecules requested; 1–10,000.
strategy "random" or "diverse" "random" Random baseline or generic-scaffold balancing.
seed int or None None Unsigned 32-bit seed (02**32 - 1); generated and returned when omitted.
properties mapping none Inclusive molecular-property ranges applied before sampling.
include_properties bool False Return descriptors and Murcko scaffold strings on each molecule.
exclude_inchikeys list of strings none InChIKeys to omit; maximum 100,000.
exclude_generic_scaffolds list of strings none Exact generic Bemis–Murcko scaffold SMILES to omit; maximum 10,000.

Each property range must contain min, max, or both, and min cannot exceed max. Unknown property names are rejected.

Property Unit/domain Description
molecular_weight Da, ≥ 0 Average molecular weight.
clogp dimensionless Crippen calculated logP.
tpsa Ų, ≥ 0 Topological polar surface area.
hbd count, ≥ 0 Lipinski hydrogen-bond donors.
hba count, ≥ 0 Lipinski hydrogen-bond acceptors.
rotatable_bonds count, ≥ 0 Rotatable bonds.
heavy_atoms count, ≥ 0 Non-hydrogen atoms.
fraction_csp3 0–1 Fraction of carbon atoms that are sp³.
aromatic_rings count, ≥ 0 Aromatic rings.
rings count, ≥ 0 Total rings.
qed 0–1 Quantitative estimate of drug-likeness.

Choosing a strategy

  • strategy="random" visits hash buckets in a seeded, size-weighted order and ranks matching molecules with a deterministic 64-bit hash of InChIKey and seed. It is fast, reproducible, and independent of Parquet row ordering.
  • strategy="diverse" first draws a seeded reservoir of up to 10× the requested count (capped at 100,000), then round-robins across generic Bemis–Murcko frameworks. It improves framework coverage within the reservoir; it is not a global fingerprint MaxMin over the entire index.

The same request and seed produce the same ordered result while the serving index is unchanged. Changing filters, exclusions, strategy, or the index changes the sample.

Response

sample_space() returns the molecules and the effective seed:

{
    "molecules": [
        {
            "smiles": "...",
            "inchikey": "...",
            "price_usd": 125,
            # Present only when include_properties=True:
            "properties": {
                "molecular_weight": 347.4,
                "clogp": 2.6,
                "tpsa": 73.1,
                "hbd": 1,
                "hba": 5,
                "rotatable_bonds": 4,
                "heavy_atoms": 24,
                "fraction_csp3": 0.36,
                "aromatic_rings": 2,
                "rings": 3,
                "qed": 0.71,
                "murcko_scaffold": "...",
                "generic_murcko_scaffold": "...",
            },
        }
    ],
    "seed": 42,
}

There is no partial-success response: the API returns exactly count molecules or an HTTP error with a useful detail message. For a restrictive property window, reduce count or relax the filters and retry.

Follow-up batches and scaffold exclusions

InChIKey is the stable molecule identity. Pass previously returned InChIKeys to avoid repeats in follow-up batches:

first = client.sample_space(count=384, seed=100)
seen = [molecule["inchikey"] for molecule in first["molecules"]]

second = client.sample_space(
    count=384,
    seed=101,
    exclude_inchikeys=seen,
)

To exclude whole framework families, first request properties, collect the exact generic_murcko_scaffold strings, and pass them back unchanged:

first = client.sample_space(count=384, seed=100, include_properties=True)
scaffolds = sorted({
    molecule["properties"]["generic_murcko_scaffold"]
    for molecule in first["molecules"]
})

second = client.sample_space(
    count=384,
    seed=101,
    exclude_generic_scaffolds=scaffolds,
)

HTTP request

curl -X POST https://api.onepot.ai/v1/space/sample \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "count": 384,
    "strategy": "diverse",
    "seed": 42,
    "properties": {
      "molecular_weight": {"min": 250, "max": 450},
      "clogp": {"min": 1, "max": 4}
    },
    "include_properties": true
  }'

Validation failures return HTTP 422. The Python client raises httpx.HTTPStatusError and includes the server's detail message, which makes invalid ranges and property names visible without inspecting the raw response.

Order

Submit results for synthesis quoting. Returns an order_id you can reference in followup.

order = client.order(
    smiles=["CCO", "c1ccccc1"],
    email="you@example.com",
    notes="Optional notes",
)
# {"order_id": "a1b2c3d4-...", "molecule_count": 2}
curl -X POST https://api.onepot.ai/v1/order \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "smiles": ["CCO", "c1ccccc1"],
    "email": "you@example.com",
    "notes": "Optional notes"
  }'

Pricing

Credits are charged per SMILES in the query, by mode and chemistry-risk tier:

Tier Full search Exact lookup
Base 1 0.1
include_chemistry_risk=True 5 0.5
include_chemistry_risk_score=True 10 1.0

decompose, bb_filters, and substructure_search don't change the price.

exact_lookup=True bills at 0.1× the full-search rate (it skips the analog search). The total is charged as a whole number per request — the per-SMILES rate × molecule count, rounded, with a minimum of 1 credit per request. So a 5,000-molecule exact base search costs 500 credits, while a single molecule costs 1.

Screening-space sampling currently uses 0 credits; the response reports this as credits_used=0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

onepot-0.2.1.tar.gz (9.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

onepot-0.2.1-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

File details

Details for the file onepot-0.2.1.tar.gz.

File metadata

  • Download URL: onepot-0.2.1.tar.gz
  • Upload date:
  • Size: 9.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for onepot-0.2.1.tar.gz
Algorithm Hash digest
SHA256 5a9c4e20d19282c7b71b9c8c58f2f2372d474051415fc95e90486790c1bd8861
MD5 c5005dd423a68064fdb0e7ca6638f60c
BLAKE2b-256 bd2bf7cc2e537d68248844d73a44680b8bb2dff56783ede8800a54e80037be8b

See more details on using hashes here.

File details

Details for the file onepot-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: onepot-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for onepot-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cb46722e927f999032e1f6174ed86192a7bc87a7378b40a13b00bda4ce797e97
MD5 f53a40943ea9e065ac40789570b04a5f
BLAKE2b-256 b4c5a48bc3d5da2dc0116af793fdaf16d2e99f2aacd8fbd252d3b0ee089b98c4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page