Skip to content

API Design — ChemLib

Overview

The API is built on FastAPI with automatic OpenAPI documentation. All endpoints are under /api/ and return JSON. The UI interacts with the system exclusively through these endpoints.

Base URL: http://localhost:8000 - API docs: http://localhost:8000/docs (Swagger UI) - ReDoc: http://localhost:8000/redoc - UI pages: http://localhost:8000/ (Jinja2 templates)


Click diagram to zoom and pan:

API Route Groups Overview

Endpoint Summary

Method Path Description Service
Compounds
POST /api/compounds/ Create compound from SMILES CompoundService
POST /api/compounds/upload Import compounds from SDF file CompoundService
GET /api/compounds/ List compounds with property filters CompoundService
GET /api/compounds/{id} Get compound detail CompoundService
PUT /api/compounds/{id} Update compound name/metadata CompoundService
DELETE /api/compounds/{id} Delete compound CompoundService
POST /api/compounds/search/similar Similarity search CompoundService
POST /api/compounds/search/substructure Substructure search CompoundService
Fragments
POST /api/fragments/decompose/{compound_id} Decompose compound into fragments FragmentService
GET /api/fragments/ List fragments with filters FragmentService
GET /api/fragments/{id} Get fragment detail FragmentService
GET /api/fragments/{id}/compatible Get compatible fragments FragmentService
DELETE /api/fragments/{id} Delete fragment FragmentService
Assembly
POST /api/assembly/start Start new assembly from a fragment AssemblyService
POST /api/assembly/{id}/add-fragment Add fragment to assembly AssemblyService
POST /api/assembly/{id}/finalize Finalize and score molecule AssemblyService
GET /api/assembly/{id} Get assembly state and history AssemblyService
GET /api/assembly/ List all assembled molecules AssemblyService
DELETE /api/assembly/{id} Delete assembled molecule AssemblyService
Visualization
GET /api/viz/{parent_type}/{parent_id}/3d Get 3D MOL block for viewer ConformerService
GET /api/viz/{parent_type}/{parent_id}/conformers List conformers with energies ConformerService
POST /api/viz/{parent_type}/{parent_id}/generate-3d Generate 3D conformers ConformerService
GET /api/viz/{parent_type}/{parent_id}/2d Get 2D depiction (SVG) VizService
Scoring
GET /api/scoring/{parent_type}/{parent_id} Get full scoring report ScoringService
POST /api/scoring/evaluate Score a SMILES string (no DB) ScoringService

Detailed Endpoint Specifications

Compounds

POST /api/compounds/

Create a new compound from a SMILES string.

Request:

{
  "smiles": "c1ccc(CC(=O)O)cc1",
  "name": "Phenylacetic acid"
}

Response (201):

{
  "id": 1,
  "name": "Phenylacetic acid",
  "canonical_smiles": "O=C(O)Cc1ccccc1",
  "inchi_key": "WLJVXDMOQOGPHL-UHFFFAOYSA-N",
  "formula": "C8H8O2",
  "mw": 136.15,
  "logp": 1.46,
  "tpsa": 37.3,
  "hbd": 1,
  "hba": 2,
  "num_rotatable": 2,
  "num_rings": 1,
  "qed_score": 0.72,
  "sa_score": 1.84,
  "lipinski_pass": true,
  "created_at": "2026-03-21T12:00:00Z"
}

Error (422): Invalid SMILES

{
  "detail": "Invalid SMILES string: 'invalid'. RDKit could not parse this structure."
}

Error (409): Duplicate compound

{
  "detail": "Compound with SMILES 'O=C(O)Cc1ccccc1' already exists (id=1)."
}

POST /api/compounds/upload

Import multiple compounds from an SDF file.

Request: multipart/form-data with file field sdf_file

Response (201):

{
  "imported": 42,
  "skipped": 3,
  "errors": [
    {"index": 5, "error": "Invalid structure"},
    {"index": 12, "error": "Duplicate (InChIKey: ABC...)"},
    {"index": 30, "error": "RDKit sanitization failed"}
  ]
}

GET /api/compounds/

List compounds with property-based filters.

Query Parameters: - mw_min, mw_max — Molecular weight range - logp_min, logp_max — LogP range - hbd_max — Max H-bond donors - hba_max — Max H-bond acceptors - lipinski_pass — Boolean filter - search — Substring search on name - sort_by — Column name (default: created_at) - sort_orderasc or desc - limit — Page size (default: 50, max: 500) - offset — Pagination offset

Response (200):

{
  "total": 150,
  "items": [ /* CompoundResponse objects */ ],
  "limit": 50,
  "offset": 0
}

POST /api/compounds/search/similar

Find compounds similar to a query structure.

Request:

{
  "smiles": "c1ccc(O)cc1",
  "threshold": 0.7,
  "limit": 20
}

Response (200):

{
  "query_smiles": "Oc1ccccc1",
  "threshold": 0.7,
  "results": [
    {
      "compound": { /* CompoundResponse */ },
      "similarity": 0.85
    }
  ]
}

POST /api/compounds/search/substructure

Find compounds containing a substructure (SMARTS or SMILES pattern).

Request:

{
  "pattern": "[OH]c1ccccc1",
  "is_smarts": false,
  "limit": 50
}


Fragments

POST /api/fragments/decompose/{compound_id}

Run BRICS decomposition on a compound and store resulting fragments.

Response (201):

{
  "compound_id": 1,
  "compound_smiles": "O=C(O)Cc1ccccc1",
  "fragments": [
    {
      "id": 1,
      "smiles": "[3*]C(=O)O",
      "attachment_points": [3],
      "mw": 45.02,
      "num_heavy_atoms": 3
    },
    {
      "id": 2,
      "smiles": "[16*]c1ccccc1",
      "attachment_points": [16],
      "mw": 77.08,
      "num_heavy_atoms": 6
    }
  ]
}

GET /api/fragments/{id}/compatible

Get fragments with attachment points compatible with the given fragment (BRICS rules).

Response (200):

{
  "fragment_id": 1,
  "fragment_smiles": "[3*]C(=O)O",
  "attachment_points": [3],
  "compatible_fragments": [
    {
      "id": 5,
      "smiles": "[4*]CCCN",
      "attachment_points": [4],
      "compatible_at": [3, 4]
    }
  ]
}


Assembly

POST /api/assembly/start

Begin a new molecule assembly from an initial fragment.

Request:

{
  "fragment_id": 1,
  "name": "My candidate molecule"
}

Response (201):

{
  "id": 1,
  "name": "My candidate molecule",
  "current_smiles": "[3*]C(=O)O",
  "steps": [
    {
      "step_number": 1,
      "fragment_id": 1,
      "fragment_smiles": "[3*]C(=O)O",
      "intermediate_smiles": "[3*]C(=O)O"
    }
  ],
  "available_attachment_points": [3]
}

POST /api/assembly/{id}/add-fragment

Add a fragment to the growing molecule.

Request:

{
  "fragment_id": 5,
  "attachment_point_on_molecule": 3,
  "attachment_point_on_fragment": 4
}

Response (200):

{
  "id": 1,
  "current_smiles": "O=C(O)CCCN",
  "steps": [ /* updated step list */ ],
  "available_attachment_points": [],
  "is_valid": true,
  "validation_warnings": []
}

POST /api/assembly/{id}/finalize

Finalize assembly: compute all properties, scores, and optionally generate 3D conformers.

Request:

{
  "generate_3d": true,
  "num_conformers": 50
}

Response (200):

{
  "id": 1,
  "canonical_smiles": "O=C(O)CCCN",
  "properties": {
    "mw": 103.12,
    "logp": -0.67,
    "tpsa": 63.32,
    "hbd": 3,
    "hba": 3,
    "formula": "C4H9NO2"
  },
  "scoring": {
    "qed_score": 0.45,
    "sa_score": 2.1,
    "lipinski_pass": true,
    "lipinski_violations": [],
    "veber_pass": true,
    "pains_pass": true,
    "pains_alerts": []
  },
  "conformers_generated": 50,
  "lowest_energy_kcal": -42.3
}


Visualization

GET /api/viz/{parent_type}/{parent_id}/3d

Get the 3D structure for the molecule viewer.

Path Parameters: - parent_type: compound or assembled - parent_id: integer ID

Query Parameters: - conformer_id — Specific conformer (default: lowest energy) - formatsdf (default) or mol

Response (200):

{
  "mol_block": "...MOL block with 3D coordinates...",
  "energy": -42.3,
  "force_field": "MMFF94",
  "is_minimized": true,
  "conformer_id": 5
}

GET /api/viz/{parent_type}/{parent_id}/conformers

List all conformers with their energies for the conformer browser.

Response (200):

{
  "parent_type": "assembled",
  "parent_id": 1,
  "conformers": [
    {"id": 5, "energy": -42.3, "is_lowest": true, "rmsd": 0.0},
    {"id": 3, "energy": -41.8, "is_lowest": false, "rmsd": 1.2},
    {"id": 7, "energy": -40.1, "is_lowest": false, "rmsd": 2.5}
  ]
}

POST /api/viz/{parent_type}/{parent_id}/generate-3d

Trigger 3D conformer generation and energy minimization.

Request:

{
  "num_conformers": 50,
  "force_field": "MMFF94",
  "max_iterations": 500
}

Response (202): Accepted (long-running operation)

{
  "status": "generating",
  "parent_type": "assembled",
  "parent_id": 1,
  "num_conformers_requested": 50
}

GET /api/viz/{parent_type}/{parent_id}/2d

Get 2D depiction as SVG.

Response (200): SVG image (Content-Type: image/svg+xml)


Scoring

GET /api/scoring/{parent_type}/{parent_id}

Get full drug-likeness report for a stored molecule.

Response (200):

{
  "molecule": "O=C(O)CCCN",
  "lipinski": {
    "passes": true,
    "violations": 0,
    "mw": 103.12,
    "logp": -0.67,
    "hbd": 3,
    "hba": 3,
    "rules": {
      "mw_ok": true,
      "logp_ok": true,
      "hbd_ok": true,
      "hba_ok": true
    }
  },
  "veber": {
    "passes": true,
    "rotatable_bonds": 3,
    "tpsa": 63.32
  },
  "qed": 0.45,
  "sa_score": 2.1,
  "pains": {
    "passes": true,
    "alerts": []
  },
  "overall_recommendation": "GOOD_CANDIDATE"
}

POST /api/scoring/evaluate

Score a SMILES string without storing it. Useful for quick evaluation.

Request:

{
  "smiles": "c1ccc(CC(=O)NC2CCCCC2)cc1"
}

Response (200): Same format as above.


API Design Principles

  1. Consistent response format: All list endpoints return {total, items, limit, offset}. All single-item endpoints return the resource directly.

  2. HTTP status codes:

  3. 200 — Success
  4. 201 — Created
  5. 202 — Accepted (async operation started)
  6. 400 — Bad request (invalid assembly, incompatible fragments)
  7. 404 — Not found
  8. 409 — Conflict (duplicate)
  9. 422 — Validation error (invalid SMILES, bad parameters)
  10. 500 — Server error

  11. Pagination: All list endpoints support limit and offset query parameters.

  12. Filtering: Property-based filters use query parameters on list endpoints.

  13. Error responses: Always include a detail field with a human-readable message.

  14. No business logic in routes: Routes call service methods and return their results.


FastAPI App Structure

# chemlib/main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from chemlib.api import compounds, fragments, assembly, visualization, scoring

app = FastAPI(
    title="ChemLib",
    description="Chemical Compound Library for Fragment-Based Drug Design",
    version="0.1.0",
)

# API routers
app.include_router(compounds.router)
app.include_router(fragments.router)
app.include_router(assembly.router)
app.include_router(visualization.router)
app.include_router(scoring.router)

# Static files and templates for UI
app.mount("/static", StaticFiles(directory="chemlib/static"), name="static")
templates = Jinja2Templates(directory="chemlib/templates")

# UI page routes (serve HTML)
@app.get("/")
async def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Authentication & Rate Limiting

Out of scope for Phase 1. The API runs locally without authentication. Future phases may add: - API key authentication - JWT tokens for user sessions - Rate limiting via SlowAPI