Developer API
Build on SynovAI
Plan retrosynthesis routes programmatically. Submit a target molecule, poll for confidence-scored routes, and pull reaction metadata, literature references, and scores, all over a REST API and a hosted MCP server for agents.
1. Get an API key
API access is included on Professional (with usage limits) and Enterprise (configurable by contract) plans. Signed-in users on those plans can mint keys at Account → API keys. The key is shown once; store it somewhere safe. Authenticate every request with it as a bearer token:
Authorization: Bearer <your-api-key>
# or, equivalently:
X-API-Key: <your-api-key>Not on a paid plan yet? See plans or talk to us.
2. Submit and poll
The core workflow is submit-then-poll. POST /execute creates the job and returns an id; poll GET /jobs/{id} until the status is success or error. Set the SYNOVAI_API_KEY environment variable first.
Submit a target
curl https://platform.synovai.net/api/v1/execute \
-X POST \
-H "Authorization: Bearer $SYNOVAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "predict_route",
"params": { "smiles": "CC(=O)Nc1ccc(O)cc1", "num_steps": 4, "temperature": "Standard" }
}'Response:
{
"status": "accepted",
"job_id": "cltask_01h7x2k0...",
"poll_url": "/api/v1/jobs/cltask_01h7x2k0...",
"cost_usd": 5.0
}num_steps is the route length the search aims for (accepted range 2–8). It bounds what you may send, not what comes back — routes are not strictly capped by it and have been observed longer, so treat it as a guide rather than a guarantee. temperature controls how exploratory the search is: Well-Precedented → Standard → Exploratory → Novel. Run GET /tools for the live list of tools and per-call prices, or POST /forecast to quote a job before running it.
Every response that carries routes includes schema_version, currently "1.0". It versions the route-tree shape — path_dicts and the fields inside each node — not this API. A minor bump is additive and safe to ignore; a major bump changes or removes an existing field. If you parse the tree, pin on the major.
Poll for the result
curl https://platform.synovai.net/api/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $SYNOVAI_API_KEY"
# while "status" is "running", keep polling. Terminal states: "success" or "error".The same flow in Python:
import os, time, requests
API = "https://platform.synovai.net/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SYNOVAI_API_KEY']}"}
# 1. Submit a target
submit = requests.post(f"{API}/execute", headers=HEADERS, json={
"tool": "predict_route",
"params": {"smiles": "CC(=O)Nc1ccc(O)cc1", "num_steps": 4, "temperature": "Standard"},
}).json()
job_id = submit["job_id"]
# 2. Poll until terminal
while True:
job = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS).json()
if job["status"] in ("success", "error"):
break
time.sleep(5)
# 3. Use the routes
print(job["status"], job.get("result"))3. Errors, busy and polling
Every error body is { "status": "error", "error": "…", "code": "…" }. Show error to a person and branch on code, which is stable.
HTTP status codes: legacy by default, real codes on request
For an ordinary key, a refused POST /execute (bad input, unknown tool, pipeline not available) answers HTTP 200 with status: "error" in the body. Existing integrations rely on this, so it stays the default:
HTTP/1.1 200 OK
{ "status": "error", "error": "Missing required parameter: smiles", "code": "invalid_request" }Send the header SynovAI-Errors: http to get real status codes instead: 400 bad input, 402 spend cap, 403 not allowed, 404 unknown student, 409 project closed or locked, 422 run limit reached, 429 busy, 500/503 our fault. SynovAI-Errors: legacy forces the 200 body. Class chatbot keys get real codes by default. A missing or bad key (401), the spend cap (402), busy (429), a failed dispatch (500, SUBMIT_FAILED) and unavailable capacity (503, RESERVE_FAILED) use real codes in both modes.
curl https://platform.synovai.net/api/v1/execute \
-X POST \
-H "Authorization: Bearer $SYNOVAI_API_KEY" \
-H "SynovAI-Errors: http" \
-H "Content-Type: application/json" \
-d '{ "tool": "predict_route", "params": {} }'
HTTP/1.1 400 Bad Request
{ "status": "error", "error": "Missing required parameter: smiles", "code": "invalid_request" }429: busy or rate-limited
busy, class_busy and engine_busy mean the queue is full. Nothing was started or charged. Wait the number of seconds in the Retry-After header, then send the same request again, and back off further if it repeats. rate_limit_exceeded means the key sent more requests this minute than its limit (60 a minute unless your agreement says otherwise); read the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. Every call counts, polls included.
Polling
Poll GET /jobs/{id} at a steady interval (every 15–30 seconds suits a single run; SuperSampling sweeps take longer) until status is success or error. error covers every unsuccessful end (failed, canceled, no routes); the reason is in error.
SMILES in URLs
Send SMILES in the JSON body. SMILES use characters that mean something in a URL: # starts a fragment, + reads as a space in a query string, and /, \, %, =, @, [ and ] are reserved. If a SMILES ever goes in a query string or a link, percent-encode it (encodeURIComponent in JavaScript, urllib.parse.quote(smiles, safe='') in Python). Build JSON bodies with a JSON library so any backslash is escaped for you.
Class chatbot keys
A class with the class WhatSyn add-on can connect a course chatbot. The class owner creates a class key on the class's WhatSyn tab; it is shown once. A class key is not general API access. It can only call POST /execute (predict_route or super_sample, always with on_behalf_of), GET /jobs/{id} for jobs it submitted, and GET /class/projects. Anything else answers 403 class_key_scope. It stops working when the add-on is switched off or closes, or when its creator no longer manages the class.
curl https://platform.synovai.net/api/v1/execute \
-X POST \
-H "Authorization: Bearer $CLASS_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "super_sample",
"on_behalf_of": "student@university.edu",
"class_project": "cm9classproj001",
"params": { "smiles": "CC(=O)Nc1ccc(O)cc1", "lengths": [2, 4] }
}'on_behalf_of(required): the student's email. The student must already have joined the class in SynovAI with that email. The run belongs to the student, goes into their group's project, and costs the student nothing.class_project: the project id fromGET /class/projects. Optional when exactly one project is open.- Route length:
params.num_steps(one length) orparams.lengths(a list; exactly one entry forpredict_route), never both. Every length must be in the class's allowed set.nameis optional. callback_url,min_steps,max_steps,precedence,deep_search,exclude_smilesandstarting_materialare refused; poll the job instead.- The accepted response adds
view_url(where the student opens the result),project_url,queue_position(1 = next to start; null once it has started),lengthsandgrid_size. While the job runs,GET /jobs/{id}also returnsqueue_positionandview_url. GET /class/projects?on_behalf_of=…lists the open projects, the allowed run types and lengths, and how many runs the student has left in each project.
Class error codes (real HTTP codes by default)
| code | HTTP | Meaning |
|---|---|---|
| invalid_request | 400 | |
| invalid_smiles | 400 | |
| class_project_required | 400 | |
| class_key_scope | 403 | |
| addon_inactive | 403 | |
| key_inactive | 403 | |
| run_type_not_allowed | 403 | |
| lengths_not_allowed | 403 | |
| dangerous_target, compound_blocked | 403 | |
| student_not_enrolled | 404 | |
| project_closed, project_locked | 409 | |
| run_limit_reached | 422 | |
| class_busy, engine_busy | 429 | |
| submit_failed, models_unavailable | 500, 503 |
With SynovAI-Errors: legacy, every class refusal answers HTTP 200 except class_key_scope, addon_inactive and key_inactive from the key check (403) and the busy codes (429). Teachers: see Using WhatSyn in your class.
Endpoints
Base URL https://platform.synovai.net/api/v1. Full request/response schemas are in the interactive reference and the OpenAPI spec.
| Method | Path | Purpose |
|---|---|---|
| POST | /execute | |
| GET | /jobs/{id} | |
| GET | /jobs/{id}/references | |
| GET | /jobs/{id}/scored | |
| POST | /validate-smiles | |
| POST | /forecast | |
| GET | /tools | |
| POST | /patents/check | |
| GET | /usage | |
| POST | /webhooks | |
| GET | /class/projects |
MCP for agents
For agentic integrations we expose a hosted Model Context Protocol server at /api/v1/mcp (Streamable HTTP, JSON-RPC 2.0). Point any MCP-compatible client (Claude Desktop, Cursor, custom agents) at it with your API key as a bearer token, and the agent can drive retrosynthesis directly. Tools exposed:
submit_retrosynthesisget_job_statusget_job_referencesget_job_convergencevalidate_smiles
See the MCP setup guide for Claude Desktop, Cursor, and direct JSON-RPC config.
Ready to build?
Mint a key and make your first call in a couple of minutes.