API
Integrate computational toxicology into your drug discovery pipeline. Submit compounds, retrieve predictions, and download reports — all from your stack.
Sign up free — no credit card required. You get 3 lifetime screenings on the Free Trial tier.
After logging in, go to the Dashboard → API 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).
https://toxscreen.ai/api/toxscreen
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>
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.
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
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"
}'
{
"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.
curl https://toxscreen.ai/api/toxscreen/v1/jobs/ts_job_a1b2c3d4e5f6 \
-H "X-API-Key: tsk_..."
{
"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.
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 flagsreport.xlsx — Excel workbook: Summary + Per-Target (with 80%/95% CIs) + ADMET + Extended ADMETreport.csv — CSV: one row per compound with all scores and extended ADMET endpointscurl -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).
compounds — (array, required) List of objects, each with smiles (required), name (optional), panel (optional). Max 100 items.
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"}]}'
{
"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.
curl https://toxscreen.ai/api/toxscreen/queue
{
"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.
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"}'
{
"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.
curl https://toxscreen.ai/api/toxscreen/calibration
{
"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.
curl https://toxscreen.ai/api/toxscreen/v1/notifications/test \
-H "X-API-Key: tsk_..."
{
"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.
curl https://toxscreen.ai/api/toxscreen/billing/plans
{
"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.
Jobs — Tier-aware submission rate limit (sliding 60-second window)
| Tier | Max submissions / min |
|---|---|
| Free / Starter / Trial | 5 |
| Pro | 50 |
| Enterprise | 200 |
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.
All errors return a JSON body with a detail field describing the issue.
| Status | Code | Meaning |
|---|---|---|
| 400 | Bad Request | Invalid SMILES, missing required field, or malformed JSON. |
| 401 | Unauthorized | Missing, invalid, or expired credentials. Ensure your X-API-Key (programmatic) or Authorization: Bearer session token is set correctly. |
| 402 | Payment Required | Account has insufficient screenings remaining or requires a plan upgrade. |
| 404 | Not Found | Job ID or endpoint does not exist. Verify the job ID is correct. |
| 429 | Too Many Requests | Rate limit exceeded. All 429 responses include a Retry-After: 60 header — wait that many seconds before retrying. |
| 500 | Internal Server Error | Something went wrong on our side. Retry with exponential backoff. Contact support if it persists. |
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.
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
{
"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.
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.