{
    "openapi": "3.0.3",
    "info": {
        "title": "SynovAI Public API",
        "version": "1.1.0",
        "description": "Programmatic access to SynovAI retrosynthesis pipelines. Submit molecules, run predictions asynchronously, retrieve route trees, references, convergence analysis, and per-end-user usage data. Authenticate with a per-customer API key created via /manage/api-keys.\n\nFor MCP-native clients, the same tools are exposed at POST /api/v1/mcp using the Streamable HTTP transport (JSON-RPC 2.0).",
        "x-paramus-tools": [
            {
                "id": "synovai_predict_route",
                "execution": "async",
                "endpoints": {
                    "submit": "/api/v1/execute",
                    "status": "/api/v1/jobs/{job_id}",
                    "quote": "/api/v1/forecast"
                },
                "endpoint": "/api/v1/execute",
                "quote_endpoint": "/api/v1/forecast",
                "cost_usd": 5,
                "cost_model": "quoted"
            },
            {
                "id": "synovai_predict_route_f",
                "execution": "async",
                "endpoints": {
                    "submit": "/api/v1/execute",
                    "status": "/api/v1/jobs/{job_id}",
                    "quote": "/api/v1/forecast"
                },
                "endpoint": "/api/v1/execute",
                "quote_endpoint": "/api/v1/forecast",
                "cost_usd": 5,
                "cost_model": "quoted",
                "deprecated": true,
                "deprecation_note": "Alias for synovai_predict_route. Coordinate with SynovAI before removing; existing Paramus integrations may still call this ID."
            },
            {
                "id": "synovai_validate_smiles",
                "execution": "realtime",
                "endpoints": {
                    "invoke": "/api/v1/validate-smiles"
                },
                "endpoint": "/api/v1/validate-smiles",
                "cost_usd": 0,
                "cost_model": "fixed"
            },
            {
                "id": "synovai_library_convergence",
                "execution": "realtime",
                "endpoints": {
                    "invoke": "/api/v1/library-convergence"
                },
                "endpoint": "/api/v1/library-convergence",
                "cost_usd": 0,
                "cost_model": "fixed"
            },
            {
                "id": "synovai_screen_library",
                "execution": "async",
                "endpoints": {
                    "submit": "/api/v2/batch",
                    "status": "/api/v2/batch/{job_id}",
                    "result": "/api/v2/batch/{job_id}/result",
                    "cancel": "/api/v2/batch/{job_id}/cancel"
                },
                "pricing": {
                    "cost_usd_max": 500,
                    "cost_usd_per_item": 5
                },
                "description": "Library screening workflow: fan-out predict_route over up to 100 target SMILES. Each child costs $5; total cost = $5 * len(targets)."
            }
        ]
    },
    "servers": [
        {
            "url": "/api/v1",
            "description": "API v1"
        }
    ],
    "security": [
        {
            "BearerAuth": []
        },
        {
            "ApiKeyAuth": []
        }
    ],
    "paths": {
        "/health": {
            "get": {
                "operationId": "getHealth",
                "summary": "Health check",
                "description": "Returns the health status of the API, database, and model server. No authentication required.",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Service is healthy",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HealthResponse"
                                },
                                "example": {
                                    "status": "ok",
                                    "database": "ok",
                                    "models": "ok",
                                    "timestamp": "2026-04-07T12:00:00.000Z"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service unhealthy (database unreachable)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                },
                                "example": {
                                    "status": "error",
                                    "error": "Database unreachable"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/tools": {
            "get": {
                "operationId": "listTools",
                "summary": "List available tools",
                "description": "Returns the list of available retrosynthesis tools with their parameters and current per-call cost.",
                "responses": {
                    "200": {
                        "description": "List of tools",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/Tool"
                                    }
                                },
                                "example": [
                                    {
                                        "name": "predict_route",
                                        "description": "Generate retrosynthesis routes using Pipeline F (canonical). Alias: predict_route_f (deprecated).",
                                        "cost_per_call_usd": 5,
                                        "params": {
                                            "smiles": {
                                                "type": "string",
                                                "required": true,
                                                "description": "Target molecule SMILES string"
                                            },
                                            "num_steps": {
                                                "type": "integer",
                                                "required": false,
                                                "default": null,
                                                "min": 2,
                                                "max": 8,
                                                "description": "Number of synthesis steps (null for auto)"
                                            },
                                            "starting_material": {
                                                "type": "string",
                                                "required": false,
                                                "default": null,
                                                "description": "Starting material SMILES (optional)"
                                            },
                                            "deep_search": {
                                                "type": "boolean",
                                                "required": false,
                                                "default": false,
                                                "description": "Enable deep search for more diverse routes"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/forecast": {
            "post": {
                "operationId": "forecastCost",
                "summary": "Forecast job cost",
                "description": "Returns the current cost in USD for a single call to the specified tool. Does not execute the tool.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ForecastRequest"
                            },
                            "example": {
                                "tool": "predict_route"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Cost forecast",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ForecastResponse"
                                },
                                "example": {
                                    "cost_usd": 5
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Unknown tool",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                },
                                "example": {
                                    "status": "error",
                                    "error": "Unknown tool: invalid_tool. Use GET /api/v1/tools to list available tools."
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "500": {
                        "description": "Internal error (worker not found)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                },
                                "example": {
                                    "status": "error",
                                    "error": "Worker not found"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/execute": {
            "post": {
                "operationId": "executeJob",
                "summary": "Execute a retrosynthesis job",
                "description": "Submits an asynchronous retrosynthesis job. Returns a job ID that can be polled via GET /jobs/{jobId}. Optionally provide a callback_url to receive a webhook POST when the job completes. Note: validation errors return HTTP 200 with status 'error' in the body.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ExecuteRequest"
                            },
                            "example": {
                                "tool": "predict_route",
                                "callback_url": "https://api.paramus.ai/webhooks/synovai",
                                "params": {
                                    "smiles": "CC(=O)Oc1ccccc1C(=O)O",
                                    "num_steps": 3,
                                    "temperature": "Standard"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Job accepted or validation error. Check the `status` field to distinguish success from error.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ExecuteAcceptedResponse"
                                        },
                                        {
                                            "$ref": "#/components/schemas/ErrorResponse"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "status"
                                    }
                                },
                                "examples": {
                                    "accepted": {
                                        "summary": "Job accepted",
                                        "value": {
                                            "status": "accepted",
                                            "job_id": "cm9abc123def456",
                                            "poll_url": "/api/v1/jobs/cm9abc123def456",
                                            "cost_usd": 5
                                        }
                                    },
                                    "error": {
                                        "summary": "Validation error",
                                        "value": {
                                            "status": "error",
                                            "error": "Missing required parameter: smiles"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/conditions": {
            "post": {
                "operationId": "predictConditions",
                "summary": "Predict reaction conditions (HowSyn / MOSAIC)",
                "description": "Predict experimental conditions (reagents, solvent, temperature, time, estimated yield) for a single reaction using the HowSyn / MOSAIC model. Synchronous and cache-first: a cache hit returns instantly and is free; a miss runs inference (30 s - 3 min) and is billed to the API key owner at the per-call price from GET /api/v1/tools. Provide either reaction_smiles OR (reactants + product). Requires a plan that includes the condition_prediction feature.",
                "security": [
                    {
                        "BearerAuth": []
                    },
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "reaction_smiles": {
                                        "type": "string",
                                        "description": "Full reaction SMILES (reactants>>product)."
                                    },
                                    "reactants": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "Reactant SMILES (with product)."
                                    },
                                    "product": {
                                        "type": "string",
                                        "description": "Product SMILES (with reactants)."
                                    },
                                    "mode": {
                                        "type": "string",
                                        "enum": ["conditions", "yield", "all"],
                                        "default": "conditions"
                                    },
                                    "end_user_id": {
                                        "type": "string",
                                        "description": "Opaque downstream-user id for reseller re-billing (or send X-End-User-Id header)."
                                    }
                                }
                            },
                            "example": {
                                "reaction_smiles": "Brc1ccccc1.Nc1ccccc1>>c1ccc(Nc2ccccc2)cc1",
                                "mode": "conditions"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Condition prediction (cached or freshly inferred).",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "cached": false,
                                    "cost_usd": 2.5,
                                    "end_user_id": null,
                                    "model_version": "mosaic-v1-2026-05",
                                    "response": {
                                        "request_id": "abc-123",
                                        "model_version": "mosaic-v1-2026-05",
                                        "elapsed_ms": 41200,
                                        "warnings": [],
                                        "experts_consulted": [
                                            {
                                                "expert_id": 883,
                                                "centroid_distance": 0.85
                                            }
                                        ],
                                        "predictions": [
                                            {
                                                "rank": 1,
                                                "structured": {
                                                    "solvent": {
                                                        "name": "1,4-dioxane"
                                                    },
                                                    "temperature_c": 100,
                                                    "time_h": 16,
                                                    "yield_bin": "60-70%"
                                                }
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Validation failed (missing/invalid reaction input).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "402": {
                        "description": "Spend cap exceeded for the API key or end user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Plan does not include condition_prediction.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "MOSAIC backend error.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/jobs/{jobId}": {
            "get": {
                "operationId": "getJobStatus",
                "summary": "Get job status and results",
                "description": "Poll this endpoint to check job progress. Returns status 'running' while in progress, 'success' with results when complete, or 'error' if the job failed.",
                "parameters": [
                    {
                        "name": "jobId",
                        "in": "path",
                        "required": true,
                        "description": "The job ID returned from POST /execute",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Job status (running or success)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/JobRunningResponse"
                                        },
                                        {
                                            "$ref": "#/components/schemas/JobSuccessResponse"
                                        },
                                        {
                                            "$ref": "#/components/schemas/JobErrorResponse"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "status"
                                    }
                                },
                                "examples": {
                                    "running": {
                                        "summary": "Job still running",
                                        "value": {
                                            "status": "running",
                                            "job_id": "cm9abc123def456",
                                            "created_at": "2026-04-07T12:00:00.000Z"
                                        }
                                    },
                                    "success": {
                                        "summary": "Job completed",
                                        "value": {
                                            "status": "success",
                                            "job_id": "cm9abc123def456",
                                            "result": {},
                                            "cost_usd": 5,
                                            "created_at": "2026-04-07T12:00:00.000Z",
                                            "completed_at": "2026-04-07T12:01:30.000Z"
                                        }
                                    },
                                    "error": {
                                        "summary": "Job failed",
                                        "value": {
                                            "status": "error",
                                            "job_id": "cm9abc123def456",
                                            "error": "Job failed",
                                            "created_at": "2026-04-07T12:00:00.000Z"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Job not found",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                },
                                "example": {
                                    "status": "error",
                                    "error": "Job not found"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/jobs/{jobId}/references": {
            "get": {
                "operationId": "getJobReferences",
                "summary": "Get literature references for a completed job",
                "description": "Walks the result tree, collects every internal_reference_index from each non-leaf node, and returns the corresponding patents / experimental procedures / yields grouped by route → step. Use ?route=N to narrow to a single route.",
                "parameters": [
                    {
                        "name": "jobId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The completed job ID."
                    },
                    {
                        "name": "route",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        },
                        "description": "1-based route index. Omit for all routes."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Reference catalog",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "job_id": "cm9abc123",
                                        "routes": [
                                            {
                                                "route_index": 1,
                                                "steps": [
                                                    {
                                                        "step_index": 1,
                                                        "product_smiles": "CCO",
                                                        "reactant_smiles": ["CC=O"],
                                                        "reaction_type": "Hydrogenation",
                                                        "references": [
                                                            {
                                                                "index": 12345,
                                                                "reaction": "[CH3:1][CHO:2]>>[CH3:1][CH2:2][OH]",
                                                                "reaction_name": "Aldehyde reduction",
                                                                "yield": [88],
                                                                "reference": "WO2019/123456",
                                                                "paragraph_text": "To a solution of …"
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Job not found"
                    },
                    "409": {
                        "description": "Job not yet complete"
                    },
                    "502": {
                        "description": "Reference service unavailable"
                    }
                }
            }
        },
        "/jobs/{jobId}/convergence": {
            "get": {
                "operationId": "getJobConvergence",
                "summary": "Convergence analysis around a focal molecule",
                "description": "For a completed job, returns precursors (what makes the focal SMILES), products (what it makes), and partners (co-occurring at the same step) across every route. Useful for surfacing 'N of M routes use this intermediate'.",
                "parameters": [
                    {
                        "name": "jobId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "smiles",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Focal molecule SMILES."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Convergence analysis",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "job_id": "cm9abc123",
                                        "focal_smiles": "OCC(=O)c1ccccc1",
                                        "total_routes": 32,
                                        "routes_containing": 31,
                                        "precursors": [],
                                        "products": [],
                                        "partners": []
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "smiles query param missing"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Job not found"
                    },
                    "409": {
                        "description": "Job not yet complete"
                    }
                }
            }
        },
        "/jobs/{jobId}/scored": {
            "get": {
                "operationId": "getJobScoredRoutes",
                "summary": "Rank routes under a scoring profile",
                "description": "Re-ranks a completed job's routes using the requested scoring profile. Built-in profile ids: discovery, medicinal, process, computational, materials, green, educational. Omit `profile` to use platform defaults. Returns each route with its composite feasibility score (0-10), per-dimension scores, confidence, and rationale.",
                "parameters": [
                    {
                        "in": "path",
                        "name": "jobId",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "in": "query",
                        "name": "profile",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "discovery",
                                "medicinal",
                                "process",
                                "computational",
                                "materials",
                                "green",
                                "educational"
                            ]
                        },
                        "description": "Built-in scoring profile id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Ranked routes with score breakdown",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "job_id": "cm9abc123",
                                        "profile": {
                                            "id": "process",
                                            "name": "Process"
                                        },
                                        "total_routes": 32,
                                        "ranked": [
                                            {
                                                "originalRouteIndex": 7,
                                                "composite": 8.4,
                                                "compositeLabel": "good",
                                                "dimensions": [
                                                    {
                                                        "id": "route-confidence",
                                                        "name": "Route Confidence",
                                                        "score": 8.5,
                                                        "confidence": "high",
                                                        "details": "3 steps, all literature-precedented",
                                                        "rationale": null
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Unknown profile id"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Job not found"
                    },
                    "409": {
                        "description": "Job not yet complete"
                    }
                }
            }
        },
        "/library-convergence": {
            "post": {
                "operationId": "libraryConvergence",
                "summary": "Find shared intermediates across many jobs",
                "description": "Given a list of completed job ids, walks each result tree and surfaces intermediates that recur most across the library. Useful for batch / library-synthesis workflows where the question is 'which common precursor would let me reach all of these targets in fewer steps'. The same chemistry function backs the in-app library convergence view.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["job_ids"],
                                "properties": {
                                    "job_ids": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 100,
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "min_molecule_fraction": {
                                        "type": "number",
                                        "minimum": 0,
                                        "maximum": 1,
                                        "description": "Minimum fraction of library molecules an intermediate must appear in to be included. Default 0.1."
                                    }
                                }
                            },
                            "example": {
                                "job_ids": ["cm9abc123", "cm9def456", "cm9ghi789"],
                                "min_molecule_fraction": 0.1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Library convergence result",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "molecule_count": 3,
                                        "total_routes": 28,
                                        "intermediates": [
                                            {
                                                "smiles": "Oc1ccc(Br)cc1",
                                                "molecule_count": 3,
                                                "molecule_fraction": 1,
                                                "route_count": 14,
                                                "avg_depth": 2.1,
                                                "entries": [
                                                    {
                                                        "target_smiles": "CC(=O)Oc1ccc(C(=O)O)cc1",
                                                        "task_id": "cm9abc123",
                                                        "route_count": 5,
                                                        "route_fraction": 0.5
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Invalid body"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "One or more jobs not found or not owned by the calling key"
                    },
                    "409": {
                        "description": "One or more jobs not yet complete"
                    }
                }
            }
        },
        "/validate-smiles": {
            "post": {
                "operationId": "validateSmiles",
                "summary": "Canonicalize and validate a SMILES string",
                "description": "Wraps the same RDKit-backed validator used by the in-app submission form. Returns the canonical form so integrators can replace ad-hoc PubChem lookups with us.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["smiles"],
                                "properties": {
                                    "smiles": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 2048
                                    }
                                }
                            },
                            "example": {
                                "smiles": "CCO"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Validation result",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "input": "CCO",
                                        "valid": true,
                                        "canonical_smiles": "CCO",
                                        "message": "ok"
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Missing or invalid smiles input"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/usage": {
            "get": {
                "operationId": "getUsage",
                "summary": "Aggregate API usage and per-end-user spend",
                "description": "Returns total queries + cost plus a per-end-user breakdown for the calling key's owner. Used by API customers (e.g. resellers) to re-bill their own sub-tenants. Tag submissions with X-End-User-Id (REST) or end_user_id (MCP / body) so the breakdown is non-empty.",
                "parameters": [
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Inclusive lower bound. Defaults to start of current month UTC."
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Exclusive upper bound. Defaults to now."
                    },
                    {
                        "name": "end_user_id",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Filter to a single sub-tenant."
                    },
                    {
                        "name": "include_detail",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": ["1"]
                        },
                        "description": "Set to 1 to include the last 100 raw records inline."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Usage report",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "from": "2026-04-01T00:00:00.000Z",
                                        "to": null,
                                        "total": {
                                            "queries": 47,
                                            "cost_cents": 2350,
                                            "cost_usd": 23.5
                                        },
                                        "by_end_user": [
                                            {
                                                "end_user_id": "alice@pfizer.example",
                                                "queries": 32,
                                                "cost_cents": 1600,
                                                "cost_usd": 16
                                            },
                                            {
                                                "end_user_id": "bob@pfizer.example",
                                                "queries": 15,
                                                "cost_cents": 750,
                                                "cost_usd": 7.5
                                            }
                                        ]
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Invalid date in `from` or `to`"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/mcp": {
            "post": {
                "operationId": "mcpJsonRpc",
                "summary": "MCP server endpoint (JSON-RPC 2.0, Streamable HTTP)",
                "description": "Model Context Protocol entry point for AI-native clients (Claude Desktop, Cursor, MCP-aware agents). The same retrosynthesis tools the REST API exposes are available as MCP tools without hand-rolling REST scaffolding. Auth is the same Bearer token; tool catalog: submit_retrosynthesis, get_job_status, get_job_references, get_job_convergence, validate_smiles. Pass end_user_id as a tool argument for usage attribution.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["jsonrpc", "method"],
                                "properties": {
                                    "jsonrpc": {
                                        "type": "string",
                                        "enum": ["2.0"]
                                    },
                                    "id": {
                                        "oneOf": [
                                            {
                                                "type": "string"
                                            },
                                            {
                                                "type": "number"
                                            },
                                            {
                                                "type": "null"
                                            }
                                        ]
                                    },
                                    "method": {
                                        "type": "string",
                                        "enum": [
                                            "initialize",
                                            "tools/list",
                                            "tools/call",
                                            "notifications/initialized",
                                            "prompts/list",
                                            "resources/list"
                                        ]
                                    },
                                    "params": {
                                        "type": "object"
                                    }
                                }
                            },
                            "example": {
                                "jsonrpc": "2.0",
                                "id": 1,
                                "method": "tools/call",
                                "params": {
                                    "name": "submit_retrosynthesis",
                                    "arguments": {
                                        "smiles": "CCO",
                                        "num_steps": 2,
                                        "end_user_id": "alice@pfizer.example"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "JSON-RPC response (or batched array when input is a batch)"
                    },
                    "204": {
                        "description": "Notification accepted (no body)"
                    },
                    "400": {
                        "description": "Invalid JSON-RPC envelope"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            },
            "get": {
                "operationId": "mcpServerInfo",
                "summary": "MCP server metadata probe",
                "description": "Some MCP clients GET the endpoint first to confirm transport support before issuing JSON-RPC.",
                "responses": {
                    "200": {
                        "description": "Server info",
                        "content": {
                            "application/json": {
                                "example": {
                                    "name": "synovai",
                                    "version": "1.0.0",
                                    "description": "SynovAI retrosynthesis and route-analysis tools.",
                                    "protocolVersion": "2025-06-18",
                                    "transport": "streamable-http",
                                    "endpoint": "/api/v1/mcp"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/patents/check": {
            "post": {
                "operationId": "checkPatentOverlap",
                "summary": "Screen SMILES for patent overlap",
                "description": "Submit up to 1000 SMILES strings and receive per-molecule patent-landscape findings (known patents, claim type, freedom-to-operate signal). Useful for IP-risk screening before committing to a route.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["smiles"],
                                "properties": {
                                    "smiles": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 1000,
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "SMILES strings to check (max 1000 per request)."
                                    }
                                }
                            },
                            "example": {
                                "smiles": ["CC(=O)Oc1ccccc1C(=O)O", "CCO"]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Patent overlap result"
                    },
                    "400": {
                        "description": "Invalid body or > 1000 SMILES"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/webhooks": {
            "get": {
                "operationId": "listWebhooks",
                "summary": "List registered webhook endpoints",
                "description": "Returns webhook endpoints owned by the calling API key. Secrets are NOT included; they are returned only once at creation.",
                "responses": {
                    "200": {
                        "description": "List of endpoints",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "endpoints": [
                                            {
                                                "id": "wh_abc123",
                                                "url": "https://api.paramus.ai/webhooks/synovai",
                                                "events": ["task.completed", "task.failed"],
                                                "description": "Production sink",
                                                "isActive": true,
                                                "createdAt": "2026-05-14T12:00:00.000Z"
                                            }
                                        ],
                                        "supportedEvents": ["task.completed", "task.failed"]
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            },
            "post": {
                "operationId": "createWebhook",
                "summary": "Register a webhook endpoint",
                "description": "Subscribes the calling API key's owner to terminal-state job events. Returns the signing secret ONCE in the response; you must store it (used for HMAC verification on each delivery). After ~10 consecutive 5xx responses an endpoint is auto-deactivated and must be re-created or reactivated.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["url", "events"],
                                "properties": {
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "description": "Absolute URL the platform will POST to. Must be in ALLOWED_CALLBACK_DOMAINS."
                                    },
                                    "events": {
                                        "type": "array",
                                        "minItems": 1,
                                        "items": {
                                            "type": "string",
                                            "enum": ["task.completed", "task.failed"]
                                        }
                                    },
                                    "description": {
                                        "type": "string",
                                        "nullable": true
                                    }
                                }
                            },
                            "example": {
                                "url": "https://api.paramus.ai/webhooks/synovai",
                                "events": ["task.completed", "task.failed"],
                                "description": "Production sink"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Webhook created. `data.secret` is returned ONCE.",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "ok",
                                    "data": {
                                        "id": "wh_abc123",
                                        "url": "https://api.paramus.ai/webhooks/synovai",
                                        "events": ["task.completed", "task.failed"],
                                        "description": "Production sink",
                                        "isActive": true,
                                        "createdAt": "2026-05-14T12:00:00.000Z",
                                        "secret": "whsec_<store-this-now>"
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Invalid body, unsupported event, or URL domain not allowed"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    }
                }
            }
        },
        "/webhooks/{id}": {
            "delete": {
                "operationId": "deleteWebhook",
                "summary": "Delete a webhook endpoint",
                "parameters": [
                    {
                        "in": "path",
                        "name": "id",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Webhook endpoint id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Deleted"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Not found (or not owned by the calling key)"
                    }
                }
            }
        },
        "/api/v2/deep-link": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root — overrides the /api/v1 servers default for this v2 path."
                }
            ],
            "post": {
                "operationId": "mintDeepLink",
                "summary": "Mint a single-use deep-link token",
                "description": "Paramus Provider Spec deep-link mint endpoint (server-to-server). Returns a 60-second single-use token + the URL to redirect the user's browser to. Auto-provisions the SynovAI user if they don't exist (DEMO_TIER + 14-day trial, same as a magic-link signup). Per-user keys may only mint for their own owner (403 on cross-user); keys owned by paramus-* system users are treated as global.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["username", "intent"],
                                "properties": {
                                    "username": {
                                        "type": "string",
                                        "description": "Email of the SynovAI user this deep-link will sign in as."
                                    },
                                    "intent": {
                                        "type": "string",
                                        "enum": ["dashboard", "submit", "usage"],
                                        "description": "Which landing path the user should be redirected to after the token is consumed."
                                    }
                                }
                            },
                            "example": {
                                "username": "alice@novartis.example",
                                "intent": "dashboard"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Token minted",
                        "content": {
                            "application/json": {
                                "example": {
                                    "token": "QMg5k0ixTZhHhPkqDtVPrEXYecTCOIg0",
                                    "intent": "dashboard",
                                    "expires_in": 60,
                                    "target_url": "https://platform.synovai.net/deeplink?token=QMg5k0ixTZhHhPkqDtVPrEXYecTCOIg0"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Unsupported intent or missing field"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "description": "Per-user key attempted to mint for a different user, or target user is deactivated"
                    }
                }
            }
        },
        "/api/v2/batch": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root override for batch paths."
                }
            ],
            "post": {
                "operationId": "submitBatch",
                "summary": "Submit a batch / async workflow",
                "description": "Paramus Provider Spec Scenario 2. Fans out N child Tasks (one per input item), returns a batch job_id the caller polls. Supported workflows: screen_library (predict_route over up to 100 SMILES targets).",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["workflow", "input"],
                                "properties": {
                                    "workflow": {
                                        "type": "string",
                                        "enum": ["screen_library"]
                                    },
                                    "input": {
                                        "type": "object",
                                        "description": "Workflow-specific input. For screen_library: targets (string[]), num_steps (int), temperature (string), deep_search (bool), stock_filter (string[])."
                                    },
                                    "callback_url": {
                                        "type": "string",
                                        "format": "uri",
                                        "nullable": true
                                    }
                                }
                            },
                            "example": {
                                "workflow": "screen_library",
                                "input": {
                                    "targets": ["CCO", "CC(=O)Oc1ccccc1C(=O)O"],
                                    "num_steps": 3
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Batch accepted; poll status endpoint until terminal."
                    },
                    "400": {
                        "description": "Missing or invalid field, or unsupported workflow"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "503": {
                        "description": "Worker unavailable or all child dispatches failed"
                    }
                }
            }
        },
        "/api/v2/batch/{job_id}": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root override."
                }
            ],
            "get": {
                "operationId": "getBatchStatus",
                "summary": "Get batch status",
                "description": "Aggregate status derived from child tasks: queued | running | succeeded | failed | cancelled.",
                "parameters": [
                    {
                        "in": "path",
                        "name": "job_id",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Aggregate status"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Batch job not found (or not owned by calling key)"
                    }
                }
            }
        },
        "/api/v2/batch/{job_id}/result": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root override."
                }
            ],
            "get": {
                "operationId": "getBatchResult",
                "summary": "Get final result for a succeeded batch",
                "description": "Returns aggregate result + metrics only when status is succeeded. 409 otherwise.",
                "parameters": [
                    {
                        "in": "path",
                        "name": "job_id",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Aggregate result + metrics"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Batch job not found"
                    },
                    "409": {
                        "description": "Batch not yet succeeded"
                    }
                }
            }
        },
        "/api/v2/batch/{job_id}/cancel": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root override."
                }
            ],
            "post": {
                "operationId": "cancelBatch",
                "summary": "Cancel pending children of a batch",
                "description": "Marks every still-QUEUED child task as CANCELLED. Tasks already running or terminal are left alone.",
                "parameters": [
                    {
                        "in": "path",
                        "name": "job_id",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Cancel processed"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "404": {
                        "description": "Batch job not found"
                    }
                }
            }
        },
        "/deeplink": {
            "servers": [
                {
                    "url": "/",
                    "description": "Absolute root — overrides the /api/v1 servers default for this browser-facing path."
                }
            ],
            "get": {
                "operationId": "consumeDeepLink",
                "summary": "Consume a deep-link token + sign in",
                "description": "Paramus Provider Spec deep-link consume endpoint (browser-facing, step 2 of the handshake). Looks up the token, atomically marks it consumed (single-use), creates a NextAuth Session for the bound user, sets the session cookie, and redirects to the intent's landing path. On invalid / expired / consumed tokens, redirects to /auth-error.",
                "security": [],
                "parameters": [
                    {
                        "in": "query",
                        "name": "token",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The single-use token returned from POST /api/v2/deep-link."
                    }
                ],
                "responses": {
                    "302": {
                        "description": "Redirect to the intent's landing path (or /auth-error on failure)"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "BearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "API key provided as a Bearer token in the Authorization header."
            },
            "ApiKeyAuth": {
                "type": "apiKey",
                "in": "header",
                "name": "X-API-Key",
                "description": "API key provided in the X-API-Key header (Paramus-compatible)."
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Authentication failed",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        },
                        "example": {
                            "status": "error",
                            "error": "Missing or invalid Authorization header"
                        }
                    }
                }
            }
        },
        "schemas": {
            "ErrorResponse": {
                "type": "object",
                "required": ["status", "error"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["error"]
                    },
                    "error": {
                        "type": "string"
                    }
                }
            },
            "HealthResponse": {
                "type": "object",
                "required": ["status", "database", "models", "timestamp"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["ok"]
                    },
                    "database": {
                        "type": "string",
                        "enum": ["ok"]
                    },
                    "models": {
                        "type": "string",
                        "enum": ["ok", "degraded", "unreachable", "unknown"]
                    },
                    "timestamp": {
                        "type": "string",
                        "format": "date-time"
                    }
                }
            },
            "ToolParam": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "required": {
                        "type": "boolean"
                    },
                    "default": {},
                    "description": {
                        "type": "string"
                    },
                    "min": {
                        "type": "integer"
                    },
                    "max": {
                        "type": "integer"
                    },
                    "enum": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "items": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "type": "string"
                            },
                            "enum": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "Tool": {
                "type": "object",
                "required": ["name", "description", "cost_per_call_usd", "params"],
                "properties": {
                    "name": {
                        "type": "string",
                        "enum": ["predict_route", "predict_route_f"]
                    },
                    "description": {
                        "type": "string"
                    },
                    "cost_per_call_usd": {
                        "type": "number"
                    },
                    "params": {
                        "type": "object",
                        "additionalProperties": {
                            "$ref": "#/components/schemas/ToolParam"
                        }
                    }
                }
            },
            "ForecastRequest": {
                "type": "object",
                "required": ["tool"],
                "properties": {
                    "tool": {
                        "type": "string",
                        "enum": ["predict_route", "predict_route_f"],
                        "description": "The tool name to get cost for"
                    }
                }
            },
            "ForecastResponse": {
                "type": "object",
                "required": ["cost_usd"],
                "properties": {
                    "cost_usd": {
                        "type": "number",
                        "description": "Cost in USD for a single call"
                    }
                }
            },
            "ExecuteRequest": {
                "type": "object",
                "required": ["tool", "params"],
                "properties": {
                    "tool": {
                        "type": "string",
                        "enum": ["predict_route", "predict_route_f"],
                        "description": "The tool to execute"
                    },
                    "callback_url": {
                        "type": "string",
                        "format": "uri",
                        "nullable": true,
                        "description": "Optional webhook URL. When provided, the API will POST the job result to this URL when the job reaches a terminal state (success or error). The POST body matches the GET /jobs/{jobId} response format. Fire-and-forget with a 5-second timeout; no retries in v1."
                    },
                    "params": {
                        "type": "object",
                        "required": ["smiles"],
                        "properties": {
                            "smiles": {
                                "type": "string",
                                "description": "Target molecule SMILES string"
                            },
                            "num_steps": {
                                "type": "integer",
                                "minimum": 2,
                                "maximum": 8,
                                "nullable": true,
                                "description": "Number of synthesis steps. Required for predict_route (deprecated alias: predict_route_f)."
                            },
                            "deep_search": {
                                "type": "boolean",
                                "default": false,
                                "description": "Enable deep search for more diverse routes"
                            },
                            "temperature": {
                                "type": "string",
                                "enum": ["Well-Precedented", "Standard", "Exploratory", "Novel"],
                                "default": "Standard",
                                "description": "Route generation temperature"
                            },
                            "stock_filter": {
                                "type": "array",
                                "items": {
                                    "type": "string",
                                    "enum": ["Enamine", "Buyables", "eMolecules"]
                                },
                                "nullable": true,
                                "description": "Filter routes by commercial stock availability"
                            }
                        }
                    }
                }
            },
            "ExecuteAcceptedResponse": {
                "type": "object",
                "required": ["status", "job_id", "poll_url", "cost_usd"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["accepted"]
                    },
                    "job_id": {
                        "type": "string",
                        "description": "Unique job identifier for polling"
                    },
                    "poll_url": {
                        "type": "string",
                        "description": "Relative URL to poll for job status"
                    },
                    "cost_usd": {
                        "type": "number",
                        "description": "Cost charged for this execution"
                    }
                }
            },
            "JobRunningResponse": {
                "type": "object",
                "required": ["status", "job_id", "created_at"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["running"]
                    },
                    "job_id": {
                        "type": "string"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                }
            },
            "JobSuccessResponse": {
                "type": "object",
                "required": ["status", "job_id", "result", "cost_usd", "created_at", "completed_at"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["success"]
                    },
                    "job_id": {
                        "type": "string"
                    },
                    "result": {
                        "type": "object",
                        "description": "Pipeline-specific result data containing retrosynthesis routes"
                    },
                    "cost_usd": {
                        "type": "number"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "completed_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    }
                }
            },
            "JobErrorResponse": {
                "type": "object",
                "required": ["status", "job_id", "error", "created_at"],
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["error"]
                    },
                    "job_id": {
                        "type": "string"
                    },
                    "error": {
                        "type": "string"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                }
            }
        }
    }
}
