API Reference

Documentation

Physon exposes a small, sharp REST API. Every endpoint requires an X-API-Key header. Get one for free at /signup.

Base URL

https://sharoncs888--physon-mvp-api.modal.run

Authentication

Every request needs an X-API-Key header. Keys are per-account, rotatable via the dashboard.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/molecular/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_YOUR_KEY_HERE" \
  -d '{"smiles": "CC(=O)Oc1ccccc1C(=O)O", "n_conformers": 20}'

Endpoints

POST/auth/signup

Get an API key

Sign up with an email address. Returns an existing key if the email already has one.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

# Response
{
  "email": "you@example.com",
  "api_key": "physon_...",
  "plan": "free",
  "daily_limit": 20
}
POST/molecular/prepare

Tautomer + charge state enumeration

Prepare a SMILES for docking-ready conformer generation. Returns physiological-pH protonation states + canonical tautomers. Feed each state to /molecular/generate.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/molecular/prepare \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_..." \
  -d '{"smiles": "CC(=O)Oc1ccccc1C(=O)O",
       "include_tautomers": true,
       "include_charges": true,
       "max_tautomers": 3}'

# Response
{
  "smiles": "CC(=O)Oc1ccccc1C(=O)O",
  "n_states": 4,
  "states": [
    {"smiles": "CC(=O)Oc1ccccc1C(=O)[O-]",
     "kind": "neutralized+protonated",
     "weight": 0.25},
    ...
  ],
  "model": "physon-prep"
}
POST/molecular/generate

Generate 3D conformers

Distribution-aware conformer generation. Auto-dispatches based on molecule type — catalog molecules go to the diffusion arch, macrocycles to CSD-augmented ETKDG.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/molecular/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_..." \
  -d '{"smiles": "CC(=O)Oc1ccccc1C(=O)O",
       "n_conformers": 20,
       "seed": 42}'

# Response fields
{
  "smiles": "...",
  "n_conformers": 20,
  "conformers": [{"xyz": [[x,y,z], ...],
                   "atoms": ["C", "H", ...]}],
  "inference_ms": 425,
  "model": "physon-molecular"
}

# model values you'll see:
# physon-molecular         — diffusion arch (catalog molecule)
# physon-molecular-ring    — diffusion + ring pucker diversity
# physon-macrocycle-hybrid — CSD-augmented ETKDG for macrocycles
# physon-molecular-dev     — MOCK fallback (unsupported SMILES)
POST/molecular/generate/batch

Batch conformer generation

Generate ensembles for up to 50 SMILES per request. Each SMILES counts as one quota unit; a 50-SMILES batch uses 1 quota unit.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/molecular/generate/batch \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_..." \
  -d '{"items": [
       {"smiles": "CC(=O)Oc1ccccc1C(=O)O", "n_conformers": 10},
       {"smiles": "CCCCCCC",                "n_conformers": 10}
     ]}'
POST/molecular/properties

Physicochemical properties + ensemble geometry

Lipinski Rule-of-5 (MW, log P, TPSA, HBD/HBA), plus ensemble radius-of-gyration and bounding box computed across a generated conformer set.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/molecular/properties \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_..." \
  -d '{"smiles": "CC(=O)Oc1ccccc1C(=O)O", "n_conformers": 10}'
GET/molecular/catalog

List supported molecules

Public catalog of SMILES with real (non-MOCK) inference. Any SMILES outside the catalog returns MOCK or the macrocycle-hybrid mode depending on structure.

curl https://sharoncs888--physon-mvp-api.modal.run/molecular/catalog
POST/3d/generate

3D voxel scene generation

Physon.3D — physics-valid 3D scene evolution. See /3d for available presets.

curl -X POST https://sharoncs888--physon-mvp-api.modal.run/3d/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: physon_..." \
  -d '{"preset_name": "bouncing_sphere", "horizon": 8}'

Rate limits

  • Per-key daily quota — see pricing
  • Per-IP: 30 inference calls / minute (all plans)
  • Per-IP: 5 signups / hour (abuse mitigation)
  • Batch endpoints count as 1 quota unit per request, regardless of item count

Error responses

401 — missing or invalid X-API-Key

400 — invalid SMILES, malformed request body, or unsupported preset

429 — rate limit or daily quota exceeded

500 — internal error; response includes request_id for support tickets

Docking pipeline integration

Physon's SDF output is docking-ready — MMFF energies + rank + provenance tags are already in the file. Drop straight into AutoDock Vina, Schrödinger Glide, or OpenEye FRED.

End-to-end: SMILES → Physon → Vina

Python example. Assumes you have AutoDock Vina installed (brew install autodock-vina or apt), a receptor prepared as receptor.pdbqt, and a grid box defined.

import httpx, subprocess, tempfile
from rdkit import Chem
from rdkit.Chem import AllChem

API = "https://sharoncs888--physon-mvp-api.modal.run"
KEY = "physon_YOUR_KEY_HERE"
SMILES = "CC(=O)Oc1ccccc1C(=O)O"   # aspirin

# 1. Ask Physon for a docking-ready SDF (ranked by MMFF energy)
r = httpx.post(
    f"{API}/molecular/generate/download",
    headers={"X-API-Key": KEY, "Content-Type": "application/json"},
    json={"smiles": SMILES, "n_conformers": 20, "format": "sdf"},
    timeout=60.0,
)
r.raise_for_status()
sdf_text = r.text

# 2. Convert SDF → PDBQT (Vina input format) via RDKit + Meeko or obabel
with tempfile.NamedTemporaryFile(suffix=".sdf", delete=False) as f:
    f.write(sdf_text.encode())
    sdf_path = f.name

pdbqt_path = sdf_path.replace(".sdf", ".pdbqt")
subprocess.run([
    "obabel", sdf_path, "-O", pdbqt_path, "--gen3d",
], check=True)

# 3. Dock
subprocess.run([
    "vina",
    "--receptor", "receptor.pdbqt",
    "--ligand", pdbqt_path,
    "--config", "vina_config.txt",   # grid box definition
    "--out", "docked.pdbqt",
    "--exhaustiveness", "8",
], check=True)

# 4. Parse Vina scores — best pose is first
with open("docked.pdbqt") as f:
    for line in f:
        if line.startswith("REMARK VINA RESULT:"):
            score = float(line.split()[3])
            print(f"Vina score: {score:.2f} kcal/mol")
            break

Schrödinger Glide (LigPrep input)

Physon SDF drops straight into LigPrep. Skip conformer generation in LigPrep — Physon already produced them.

# 1. Fetch the SDF as above → aspirin.sdf

# 2. Skip LigPrep's conformer step (Physon already generated + ranked):
$SCHRODINGER/ligprep -inp physon.sdf -omae aspirin_prepped.mae \
    -nc 0 \
    -bff 16 \
    -epik    # ionization + tautomerization

# 3. Glide docking (grid + config as usual):
$SCHRODINGER/glide docking.in -HOST localhost -NJOBS 4

Complete workflow — tautomer prep → conformer gen → rank → dock

This is the full enterprise flow. Prepare ionization states, generate conformer ensembles per state, rank by cluster, take medoids into docking.

import httpx

API = "https://sharoncs888--physon-mvp-api.modal.run"
KEY = "physon_YOUR_KEY_HERE"
SMILES = "CC(=O)Oc1ccccc1C(=O)O"

# Step 1 — enumerate protonation states + tautomers
prep = httpx.post(
    f"{API}/molecular/prepare",
    headers={"X-API-Key": KEY},
    json={"smiles": SMILES, "include_tautomers": True, "include_charges": True},
).json()
print(f"{prep['n_states']} docking-ready states")

# Step 2 — rank conformer families per state, keep medoids
all_medoids = []
for state in prep["states"]:
    ranked = httpx.post(
        f"{API}/molecular/rank",
        headers={"X-API-Key": KEY},
        json={"smiles": state["smiles"], "n_conformers": 20,
              "rmsd_threshold": 0.75},
    ).json()
    for cluster in ranked["clusters"]:
        all_medoids.append({
            "smiles": state["smiles"],
            "state_kind": state["kind"],
            "state_weight": state["weight"],
            "cluster_id": cluster["cluster_id"],
            "cluster_size": cluster["size"],
            "medoid_energy_kJ_mol": cluster["medoid_energy_kJ_mol"],
            "medoid_xyz": cluster["medoid_xyz"],
            "medoid_atoms": cluster["medoid_atoms"],
        })

print(f"{len(all_medoids)} unique conformers to dock")
# → feed all_medoids into your docking pipeline
# → weight scores by state_weight for population-corrected affinity

Enterprise plans include curated integrations for KNIME, Pipeline Pilot, and Databricks. Talk to us at /enterprise.

Python SDK — coming soon

First-party Python client with async support, retries, and typed responses. Meanwhile, httpx or requests work fine — every endpoint is plain JSON.