rowan
$
npx mdskill add K-Dense-AI/scientific-agent-skills/rowanRun complex chemistry workflows without local HPC infrastructure.
- Executes pKa prediction, docking, and molecular dynamics tasks.
- Requires Python 3.12 and a valid API key for access.
- Automates batch screening and multi-step medicinal chemistry pipelines.
- Delivers computed results directly through its Python interface.
SKILL.md
.github/skills/rowanView on GitHub ↗
---
name: rowan
description: Rowan is a cloud-native molecular modeling and medicinal-chemistry workflow platform with a Python API. Use for pKa and macropKa prediction, conformer and tautomer ensembles, docking and analogue docking, protein-ligand cofolding, MSA generation, molecular dynamics, permeability, descriptor workflows, and related small-molecule or protein modeling tasks. Ideal for programmatic batch screening, multi-step chemistry pipelines, and workflows that would otherwise require maintaining local HPC/GPU infrastructure.
license: Proprietary (API key required)
compatibility: Python 3.12+, API key required
metadata:
skill-author: Rowan Science
trigger-keywords: ["pKa prediction", "molecular docking", "conformer search", "chemistry workflow", "drug discovery", "SMILES", "protein structure", "batch molecular modeling", "cloud chemistry"]
---
# Rowan: Cloud-Native Molecular-Modeling and Drug-Design Workflows
## Overview
Rowan is a cloud-native workflow platform for molecular simulation, medicinal chemistry, and structure-based design. Its Python API exposes a unified interface for small-molecule modeling, property prediction, docking, molecular dynamics, and AI structure workflows.
Use Rowan when you want to run medicinal-chemistry or molecular-design workflows programmatically without maintaining local HPC infrastructure, GPU provisioning, or a collection of separate modeling tools. Rowan handles all infrastructure, result management, and computation scaling.
## When to use Rowan
**Rowan is a good fit for:**
- Quantum chemistry, semiempirical methods, or neural network potentials
- Batch property prediction (pKa, descriptors, permeability, solubility)
- Conformer and tautomer ensemble generation
- Docking workflows (single-ligand, analogue series, pose refinement)
- Protein-ligand cofolding and MSA generation
- Multi-step chemistry pipelines (e.g., tautomer search → docking → pose analysis)
- Batch medicinal-chemistry campaigns where you need consistent, scalable infrastructure
**Rowan is not the right fit for:**
- Simple molecular I/O (use RDKit directly)
- Post-HF *ab initio* quantum chemistry or relativistic calculations
## Access and pricing model
Rowan uses a credit-based usage model. All users, including free-tier users, can create API keys and use the Python API.
### Free-tier access
- Access to all Rowan core workflows
- 20 credits per week
- 500 signup credits
### Pricing and credit consumption
Credits are consumed according to compute type:
- **CPU**: 1 credit per minute
- **GPU**: 3 credits per minute
- **H100/H200 GPU**: 7 credits per minute
Purchased credits are priced per credit and remain valid for up to one year from purchase.
### Typical cost estimates
| Workflow | Typical Runtime | Estimated Credits | Notes |
|----------|----------------|-------------------|-------|
| Descriptors | <1 min | 0.5–2 | Lightweight, good for triage |
| pKa (single transition) | 2–5 min | 2–5 | Depends on molecule size |
| MacropKa (pH 0–14) | 5–15 min | 5–15 | Broader sampling, higher cost |
| Conformer search | 3–10 min | 3–10 | Ensemble quality matters |
| Tautomer search | 2–5 min | 2–5 | Heterocyclic systems |
| Docking (single ligand) | 5–20 min | 5–20 | Depends on pocket size, refinement |
| Analogue docking series (10–50 ligands) | 30–120 min | 30–100+ | Shared reference frame |
| MSA generation | 5–30 min | 5–30 | Sequence length dependent |
| Protein-ligand cofolding | 15–60 min | 20–50+ | AI structure prediction, GPU-heavy |
## Quick start
```bash
uv pip install rowan-python
```
```python
import rowan
rowan.api_key = "your_api_key_here" # or set ROWAN_API_KEY env var
# Submit a descriptors workflow — completes in under a minute
wf = rowan.submit_descriptors_workflow("CC(=O)Oc1ccccc1C(=O)O", name="aspirin")
result = wf.result()
print(result.descriptors['MW']) # 180.16
print(result.descriptors['SLogP']) # 1.19
print(result.descriptors['TPSA']) # 59.44
```
If that prints without error, you're set up correctly.
## Installation
```bash
uv pip install rowan-python
# or: pip install rowan-python
```
## User and webhook management
### Authentication
Set an API key via environment variable (recommended):
```bash
export ROWAN_API_KEY="your_api_key_here"
```
Or set directly in Python:
```python
import rowan
rowan.api_key = "your_api_key_here"
```
Verify authentication:
```python
import rowan
user = rowan.whoami() # Returns user info if authenticated
print(f"User: {user.email}")
print(f"Credits available: {user.credits_available_string}")
```
### Webhook secret management
For webhook signature verification, manage secrets through your user account:
```python
import rowan
# Get your current webhook secret (returns None if none exists)
secret = rowan.get_webhook_secret()
if secret is None:
secret = rowan.create_webhook_secret()
print(f"Secret key: {secret.secret}")
# Rotate your secret (invalidates old, creates new)
# Use this periodically for security
new_secret = rowan.rotate_webhook_secret()
print(f"New secret created (old secret disabled): {new_secret.secret}")
# Verify incoming webhook signatures
is_valid = rowan.verify_webhook_secret(
request_body=b"...", # Raw request body (bytes)
signature="X-Rowan-Signature", # From request header
secret=secret.secret
)
```
## Molecule input formats
Rowan accepts molecules in the following formats:
- **SMILES** (preferred): `"CCO"`, `"c1ccccc1O"`
- **SMARTS patterns** (for some workflows): subset of SMARTS for substructure matching
- **InChI** (if supported in your API version): `"InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3"`
The API will validate input and raise a `rowan.ValidationError` if a molecule cannot be parsed. Always use canonicalized SMILES for reproducibility.
**Tip:** Use RDKit to validate SMILES before submission:
```python
from rdkit import Chem
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)
if mol is None:
raise ValueError(f"Invalid SMILES: {smiles}")
```
## Core usage pattern
Most Rowan tasks follow the same three-step pattern:
1. **Submit** a workflow
2. **Wait** for completion (with optional streaming)
3. **Retrieve** typed results with convenience properties
```python
import rowan
# 1. Submit — use the specific workflow function (not the generic submit_workflow)
workflow = rowan.submit_descriptors_workflow(
"CC(=O)Oc1ccccc1C(=O)O",
name="aspirin descriptors",
)
# 2. & 3. Wait and retrieve
result = workflow.result() # Blocks until done (default: wait=True, poll_interval=5)
print(result.data) # Raw dict
print(result.descriptors['MW']) # 180.16 — use result.descriptors dict, not result.molecular_weight
```
For long-running workflows, use streaming:
```python
for partial in workflow.stream_result(poll_interval=5):
print(f"Progress: {partial.complete}%")
print(partial.data)
```
### result() vs. stream_result()
| Pattern | Use When | Duration |
|---------|----------|----------|
| `result()` | You can wait for the full result | <5 min typical |
| `stream_result()` | You want progress feedback or need early partial results | >5 min, or interactive use |
**Guideline:** Use `result()` for descriptors, pKa. Use `stream_result()` for conformer search, docking, cofolding.
## Working with results
Rowan's API includes **typed workflow result objects** with convenience properties.
### Using typed properties and .data
Results have two access patterns:
1. **Convenience properties** (recommended first): `result.descriptors`, `result.best_pose`, `result.conformer_energies`
2. **Raw fallback**: `result.data` — raw dictionary from the API
Example:
```python
result = rowan.submit_descriptors_workflow(
"CCO",
name="ethanol",
).result()
# Convenience property (returns dict of all descriptors):
print(result.descriptors['MW']) # 46.042
print(result.descriptors['SLogP']) # -0.001
print(result.descriptors['TPSA']) # 57.96
# Raw data fallback (descriptors are nested under 'descriptors' key):
print(result.data['descriptors'])
# {'MW': 46.042, 'SLogP': -0.001, 'TPSA': 57.96, 'nHBDon': 1.0, 'nHBAcc': 1.0, ...}
```
**Note:** `DescriptorsResult` does **not** have a `molecular_weight` property. Descriptor keys use short names (`MW`, `SLogP`, `nHBDon`) not verbose names.
### Cache invalidation
Some result properties are lazily loaded (e.g., conformer geometries, protein structures). To refresh:
```python
result.clear_cache()
new_structures = result.conformer_molecules # Refetched
```
## Projects, folders, and organization
For nontrivial campaigns, use projects and folders to keep work organized.
### Projects
```python
import rowan
# Create a project
project = rowan.create_project(name="CDK2 lead optimization")
rowan.set_project("CDK2 lead optimization")
# All subsequent workflows go into this project
wf = rowan.submit_descriptors_workflow("CCO", name="test compound")
# Retrieve later
project = rowan.retrieve_project("CDK2 lead optimization")
workflows = rowan.list_workflows(project=project, size=50)
```
### Folders
```python
# Create a hierarchical folder structure
folder = rowan.create_folder(name="docking/batch_1/screening")
wf = rowan.submit_docking_workflow(
# ... docking params ...
folder=folder,
name="compound_001",
)
# List workflows in a folder
results = rowan.list_workflows(folder=folder)
```
## Workflow decision trees
### pKa vs. MacropKa
**Use microscopic pKa when:**
- You need the pKa of a single ionizable group
- You're interested in acid–base transitions and protonation thermodynamics
- The molecule has one or two ionizable sites
- Speed is critical (faster, fewer credits)
**Use macropKa when:**
- You need pH-dependent behavior across a physiologically relevant range (e.g., 0–14)
- You want aggregated charge and protonation-state populations across pH
- The molecule has multiple ionizable groups with coupled protonation
- You need downstream properties like aqueous solubility at different pH
**Example decision:**
```text
Phenol (pKa ~10): Use microscopic pKa
Amine (pKa ~9–10): Use microscopic pKa
Multi-ionizable drug (N, O, acidic group): Use macropKa
ADME assessment across GI pH: Use macropKa
```
### Conformer search vs. tautomer search
**Use conformer search when:**
- A single tautomeric form is known
- You need a diverse 3D ensemble for docking, MD, or SAR analysis
- Rotatable bonds dominate the chemical space
**Use tautomer search when:**
- Tautomeric equilibrium is uncertain (e.g., heterocycles, keto–enol systems)
- You need to model all relevant protonation isomers
- Downstream calculations (docking, pKa) depend on tautomeric form
**Combined workflow:**
```python
# Step 1: Find best tautomer
taut_wf = rowan.submit_tautomer_search_workflow(
initial_molecule="O=c1[nH]ccnc1",
name="imidazole tautomers",
)
best_taut = taut_wf.result().best_tautomer
# Step 2: Generate conformers from best tautomer
conf_wf = rowan.submit_conformer_search_workflow(
initial_molecule=best_taut,
name="imidazole conformers",
)
```
### Docking vs. analogue docking vs. cofolding
| Workflow | Use When | Input | Output |
|----------|----------|-------|--------|
| Docking | Single ligand, known pocket | Protein + SMILES + pocket coords | Pose, score, dG |
| Analogue docking | 5–100+ related compounds | Protein + SMILES list + reference ligand | All poses, reference-aligned |
| Protein-ligand cofolding | Sequence + ligand, no crystal structure | Protein sequence + SMILES | ML-predicted bound complex |
## Common workflow categories
### 1. Descriptors
A lightweight entry point for batch triage, SAR, or exploratory scripts.
```python
wf = rowan.submit_descriptors_workflow(
"CC(=O)Oc1ccccc1C(=O)O", # positional arg, accepts SMILES string
name="aspirin descriptors",
)
result = wf.result()
print(result.descriptors['MW']) # 180.16
print(result.descriptors['SLogP']) # 1.19
print(result.descriptors['TPSA']) # 59.44
print(result.data['descriptors'])
# {'MW': 180.16, 'SLogP': 1.19, 'TPSA': 59.44, 'nHBDon': 1.0, 'nHBAcc': 4.0, ...}
```
**Common descriptor keys:**
| Key | Description | Typical drug range |
|-----|-------------|-------------------|
| `MW` | Molecular weight (Da) | <500 (Lipinski) |
| `SLogP` | Calculated LogP (lipophilicity) | -2 to +5 |
| `TPSA` | Topological polar surface area (Ų) | <140 for oral bioavailability |
| `nHBDon` | H-bond donor count | ≤5 (Lipinski) |
| `nHBAcc` | H-bond acceptor count | ≤10 (Lipinski) |
| `nRot` | Rotatable bond count | <10 for oral drugs |
| `nRing` | Ring count | — |
| `nHeavyAtom` | Heavy atom count | — |
| `FilterItLogS` | Estimated aqueous solubility (LogS) | >-4 preferred |
| `Lipinski` | Lipinski Ro5 pass (1.0) or fail (0.0) | — |
The result contains hundreds of additional molecular descriptors (BCUT, GETAWAY, WHIM, etc.); access any via `result.descriptors['key']`.
### 2. Microscopic pKa
For protonation-state energetics and acid/base behavior of a specific structure.
Two methods are available:
| Method | Input | Speed | Covers | Use when |
|--------|-------|-------|--------|----------|
| `chemprop_nevolianis2025` | SMILES string | Fast | Deprotonation only (anionic conjugate bases) | Acidic groups only; quick screening |
| `starling` | SMILES string | Fast | Acid + base (full protonation/deprotonation) | Most drug-like molecules; preferred SMILES method |
| `aimnet2_wagen2024` (default) | 3D molecule object | Slower, higher accuracy | Acid + base | You already have a 3D structure (e.g. from conformer search) |
```python
# Fast path: SMILES input with full acid+base coverage (use starling method when available)
wf = rowan.submit_pka_workflow(
initial_molecule="c1ccccc1O", # phenol SMILES; param is initial_molecule, not initial_smiles
method="starling", # fast SMILES method, covers acid+base; chemprop_nevolianis2025 is deprotonation-only
name="phenol pKa",
)
result = wf.result()
print(result.strongest_acid) # 9.81 (pKa of the most acidic site)
print(result.conjugate_bases) # list of {pka, smiles, atom_index, ...} per deprotonatable site
```
### 3. MacropKa
For pH-dependent protonation behavior across a range.
```python
wf = rowan.submit_macropka_workflow(
initial_smiles="CN1CCN(CC1)C2=NC=NC3=CC=CC=C32", # imidazole
min_pH=0,
max_pH=14,
min_charge=-2, # default
max_charge=2, # default
compute_aqueous_solubility=True, # default
name="imidazole macropKa",
)
result = wf.result()
print(result.pka_values) # list of pKa values
print(result.logd_by_ph) # dict of {pH: logD}
print(result.aqueous_solubility_by_ph) # dict of {pH: solubility}
print(result.isoelectric_point) # isoelectric point
print(result.data)
# {'pKa_values': [...], 'logD_by_pH': {...}, 'aqueous_solubility_by_pH': {...}, ...}
```
### 4. Conformer search
For 3D ensemble generation when ensemble quality matters.
```python
wf = rowan.submit_conformer_search_workflow(
initial_molecule="CCOC(=O)N1CCC(CC1)Oc1ncnc2ccccc12",
num_conformers=50, # Optional: override default
name="conformer search",
)
result = wf.result()
print(result.conformer_energies) # [0.0, 1.2, 2.5, ...]
print(result.conformer_molecules) # List of 3D molecules
print(result.best_conformer) # Lowest-energy conformer
```
### 5. Tautomer search
For heterocycles and systems where tautomer state affects downstream modeling.
```python
wf = rowan.submit_tautomer_search_workflow(
initial_molecule="O=c1[nH]ccnc1", # or keto tautomer
name="imidazolone tautomers",
)
result = wf.result()
print(result.best_tautomer) # Most stable SMILES string
print(result.tautomers) # List of tautomeric SMILES
print(result.molecules) # List of molecule objects
```
### 6. Docking
For protein-ligand docking with optional pose refinement and conformer generation.
```python
# Upload protein once, reuse in multiple workflows
protein = rowan.upload_protein(
name="CDK2",
file_path="cdk2.pdb",
)
# Define binding pocket
pocket = {
"center": [10.5, 24.2, 31.8],
"size": [18.0, 18.0, 18.0],
}
# Submit docking
wf = rowan.submit_docking_workflow(
protein=protein,
pocket=pocket,
initial_molecule="CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1",
do_pose_refinement=True,
do_conformer_search=True,
name="lead docking",
)
result = wf.result()
print(result.scores) # Docking scores (kcal/mol)
print(result.best_pose) # Mol object with 3D coordinates
print(result.data) # Raw result dict
```
**Protein preparation tips:**
- PDB files should be reasonably clean (remove water/heteroatoms unless intended)
- Use the same protein object across a docking series for consistency
- If you have a PDB ID, use `rowan.create_protein_from_pdb_id()` instead
### 7. Analogue docking
For placing a compound series into a shared binding context.
```python
# Analogue series (e.g., SAR campaign)
analogues = [
"CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1", # reference
"CCNc1ncc(c(Nc2ccc(Cl)cc2)n1)-c1cccnc1", # chloro
"CCNc1ncc(c(Nc2ccc(OC)cc2)n1)-c1cccnc1", # methoxy
"CCNc1ncc(c(Nc2cc(C)c(F)cc2)n1)-c1cccnc1", # methyl, fluoro
]
wf = rowan.submit_analogue_docking_workflow(
analogues=analogues,
initial_molecule=analogues[0], # Reference ligand
protein=protein,
pocket=pocket,
name="SAR series docking",
)
result = wf.result()
print(result.analogue_scores) # List of scores for each analogue
print(result.best_poses) # List of poses
```
### 8. MSA generation
For multiple-sequence alignment (useful for downstream cofolding).
```python
wf = rowan.submit_msa_workflow(
initial_protein_sequences=[
"MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVP"
],
output_formats=["colabfold", "chai", "boltz"],
name="target MSA",
)
result = wf.result()
result.download_files() # Downloads alignments to disk
```
### 9. Protein-ligand cofolding
For AI-based bound-complex prediction when no crystal structure is available.
```python
wf = rowan.submit_protein_cofolding_workflow(
initial_protein_sequences=[
"MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVP"
],
initial_smiles_list=[
"CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1"
],
name="protein-ligand cofolding",
)
result = wf.result()
print(result.predictions) # List of predicted structures
print(result.messages) # Model metadata/warnings
predicted_structure = result.get_predicted_structure()
predicted_structure.write("predicted_complex.pdb")
```
## All supported workflow types
All workflows follow the same submit → wait → retrieve pattern and support webhooks and project/folder organization.
### Core molecular modeling workflows
| Workflow | Function | When to use |
|----------|----------|-------------|
| Descriptors | `submit_descriptors_workflow` | First-pass triage: MW, LogP, TPSA, HBA/HBD, Lipinski filter |
| pKa | `submit_pka_workflow` | Single ionizable group; need protonation thermodynamics |
| MacropKa | `submit_macropka_workflow` | Multi-ionizable drugs; pH-dependent charge/LogD/solubility |
| Conformer Search | `submit_conformer_search_workflow` | 3D ensemble for docking, MD, or SAR; known tautomer |
| Tautomer Search | `submit_tautomer_search_workflow` | Heterocycles, keto–enol; uncertain tautomeric form |
| Solubility | `submit_solubility_workflow` | Aqueous or solvent-specific solubility prediction |
| Membrane Permeability | `submit_membrane_permeability_workflow` | Caco-2, PAMPA, BBB, plasma permeability |
| ADMET | `submit_admet_workflow` | Broad drug-likeness and ADMET property sweep |
### Structure-based design workflows
| Workflow | Function | When to use |
|----------|----------|-------------|
| Docking | `submit_docking_workflow` | Single ligand, known binding pocket |
| Analogue Docking | `submit_analogue_docking_workflow` | SAR series (5–100+ compounds) in a shared pocket |
| Batch Docking | `submit_batch_docking_workflow` | Fast library screening; large compound sets |
| Protein MD | `submit_protein_md_workflow` | Long-timescale dynamics; conformational sampling |
| Pose Analysis MD | `submit_pose_analysis_md_workflow` | MD refinement of a docking pose |
| Protein Cofolding | `submit_protein_cofolding_workflow` | No crystal structure; AI-predicted bound complex |
| Protein Binder Design | `submit_protein_binder_design_workflow` | De novo binder generation against a protein target |
### Advanced computational chemistry
| Workflow | Function | When to use |
|----------|----------|-------------|
| Basic Calculation | `submit_basic_calculation_workflow` | QM/ML geometry optimization or single-point energy |
| Electronic Properties | `submit_electronic_properties_workflow` | Dipole, partial charges, HOMO-LUMO, ESP |
| BDE | `submit_bde_workflow` | Bond dissociation energies; metabolic soft-spot prediction |
| Redox Potential | `submit_redox_potential_workflow` | Oxidation/reduction potentials |
| Spin States | `submit_spin_states_workflow` | Spin-state energy ordering for organometallics/radicals |
| Strain | `submit_strain_workflow` | Conformational strain relative to global minimum |
| Scan | `submit_scan_workflow` | PES scans; torsion profiles |
| Multistage Optimization | `submit_multistage_opt_workflow` | Progressive optimization across levels of theory |
### Reaction chemistry
| Workflow | Function | When to use |
|----------|----------|-------------|
| Double-Ended TS Search | `submit_double_ended_ts_search_workflow` | Transition state between two known structures |
| IRC | `submit_irc_workflow` | Confirm TS connectivity; intrinsic reaction coordinate |
### Advanced properties
| Workflow | Function | When to use |
|----------|----------|-------------|
| NMR | `submit_nmr_workflow` | Predicted 1H/13C chemical shifts for structure verification |
| Ion Mobility | `submit_ion_mobility_workflow` | Collision cross-section (CCS) for MS method development |
| Hydrogen Bond Strength | `submit_hydrogen_bond_basicity_workflow` | H-bond donor/acceptor strength for formulation/solubility |
| Fukui | `submit_fukui_workflow` | Site reactivity indices for electrophilic/nucleophilic attack |
| Interaction Energy Decomposition | `submit_interaction_energy_decomposition_workflow` | Fragment-level interaction analysis |
### Binding free energy
| Workflow | Function | When to use |
|----------|----------|-------------|
| RBFE/FEP | `submit_relative_binding_free_energy_perturbation_workflow` | Relative ΔΔG for congeneric series |
| RBFE Graph | `submit_rbfe_graph_workflow` | Build and optimize an RBFE perturbation network |
### Sequence and structural biology
| Workflow | Function | When to use |
|----------|----------|-------------|
| MSA | `submit_msa_workflow` | Multiple sequence alignment for cofolding (ColabFold, Chai, Boltz) |
| Solvent-Dependent Conformers | `submit_solvent_dependent_conformers_workflow` | Solvation-aware conformer ensembles |
## Batch submission and retrieval
For libraries or analogue series, submit in a loop using the specific workflow function. The generic `rowan.batch_submit_workflow()` and `rowan.submit_workflow()` functions currently return 422 errors from the API — use the named functions (`submit_descriptors_workflow`, `submit_pka_workflow`, etc.) instead.
### Submit a batch
```python
smileses = ["CCO", "CC(=O)O", "c1ccccc1O"]
names = ["ethanol", "acetic acid", "phenol"]
workflows = [
rowan.submit_descriptors_workflow(smi, name=name)
for smi, name in zip(smileses, names)
]
print(f"Submitted {len(workflows)} workflows")
```
### Poll batch status
```python
statuses = rowan.batch_poll_status([wf.uuid for wf in workflows])
# Returns aggregate counts — not per-UUID:
# {'queued': 0, 'running': 1, 'complete': 2, 'failed': 0, 'total': 3, ...}
if statuses["complete"] == statuses["total"]:
print("All workflows done")
elif statuses["failed"] > 0:
print(f"{statuses['failed']} workflows failed")
```
### Retrieve and collect results
```python
results = []
for wf in workflows:
try:
result = wf.result()
results.append(result.data)
except rowan.WorkflowError as e:
print(f"Workflow {wf.uuid} failed: {e}")
# Optionally aggregate into DataFrame
import pandas as pd
df = pd.DataFrame(results)
```
### Non-blocking / fire-and-check pattern
For long-running workflows where you don't want to hold a process open, submit workflows, save their UUIDs, and check back later in a separate process.
**Session 1 — submit and save UUIDs:**
```python
import rowan, json
rowan.api_key = "..."
smileses = ["CCO", "CC(=O)O", "c1ccccc1O"]
workflows = [
rowan.submit_descriptors_workflow(smi, name=f"compound_{i}")
for i, smi in enumerate(smileses)
]
# Save UUIDs to disk (or a database)
uuids = [wf.uuid for wf in workflows]
with open("workflow_uuids.json", "w") as f:
json.dump(uuids, f)
print("Submitted. Check back later.")
```
**Session 2 — check status and collect results when ready:**
```python
import rowan, json
rowan.api_key = "..."
with open("workflow_uuids.json") as f:
uuids = json.load(f)
results = []
for uuid in uuids:
wf = rowan.retrieve_workflow(uuid)
if wf.done():
result = wf.result(wait=False)
results.append({"uuid": uuid, "data": result.data})
else:
print(f"{uuid}: still running ({wf.status})")
print(f"Collected {len(results)} completed results")
```
## Webhooks and asynchronous workflows
For long-running campaigns or when you don't want to keep a process alive, use webhooks to notify your backend when workflows complete.
### Setting up webhooks
Every workflow submission function accepts a `webhook_url` parameter:
```python
wf = rowan.submit_docking_workflow(
protein=protein,
pocket=pocket,
initial_molecule="CCO",
webhook_url="https://myserver.com/rowan_callback",
name="docking with webhook",
)
print(f"Workflow submitted. Result will be POSTed to webhook when complete.")
```
Webhook URLs can be passed to any specific workflow function (`submit_docking_workflow()`, `submit_pka_workflow()`, `submit_descriptors_workflow()`, etc.).
### Webhook authentication with secrets
Rowan supports webhook signature verification to ensure requests are authentic. You'll need to:
1. **Create or retrieve a webhook secret:**
```python
import rowan
# Create a new webhook secret
secret = rowan.create_webhook_secret()
print(f"Your webhook secret: {secret.secret}")
# Or retrieve an existing secret
secret = rowan.get_webhook_secret()
# Rotate your secret (invalidates old one, creates new)
new_secret = rowan.rotate_webhook_secret()
```
2. **Verify incoming webhook requests:**
```python
import rowan
import hmac
import json
def verify_webhook(request_body: bytes, signature: str, secret: str) -> bool:
"""Verify the HMAC-SHA256 signature of a webhook request."""
return rowan.verify_webhook_secret(request_body, signature, secret)
```
### Webhook payload and signature
When a workflow completes, Rowan POSTs a JSON payload to your webhook URL with the header:
```text
X-Rowan-Signature: <HMAC-SHA256 signature>
```
The request body contains the complete workflow result:
```json
{
"workflow_uuid": "wf_12345abc",
"workflow_type": "docking",
"workflow_name": "lead docking",
"status": "COMPLETED_OK",
"created_at": "2025-04-01T12:00:00Z",
"completed_at": "2025-04-01T12:15:30Z",
"data": {
"scores": [-8.2, -8.0, -7.9],
"best_pose": {...},
"metadata": {...}
}
}
```
### Example webhook handler with signature verification (FastAPI)
```python
from fastapi import FastAPI, Request, HTTPException
import rowan
import json
app = FastAPI()
_ws = rowan.get_webhook_secret() or rowan.create_webhook_secret()
webhook_secret = _ws.secret
@app.post("/rowan_callback")
async def handle_rowan_webhook(request: Request):
# Get request body and signature
body = await request.body()
signature = request.headers.get("X-Rowan-Signature")
if not signature:
raise HTTPException(status_code=400, detail="Missing X-Rowan-Signature header")
# Verify signature
if not rowan.verify_webhook_secret(body, signature, webhook_secret):
raise HTTPException(status_code=401, detail="Invalid webhook signature")
# Parse and process
payload = json.loads(body)
wf_uuid = payload["workflow_uuid"]
status = payload["status"]
if status == "COMPLETED_OK":
print(f"Workflow {wf_uuid} succeeded!")
result_data = payload["data"]
# Process result, update database, trigger next workflow, etc.
elif status == "FAILED":
print(f"Workflow {wf_uuid} failed!")
# Handle failure
# Respond quickly to prevent retries
return {"status": "received"}
```
### Webhook best practices
- **Always verify signatures** using `rowan.verify_webhook_secret()` to ensure requests are from Rowan
- **Respond quickly** (< 5 seconds); offload heavy processing to async tasks or background jobs
- **Implement idempotency**: workflows may retry; handle duplicate payloads gracefully using `workflow_uuid`
- **Log all events** for debugging and audit trails
- **Use for long campaigns**: webhooks shine with 50+ workflows; for small jobs, polling with `result()` is simpler
- **Rotate secrets regularly** using `rowan.rotate_webhook_secret()` for security
- **Return 2xx status** to confirm receipt; Rowan may retry on 5xx errors
## Protein utilities
### Upload proteins
```python
# From local PDB file
protein = rowan.upload_protein(
name="egfr_kinase_domain",
file_path="egfr_kinase.pdb",
)
# From PDB database
protein_from_pdb = rowan.create_protein_from_pdb_id(
name="CDK2 (1M17)",
code="1M17",
)
# Retrieve previously uploaded protein
protein = rowan.retrieve_protein("protein-uuid")
# List all proteins
my_proteins = rowan.list_proteins()
```
### Protein preparation guidance
- **File format**: PDB, mmCIF (Rowan auto-detects)
- **Water molecules**: Rowan usually keeps relevant water; remove bulk water beforehand if desired
- **Heteroatoms**: Cofactors, ions, and bound ligands are usually preserved; remove unwanted heteroatoms before upload
- **Multi-chain proteins**: Fully supported
- **Resolution**: Works with NMR structures, homology models, and cryo-EM; quality matters for downstream predictions
- **Validation**: Rowan validates PDB syntax; severely malformed files may be rejected
## End-to-end example: Lead optimization campaign
This example demonstrates a realistic workflow for optimizing a hit compound:
```python
import rowan
import pandas as pd
# 1. Create a project and folder for organization
project = rowan.create_project(name="CDK2 Hit Optimization")
rowan.set_project("CDK2 Hit Optimization")
folder = rowan.create_folder(name="round_1_tautomers_and_pka")
# 2. Load hit compound and analogues
hit = "CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1" # Known hit
analogues = [
"CCNc1ncc(c(Nc2ccccc2)n1)-c1cccnc1", # Remove F
"CCNc1ncc(c(Nc2ccc(Cl)cc2)n1)-c1cccnc1", # Cl instead of F
"CCC(C)Nc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1", # Propyl instead of ethyl
]
# 3. Determine best tautomers (just in case)
print("Searching tautomeric forms...")
taut_workflows = [
rowan.submit_tautomer_search_workflow(
smi, name=f"analog_{i}", folder=folder,
)
for i, smi in enumerate(analogues)
]
best_tautomers = []
for wf in taut_workflows:
result = wf.result()
best_tautomers.append(result.best_tautomer)
# 4. Predict pKa and basic properties for all analogues
print("Predicting pKa and properties...")
pka_workflows = [
rowan.submit_pka_workflow(
smi, method="chemprop_nevolianis2025", name=f"pka_{i}", folder=folder,
)
for i, smi in enumerate(best_tautomers)
]
descriptor_workflows = [
rowan.submit_descriptors_workflow(smi, name=f"desc_{i}", folder=folder)
for i, smi in enumerate(best_tautomers)
]
# 5. Collect results
pka_results = []
for wf in pka_workflows:
try:
result = wf.result()
pka_results.append({
"compound": wf.name,
"pka": result.strongest_acid, # pKa of the strongest acid site
"uuid": wf.uuid,
})
except rowan.WorkflowError as e:
print(f"pKa prediction failed for {wf.name}: {e}")
descriptor_results = []
for wf in descriptor_workflows:
try:
result = wf.result()
desc = result.descriptors
descriptor_results.append({
"compound": wf.name,
"mw": desc.get("MW"),
"logp": desc.get("SLogP"),
"hba": desc.get("nHBAcc"),
"hbd": desc.get("nHBDon"),
"uuid": wf.uuid,
})
except rowan.WorkflowError as e:
print(f"Descriptor calculation failed for {wf.name}: {e}")
# 6. Merge and summarize
df_pka = pd.DataFrame(pka_results)
df_desc = pd.DataFrame(descriptor_results)
df = df_pka.merge(df_desc, on="compound", how="outer")
print("\n=== Preliminary SAR ===")
print(df.to_string())
# 7. Select promising compound for docking
# compound names are "pka_0", "pka_1", etc. — extract index to look up SMILES
top_idx = int(df.loc[df["pka"].idxmin(), "compound"].split("_")[1])
top_smiles = best_tautomers[top_idx]
print(f"\nProceeding with docking: {top_smiles}")
# 8. Docking campaign
protein = rowan.create_protein_from_pdb_id(name="CDK2_1CKP", code="1CKP")
pocket = {"center": [10.5, 24.2, 31.8], "size": [18.0, 18.0, 18.0]}
docking_wf = rowan.submit_docking_workflow(
protein=protein,
pocket=pocket,
initial_molecule=top_smiles,
do_pose_refinement=True,
name=f"docking_{top_compound}",
)
dock_result = docking_wf.result()
print(f"\nDocking score: {dock_result.scores[0]:.2f} kcal/mol")
print(f"Best pose saved to: best_pose.pdb")
dock_result.best_pose.write("best_pose.pdb")
```
## Error handling and troubleshooting
### Common errors and solutions
```python
import rowan
# Error 1: Invalid SMILES
try:
wf = rowan.submit_descriptors_workflow("CCCC(CC", name="bad smiles") # Invalid
except rowan.ValidationError as e:
print(f"Invalid SMILES: {e}")
# Solution: Use RDKit to validate before submission
from rdkit import Chem
smi = Chem.MolToSmiles(Chem.MolFromSmiles(smi))
# Error 2: API key not set
try:
wf = rowan.submit_descriptors_workflow("CCO")
except rowan.AuthenticationError:
print("API key not found. Set ROWAN_API_KEY env var or call rowan.api_key = '...'")
# Error 3: Insufficient credits
try:
wf = rowan.submit_protein_cofolding_workflow(...)
except rowan.InsufficientCreditsError as e:
print(f"Not enough credits: {e}. Purchase more or reduce job size.")
# Error 4: Workflow failed (bad molecule, etc.)
try:
wf = rowan.submit_docking_workflow(...)
result = wf.result()
except rowan.WorkflowError as e:
print(f"Workflow failed: {e}")
# Check wf.status for details
print(f"Status: {wf.status}")
# Error 5: Workflow not yet done — poll manually
result = wf.result(wait=True, poll_interval=5) # waits and polls every 5s
# Or check status without blocking:
if not wf.done():
print("Workflow still running. Call wf.result() again later.")
```
### Debugging tips
- **Check workflow status**: `wf.status`, check `wf.done()`, or call `wf.get_status()`
- **Inspect raw result**: `result.data` instead of convenience properties
- **Re-run failed workflow**: Save UUIDs and retry with `rowan.retrieve_workflow(uuid)`
- **Validate molecules beforehand**: Use RDKit or Chemaxon before batch submission
## Recommended usage patterns
- **Prefer Rowan-native workflows** over low-level assembly when they exist
- **Use projects and folders** for any nontrivial campaign (>5 workflows)
- **Use `result()` to block until complete** (default: `wait=True, poll_interval=5`)
- **Use typed result properties first**, fall back to `.data` for unmapped fields
- **Use batch submission** for compound libraries or analogue series
- **Chain workflows** for multi-step chemistry campaigns:
- `pKa → macropKa → permeability` (ADME assessment)
- `tautomer search → docking → pose-analysis MD` (pose refinement)
- `MSA generation → protein-ligand cofolding` (AI structure prediction)
- **Use webhooks** for long-running campaigns (>50 workflows) or asynchronous pipelines
- **Use streaming** for interactive feedback on large conformer/docking searches
## Summary
Use Rowan when your workflow requires cloud execution for molecular-design tasks, especially when you want one unified API and consistent result handling across small-molecule modeling, proteins, docking, ADME prediction, and ML structure generation.
Rowan is a molecular-design workflow platform, not just a remote chemistry engine. It handles infrastructure scaling, result persistence, and multi-step pipeline orchestration so you can focus on science.
More from K-Dense-AI/scientific-agent-skills
- adaptyvHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.
- aeonThis skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
- anndataData structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
- arboretoInfer gene regulatory networks (GRNs) from gene expression data using scalable algorithms (GRNBoost2, GENIE3). Use when analyzing transcriptomics data (bulk RNA-seq, single-cell RNA-seq) to identify transcription factor-target gene relationships and regulatory interactions. Supports distributed computation for large-scale datasets.
- astropyComprehensive Python library for astronomy and astrophysics. This skill should be used when working with astronomical data including celestial coordinates, physical units, FITS files, cosmological calculations, time systems, tables, world coordinate systems (WCS), and astronomical data analysis. Use when tasks involve coordinate transformations, unit conversions, FITS file manipulation, cosmological distance calculations, time scale conversions, or astronomical data processing.
- autoskillObserve the user's screen via screenpipe, detect repeated research workflows, match them against existing scientific-agent-skills, and draft new skills (or composition recipes that chain existing ones) for the patterns not yet covered. Use when the user asks to analyze their recent work and propose skills based on what they actually do. Requires the screenpipe daemon (https://github.com/screenpipe/screenpipe) running locally on port 3030 — the skill has no other data source and will refuse to run if screenpipe is unreachable. All detection runs locally; only redacted cluster summaries reach the LLM.
- benchling-integrationBenchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
- bgpt-paper-searchSearch scientific papers and retrieve structured experimental data extracted from full-text studies via the BGPT MCP server. Returns 25+ fields per paper including methods, results, sample sizes, quality scores, and conclusions. Use for literature reviews, evidence synthesis, and finding experimental details not available in abstracts alone.
- biopythonComprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
- bioservicesUnified Python interface to 40+ bioinformatics services. Use when querying multiple databases (UniProt, KEGG, ChEMBL, Reactome) in a single workflow with consistent API. Best for cross-database analysis, ID mapping across services. For quick single-database lookups use gget; for sequence/file manipulation use biopython.