API

API Documentation

Integrate computational toxicology into your drug discovery pipeline. Submit compounds, retrieve predictions, and download reports — all from your stack.

Download OpenAPI 3.1 spec (YAML)

Getting Started

1. Create an account

Sign up free — no credit card required. You get 3 lifetime screenings on the Free Trial tier.

2. Generate an API key

After logging in, go to the DashboardAPI Keys and click Generate New Key. Copy the key immediately — it won't be shown again. API-key generation is tier-gated (Enterprise, Pro, or Trial).

3. Base URL

https://toxscreen.ai/api/toxscreen

4. Authentication

ToxScreen exposes two authentication paths:

Programmatic (recommended for integrations)

Send your API key in the X-API-Key header against POST /v1/screen. Keys are prefixed tsk_:

X-API-Key: tsk_xxxxxxxxxxxx

Browser / session (JWT)

Endpoints under /jobs use the session JWT returned by POST /auth/login (the token field), sent as a Bearer token:

Authorization: Bearer <session-token>

Endpoints

POST /api/toxscreen/v1/screen

Submit a compound for toxicity screening with your tsk_ API key (X-API-Key header). Scoring is synchronous: predictions run inline on CPU (deterministic ADMET-AI, ~1–7 seconds per compound), so the response already carries the final status — there is no queue to poll.

Parameters

smiles — (string, required) Valid RDKit-parsable SMILES

name — (string, optional) Compound label for the report

panel — (string, optional) Panel: full (default), cardiac, or hepatic

project_id — (string, optional) Assign this job to a project on submission — must be a project owned by the authenticated user

Example request

curl -X POST https://toxscreen.ai/api/toxscreen/v1/screen \
  -H "X-API-Key: tsk_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "smiles": "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1",
    "name": "Compound-42",
    "panel": "full"
  }'

Example response

{
  "job_id": "ts_job_a1b2c3d4e5f6",
  "status": "completed",
  "queue_position": 0,
  "eta_minutes": 0,
  "compound_name": "Compound-42",
  "panel": "full"
}

The job is already completed on return (queue_position and eta_minutes are always 0). Fetch the rich per-target record from GET /v1/jobs/{job_id}/result. Browser/session clients can call POST /jobs with the same body and a Bearer session token for identical behaviour.

GET /api/toxscreen/v1/jobs/{id}

Retrieve the status and headline metrics for a job. Accepts X-API-Key header or session JWT (Bearer). For the full per-target record, call GET /v1/jobs/{id}/result.

Example request

curl https://toxscreen.ai/api/toxscreen/v1/jobs/ts_job_a1b2c3d4e5f6 \
  -H "X-API-Key: tsk_..."

Example response (completed)

{
  "id": "ts_job_a1b2c3d4e5f6",
  "compound_name": "Compound-42",
  "compound_smiles": "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1",
  "panel": "full",
  "status": "completed",
  "report_available": true,
  "error_message": null,
  "created_at": "2026-06-20T14:30:00+00:00",
  "completed_at": "2026-06-20T14:30:05+00:00",
  "cti": 0.31,
  "risk_level": "MODERATE",
  "per_target_bp": {
    "7CN1": 0.12,
    "6MA7": 0.45,
    "4WNT": 0.28,
    "1OG5": 0.09
  }
}

per_target_bp is a map keyed by PDB id (7CN1 = hERG, 6MA7 = CYP3A4, 4WNT = CYP2D6, 1OG5 = CYP2C9). For queued/running/failed jobs the metric fields are omitted. The summary metrics (cti, risk_level, per_target_bp) appear only when status is completed.

GET /api/toxscreen/v1/jobs/{id}/result

Return the structured JSON result record for a completed job — the full per-target ADMET payload including calibrated probabilities, Wilson 80%/95% confidence intervals, applicability-domain confidence/abstention flags, extended ADMET panel (CYP1A2, HIA, BBB, Ames, etc.), CTI and risk level. Accepts X-API-Key or Bearer JWT.

Example request

curl https://toxscreen.ai/api/toxscreen/v1/jobs/ts_job_a1b2c3d4e5f6/result \
  -H "X-API-Key: tsk_..."

GET /api/toxscreen/v1/jobs/{id}/report.{format}

Download a report for a completed job. Accepts X-API-Key or session JWT (Bearer). format options:

  • report.pdf — Standalone PDF with per-target risks, CIs, ADMET flags
  • report.xlsx — Excel workbook: Summary + Per-Target (with 80%/95% CIs) + ADMET + Extended ADMET
  • report.csv — CSV: one row per compound with all scores and extended ADMET endpoints

Example request (PDF)

curl -O https://toxscreen.ai/api/toxscreen/v1/jobs/ts_job_a1b2c3d4e5f6/report.pdf \
  -H "X-API-Key: tsk_..."

Session clients (browser) can still use /jobs/{id}/report (HTML) or /jobs/{id}/report.pdf with a Bearer token.

POST /api/toxscreen/v1/screen/batch

Submit up to 100 compounds as a JSON array for batch screening. Each compound is scored inline (CPU) and is already complete on return. Accepts X-API-Key or session JWT (Bearer). For CSV file upload, use the legacy POST /jobs/batch-upload (multipart form; Pro/Enterprise).

Parameters (JSON body)

compounds — (array, required) List of objects, each with smiles (required), name (optional), panel (optional). Max 100 items.

Example request

curl -X POST https://toxscreen.ai/api/toxscreen/v1/screen/batch \
  -H "X-API-Key: tsk_..." \
  -H "Content-Type: application/json" \
  -d '{"compounds": [{"smiles": "CCO", "name": "ethanol"}, {"smiles": "c1ccccc1", "name": "benzene"}]}'

Example response

{
  "submitted": 48,
  "failed": 1,
  "failed_jobs": [
    {"name": "lead-077", "smiles": "C1=CC=CC=C1..."}
  ],
  "invalid_rows": [
    {"row": 12, "smiles": "INVALID", "reason": "invalid SMILES"},
    {"row": 37, "reason": "empty SMILES"}
  ],
  "jobs": [
    {"name": "lead-001", "smiles": "CCO", "job_id": "ts_job_a1b2c3d4e5f6"}
  ],
  "credits_used": 48
}

invalid_rows lists rows rejected before scoring (bad/empty SMILES; row is 1-based incl. header). failed_jobs lists valid compounds whose inline scoring failed. jobs contains the created job IDs.

GET /api/toxscreen/queue

View current active-job counts. Public — no authentication required. Because scoring is synchronous (inline CPU), these counts are normally zero.

Example request

curl https://toxscreen.ai/api/toxscreen/queue

Example response

{
  "queued": 0,
  "running": 0,
  "total_active": 0,
  "eta_minutes": 0
}

POST /api/toxscreen/smiles/preview

Validate a SMILES string and preview its molecular properties (2D SVG, MW, LogP, QED, etc.) before submitting a screening job. Public — no authentication required; an RDKit-only call with no DB writes.

Example request

curl -X POST https://toxscreen.ai/api/toxscreen/smiles/preview \
  -H "Content-Type: application/json" \
  -d '{"smiles": "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1"}'

Example response

{
  "valid": true,
  "canonical": "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1",
  "svg": "<svg ...>...</svg>",
  "mw": 355.4,
  "logp": 3.2,
  "tpsa": 38.8,
  "hbd": 0,
  "hba": 3,
  "rot_bonds": 5,
  "qed": 0.812,
  "lipinski_violations": 0
}

An invalid SMILES returns {"valid": false, "error": "..."}.

GET /api/toxscreen/calibration

Retrieve current per-target calibration benchmarks (AUROC, class separation, positive/negative counts, pass/fail status) from the latest random-split validation run. Used by the landing-page validation strip and methodology page.

Example request

curl https://toxscreen.ai/api/toxscreen/calibration

Example response

{
  "calibration_date": "2026-06-10",
  "targets": [
    {"pdb": "7CN1", "name": "hERG",   "auc_roc": 0.69, "separation": null, "n_positive": 875, "n_negative": 925,  "status": "FAIL"},
    {"pdb": "6MA7", "name": "CYP3A4", "auc_roc": 0.95, "separation": null, "n_positive": 727, "n_negative": 1073, "status": "PASS"},
    {"pdb": "4WNT", "name": "CYP2D6", "auc_roc": 0.94, "separation": null, "n_positive": 381, "n_negative": 1419, "status": "PASS"},
    {"pdb": "1OG5", "name": "CYP2C9", "auc_roc": 0.94, "separation": null, "n_positive": 671, "n_negative": 1129, "status": "PASS"}
  ]
}

Public — no authentication required. These auc_roc values are in-distribution only (random split of the same TDC data ADMET-AI trained on). CYP3A4 / CYP2D6 / CYP2C9 reach AUROC 0.94–0.95 in-distribution, but an independent novel-chemistry test found CYP3A4 and CYP2C9 AUROC indistinguishable from chance (0.47 and 0.54); CYP2D6 was inconclusive. hERG is the only endpoint with a validated novel-chemistry number (0.69 random split, ~0.84 calibrated) — see the Methodology page for the full de-leak writeup. ToxScreen calibrates conservatively and abstains when a compound falls outside the model's applicability domain. The separation field is reported when available.

GET /api/toxscreen/v1/notifications/test

Fire a test notification to all configured channels (email and/or Slack). Requires auth. Returns whether the test was attempted and which channels were active. Useful for confirming notification settings before a long batch run.

Example request

curl https://toxscreen.ai/api/toxscreen/v1/notifications/test \
  -H "X-API-Key: tsk_..."

Example response

{
  "attempted": true,
  "email_channel": true,
  "slack_channel": false,
  "note": "test notification dispatched"
}

If attempted is false, no channels are configured — enable email or add a Slack webhook in the Settings panel first.

GET /api/toxscreen/billing/plans

List available subscription plans, pricing, and feature breakdowns.

Example request

curl https://toxscreen.ai/api/toxscreen/billing/plans

Example response

{
  "plans": [
    {"tier": "trial",      "name": "Free Trial",  "price_usd": 0,   "screenings": 3,  "features": ["4-target CTI safety panel", "Extended ADMET panel (15+ endpoints)", "HTML + PDF + Excel + JSON reports", "No credit card"]},
    {"tier": "starter",    "name": "Starter",     "price_usd": 49,  "screenings": 10, "features": ["4-target CTI safety panel", "Extended ADMET panel (15+ endpoints)", "HTML + PDF + Excel + JSON reports", "API key access"]},
    {"tier": "pro",        "name": "Pro",         "price_usd": 149, "screenings": 50, "features": ["4-target CTI safety panel", "Extended ADMET panel", "HTML + PDF + Excel + JSON reports", "API key + webhooks", "Batch CSV upload", "Projects & CRO worklist", "SAR trend tracking"]},
    {"tier": "enterprise", "name": "Enterprise",  "price_usd": 999, "screenings": -1, "features": ["Everything in Pro", "Unlimited screenings", "API key + webhooks", "CRO worklist export", "Dedicated support"]}
  ]
}

This endpoint is unauthenticated and used by the public pricing section.

Rate Limiting

Jobs — Tier-aware submission rate limit (sliding 60-second window)

TierMax submissions / min
Free / Starter / Trial5
Pro50
Enterprise200

Applies to POST /jobs and POST /v1/screen. On exceed, returns 429 Too Many Requests with a JSON detail message and a Retry-After: 60 header.

Auth — 10 authentication attempts per minute per IP

Applies to POST /auth/login and POST /auth/register. Excessive attempts return 429 Too Many Requests.

Exceeded the limit? Wait for the window to reset and reduce your request rate. Pro and Enterprise plans receive elevated rate limits. Contact info@toxscreen.ai for custom quotas.

Error Handling

All errors return a JSON body with a detail field describing the issue.

StatusCodeMeaning
400Bad RequestInvalid SMILES, missing required field, or malformed JSON.
401UnauthorizedMissing, invalid, or expired credentials. Ensure your X-API-Key (programmatic) or Authorization: Bearer session token is set correctly.
402Payment RequiredAccount has insufficient screenings remaining or requires a plan upgrade.
404Not FoundJob ID or endpoint does not exist. Verify the job ID is correct.
429Too Many RequestsRate limit exceeded. All 429 responses include a Retry-After: 60 header — wait that many seconds before retrying.
500Internal Server ErrorSomething went wrong on our side. Retry with exponential backoff. Contact support if it persists.

Python SDK

Use the requests library to integrate ToxScreen into your Python pipeline.

import requests

BASE = "https://toxscreen.ai/api/toxscreen"
PDB_NAMES = {"7CN1": "hERG", "6MA7": "CYP3A4", "4WNT": "CYP2D6", "1OG5": "CYP2C9"}

# 1. Submit a compound with your API key. Scoring is synchronous
#    (inline CPU, ~1-7s/compound), so the job is already "completed".
#    Optional: assign to a project_id from GET /v1/projects.
submit = requests.post(
    f"{BASE}/v1/screen",
    headers={"X-API-Key": "tsk_xxxxxxxxxxxx"},
    json={
        "smiles": "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1",
        "name": "lead-042",
        "panel": "full",
        # "project_id": "proj_xxxxxxxxxxxxxxxx",  # optional
    },
).json()
job_id = submit["job_id"]
print(f"{job_id}: {submit['status']}")   # e.g. ts_job_...: completed

# 2. Retrieve results with your session token (the `token` field from
#    POST /auth/login). The /jobs/* read endpoints use the session JWT.
api_auth = {"X-API-Key": "tsk_xxxxxxxxxxxx"}

result = requests.get(f"{BASE}/v1/jobs/{job_id}/result", headers=api_auth).json()
print(f"CTI: {result.get('cti')} — {result.get('risk_level')}")

job = requests.get(f"{BASE}/v1/jobs/{job_id}", headers=api_auth).json()
for pdb, bp in (job.get("per_target_bp") or {}).items():
    print(f"  {PDB_NAMES.get(pdb, pdb)}: bp={bp}")

# 3. Download reports.
report = requests.get(f"{BASE}/v1/jobs/{job_id}/report.pdf", headers=api_auth)
with open("toxscreen_report.pdf", "wb") as f:
    f.write(report.content)

# Flat CSV: one row per compound with per-target binary probs + 80% CI bands.
# Columns: compound_name, smiles, 7CN1_bp, 6MA7_bp, 4WNT_bp, 1OG5_bp,
#          7CN1_ci80_lo, ..., cti, risk_level, risk_color, should_abstain, abstain_reason
csv_text = requests.get(f"{BASE}/v1/jobs/{job_id}/report.csv", headers=api_auth).text
print(csv_text)

# 4. List your recent jobs (paginated, filterable by status/panel/project).
jobs = requests.get(
    f"{BASE}/v1/jobs",
    headers=api_auth,
    params={"status": "completed", "limit": 20},
).json()
for j in jobs["jobs"]:
    print(f"  {j['job_id']}: {j['compound_name']} — {j.get('status')}")

# 5. List your projects (returns {projects, total}).
projs = requests.get(f"{BASE}/v1/projects", headers=api_auth).json()
for p in projs["projects"]:
    print(f"  {p['id']}: {p['name']}")

# Get a single project by ID.
proj = requests.get(f"{BASE}/v1/projects/{project_id}", headers=api_auth).json()
print(f"Project: {proj['name']} — created {proj['created_at']}")

# 6. Batch submit (up to 100 compounds). Optionally assign all to a project.
batch = requests.post(
    f"{BASE}/v1/screen/batch",
    headers=api_auth,
    json={
        "compounds": [
            {"smiles": "CCO", "name": "ethanol"},
            {"smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", "name": "caffeine"},
        ],
        "panel": "full",
        # "project_id": project_id,  # optional — assign all jobs to a project
    },
).json()
print(f"Submitted {batch['submitted']} jobs, {len(batch['errors'])} errors")

# 7. Reassign a job to a different project (or remove from its current project).
#    The endpoint accepts either your API key or your session token.
updated = requests.put(
    f"{BASE}/v1/jobs/{job_id}/project",
    headers=api_auth,
    json={"project_id": project_id},  # pass null/None to unassign
).json()
print(f"Job {updated['job_id']} is now in project {updated['project_id']}")

# 8. Bulk re-sync completed results (optionally filter by project).
completed = requests.get(
    f"{BASE}/v1/results",
    headers=api_auth,
    params={
        "project_id": project_id,  # optional — omit for all projects
        "limit": 200,
    },
).json()
for r in completed["results"]:
    print(f"  {r['compound_name']}: CTI={r.get('cti')} risk={r.get('risk_level')}")

# 9. Download a CRO worklist for a project (CSV or Excel).
#    The worklist ranks completed compounds by priority and estimated CRO cost.
csv_text = requests.get(
    f"{BASE}/v1/projects/{project_id}/cro-worklist.csv",
    headers=api_auth,
    params={"budget_usd": 5000, "cost_per_compound": 150},
).text
print(csv_text[:200])  # compound_name,priority,estimated_cost,...

xlsx_bytes = requests.get(
    f"{BASE}/v1/projects/{project_id}/cro-worklist.xlsx",
    headers=api_auth,
    params={"budget_usd": 5000},
).content
with open("cro_worklist.xlsx", "wb") as f:
    f.write(xlsx_bytes)

# 10. SAR trend — per-target risk trajectory across all completed jobs in a project.
#     Returns compound_count, and per-target: trajectory list, best analog, direction.
trend = requests.get(
    f"{BASE}/v1/projects/{project_id}/trend",
    headers=api_auth,
).json()
for target, data in trend.get("targets", {}).items():
    print(f"  {target}: direction={data['direction']}, best={data['best']['compound_name']}")

All /v1/* endpoints accept either X-API-Key: tsk_... or Authorization: Bearer <session_token>. You can use whichever is most convenient for your pipeline.

Webhooks

Register an HTTPS endpoint to receive a signed job.completed POST whenever a screening finishes. All webhook endpoints use your API key or session token.

import hashlib, hmac, json, requests, time

BASE = "https://toxscreen.ai/api/toxscreen"
KEY  = "tsk_xxxxxxxxxxxx"
auth = {"X-API-Key": KEY}

# Register a webhook (must be HTTPS). Returns signing_secret ONCE.
r = requests.put(f"{BASE}/v1/webhooks", headers=auth, json={"url": "https://yourserver.example.com/hook"})
signing_secret = r.json()["signing_secret"]  # Store this — shown only once

# Get current webhook
wh = requests.get(f"{BASE}/v1/webhooks", headers=auth).json()
print(wh["url"], wh["active"])

# Send a test event
requests.post(f"{BASE}/v1/webhooks/test", headers=auth)

# Delete webhook
requests.delete(f"{BASE}/v1/webhooks", headers=auth)

# ── Verify incoming POST on your server ────────────────────────────────────
def verify_toxscreen_webhook(body: bytes, sig_header: str, secret: str) -> bool:
    """Verify X-ToxScreen-Signature header (Stripe-style HMAC-SHA256)."""
    try:
        ts_part, v1_part = sig_header.split(",")
        ts = int(ts_part.split("=")[1])
        expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
        received = v1_part.split("=")[1]
        return hmac.compare_digest(expected, received) and abs(time.time() - ts) < 300
    except Exception:
        return False

Payload

{
  "event": "job.completed",
  "job_id": "ts_job_a1b2c3d4e5f6",
  "compound_name": "lead-042",
  "risk_level": "MODERATE"
}

The X-ToxScreen-Signature header format: t=<unix_ts>,v1=<hmac_hex>. Reject requests where |time.time() - ts| > 300 to prevent replay attacks. Configure the webhook URL in Dashboard → Settings → Programmatic webhook.

CLI Quick Reference

The ToxScreen SDK ships a CLI entry point. Set TOXSCREEN_API_KEY=tsk_... and TOXSCREEN_BASE_URL=https://toxscreen.ai/api/toxscreen once, then:

# Verify your key
python toxscreen.py key-check

# Screen a single compound (synchronous — returns immediately)
python toxscreen.py screen "COc1ccc(CCN2CCC(CC2=O)c3ccc(F)cc3)cc1" --name lead-042

# Screen a CSV of compounds (columns: smiles, name)
python toxscreen.py screen --csv analogs.csv --out scored.csv --project my-project-id

# Download a PDF report
python toxscreen.py report ts_job_a1b2c3d4e5 --pdf --out report.pdf

# Download an Excel report
python toxscreen.py report ts_job_a1b2c3d4e5 --xlsx --out report.xlsx

# Download flat CSV (one row per compound: per-target bp + 80% CI bands)
python toxscreen.py report ts_job_a1b2c3d4e5 --csv --out flat.csv

# Audit trail for a job (regulatory / QA provenance)
python toxscreen.py audit-trail ts_job_a1b2c3d4e5 --csv --out trail.csv

# List recent completed jobs
python toxscreen.py list-jobs --status completed --limit 20

# Manage projects
python toxscreen.py project list
python toxscreen.py project create "NAMPT Campaign Q3"
python toxscreen.py project rename proj-uuid "NAMPT Campaign Q3 v2"

# CRO worklist for a project
python toxscreen.py cro-worklist proj-uuid --budget 5000 --csv --out worklist.csv
python toxscreen.py cro-worklist proj-uuid --budget 5000 --excel --out worklist.xlsx

# SAR trajectory
python toxscreen.py trend proj-uuid

# Webhooks
python toxscreen.py webhook set https://yourserver.example.com/hook
python toxscreen.py webhook get
python toxscreen.py webhook test

# Test notification channels (email + Slack)
python toxscreen.py notify-test

Download clients/toxscreen.py from the SDK repo. It's a single-file drop-in — no pip package required beyond requests.