Skip to content

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 from GET /class/projects. Optional when exactly one project is open.
  • Route length: params.num_steps (one length) or params.lengths (a list; exactly one entry for predict_route), never both. Every length must be in the class's allowed set. name is optional.
  • callback_url, min_steps, max_steps, precedence, deep_search, exclude_smiles and starting_material are 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), lengths and grid_size. While the job runs, GET /jobs/{id} also returns queue_position and view_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)

codeHTTP
invalid_request400
invalid_smiles400
class_project_required400
class_key_scope403
addon_inactive403
key_inactive403
run_type_not_allowed403
lengths_not_allowed403
dangerous_target, compound_blocked403
student_not_enrolled404
project_closed, project_locked409
run_limit_reached422
class_busy, engine_busy429
submit_failed, models_unavailable500, 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.

MethodPath
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_retrosynthesis
  • get_job_status
  • get_job_references
  • get_job_convergence
  • validate_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.